Compare commits
767 Commits
d40f5292b6
...
log_chat_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50277c59d7 | ||
|
|
2a13752203 | ||
|
|
8ce3ea5f2c | ||
|
|
b4f3d8ae02 | ||
|
|
d70c30763b | ||
|
|
27297a1ba9 | ||
|
|
2890ccf2b1 | ||
|
|
f9f1889112 | ||
|
|
d9c5821329 | ||
|
|
7fc415de19 | ||
|
|
ec01f63317 | ||
|
|
c9ac702217 | ||
|
|
1913c608a6 | ||
|
|
94ac6cc400 | ||
|
|
97d9520bf0 | ||
|
|
e7300cc430 | ||
|
|
677d7e47a4 | ||
|
|
5f287d38ab | ||
|
|
3a00d5a679 | ||
|
|
d5d43ee97c | ||
|
|
fd12ef1535 | ||
|
|
de8317d9b8 | ||
|
|
b953f4a40d | ||
|
|
b565f0903a | ||
|
|
da71a8271d | ||
|
|
54558169e1 | ||
|
|
bcb953309a | ||
|
|
a371826613 | ||
|
|
82ee3d779c | ||
|
|
4ea0e70589 | ||
|
|
3f4e4db836 | ||
|
|
85020bc2af | ||
|
|
70bb01a2c7 | ||
|
|
38d4719073 | ||
|
|
aebb97967d | ||
|
|
97f81d0aa4 | ||
|
|
2030c17a8b | ||
|
|
b6dcd8d9af | ||
|
|
9a78f22e86 | ||
|
|
19ca3d610b | ||
|
|
dd74d3572a | ||
|
|
ad7e5d62cd | ||
|
|
ece7547695 | ||
|
|
944bc7667a | ||
|
|
0ac28e1f5f | ||
|
|
ca3a57289e | ||
|
|
a4641be7e7 | ||
|
|
0651308210 | ||
|
|
cac6aebb6c | ||
|
|
0baddf46e2 | ||
|
|
e0d893b736 | ||
|
|
2ccfa425b2 | ||
|
|
52102da839 | ||
|
|
fc08f18663 | ||
|
|
cc064d0dad | ||
|
|
8b997a5656 | ||
|
|
7d8fff535d | ||
|
|
0855a9e5bf | ||
|
|
3fa2b9fe79 | ||
|
|
c561509c82 | ||
|
|
0566b6f787 | ||
|
|
e2821933a4 | ||
|
|
34c29f872a | ||
|
|
2192a6b4f9 | ||
|
|
68ad9163ff | ||
|
|
5f6eecc1f6 | ||
|
|
5edca87622 | ||
|
|
fa01c24f23 | ||
|
|
593c2735ce | ||
|
|
48e72c62f3 | ||
|
|
f316d4c757 | ||
|
|
d6dba3e545 | ||
|
|
0eb173e046 | ||
|
|
7d9aa58103 | ||
|
|
874fa67036 | ||
|
|
bf7f81b7fe | ||
|
|
cf7f57252e | ||
|
|
e2296ad2d1 | ||
|
|
332ab244a7 | ||
|
|
ed90322a0b | ||
|
|
9809f6ed08 | ||
|
|
7401182a1b | ||
|
|
2c68964566 | ||
|
|
c639905119 | ||
|
|
9bcf1b3295 | ||
|
|
feef5be143 | ||
|
|
f805d67741 | ||
|
|
497251c0eb | ||
|
|
c111cea7fc | ||
|
|
eff4c66410 | ||
|
|
78e47ea70a | ||
|
|
147cb566f2 | ||
|
|
8452f71481 | ||
|
|
8b11b499ed | ||
|
|
ecaa414508 | ||
|
|
c09d7cbdea | ||
|
|
5a0c8439fc | ||
|
|
43219e16da | ||
|
|
e4539e9cb5 | ||
|
|
67254e0b39 | ||
|
|
09063d23da | ||
|
|
42989c74aa | ||
|
|
637faac90e | ||
|
|
065040e5b8 | ||
|
|
b62c883951 | ||
|
|
63e8835fa0 | ||
|
|
70f62dba4b | ||
|
|
564da2fb8d | ||
|
|
c6af758ee5 | ||
|
|
b57be1cc08 | ||
|
|
c84d088dfa | ||
|
|
631cf4ba74 | ||
|
|
02e1c7444b | ||
|
|
60a752cdef | ||
|
|
48a2e5c7a1 | ||
|
|
52a605787b | ||
|
|
f43d2fb98e | ||
|
|
fb55764887 | ||
|
|
346d267e26 | ||
|
|
8cd08e6a58 | ||
|
|
53218414f8 | ||
|
|
ef1390bbdf | ||
|
|
6bd64677ec | ||
|
|
559a76be8d | ||
|
|
6f6fb5b393 | ||
|
|
10856ccfd0 | ||
|
|
8f14dd6113 | ||
|
|
791595e3ae | ||
|
|
ca3ab78cf8 | ||
|
|
67855f2adf | ||
|
|
f06d7d29ef | ||
|
|
b72d4ab7c6 | ||
|
|
d22bdbe944 | ||
|
|
d24533d561 | ||
|
|
1334575ba9 | ||
|
|
9c6cb501a4 | ||
|
|
5048a50403 | ||
|
|
d450ef43c6 | ||
|
|
6dd076b27f | ||
|
|
79ae71a517 | ||
|
|
bb17cfa179 | ||
|
|
d1583e8587 | ||
|
|
86b9227577 | ||
|
|
d010157611 | ||
|
|
6794b2dc40 | ||
|
|
00f504f6aa | ||
|
|
014bca449f | ||
|
|
464e0dc7d2 | ||
|
|
d6285affec | ||
|
|
b29b685058 | ||
|
|
771b9ddfda | ||
|
|
a69ea79d83 | ||
|
|
c0839d4521 | ||
|
|
fff41a44ed | ||
|
|
eac6d4b617 | ||
|
|
720b925fd5 | ||
|
|
b60b1e369a | ||
|
|
014cbc0cbd | ||
|
|
806f0fd270 | ||
|
|
4e9ef7a334 | ||
|
|
a44e956ed9 | ||
|
|
6bcfc20533 | ||
|
|
e1db0b89bd | ||
|
|
168d5171f4 | ||
|
|
b4882a8b03 | ||
|
|
03dd99699b | ||
|
|
9524db20fe | ||
|
|
96235654f1 | ||
|
|
833e6bd652 | ||
|
|
073ce4a718 | ||
|
|
507cdf1256 | ||
|
|
b5b16df9e0 | ||
|
|
0e438dd508 | ||
|
|
b0899d0ee4 | ||
|
|
9c60ac23a3 | ||
|
|
c050b211eb | ||
|
|
29c4aebe19 | ||
|
|
87aa7c11c5 | ||
|
|
ab50182c92 | ||
|
|
327c1ba23b | ||
|
|
2aa37d9392 | ||
|
|
b09c4f3770 | ||
|
|
251680056d | ||
|
|
e9eb0da2b3 | ||
|
|
e2b26754fe | ||
|
|
87850378c7 | ||
|
|
b073155018 | ||
|
|
7775b42b59 | ||
|
|
a9e5744a37 | ||
|
|
b3b82b97ea | ||
|
|
2612e7f297 | ||
|
|
c39d10b780 | ||
|
|
f21572d22a | ||
|
|
ab6dc5db32 | ||
|
|
b070c0818f | ||
|
|
ec4449f092 | ||
|
|
5d7a24d5d0 | ||
|
|
41f28f2519 | ||
|
|
25dd0508c2 | ||
|
|
a79b449fb2 | ||
|
|
56fabb4561 | ||
|
|
bdce0570bd | ||
|
|
0a7e802d4b | ||
|
|
dd9aca83dd | ||
|
|
32bc99a11a | ||
|
|
1218ca09ae | ||
|
|
db2cf125f8 | ||
|
|
f090f1a02b | ||
|
|
536ffb3f31 | ||
|
|
c124684197 | ||
|
|
0c4dff77c9 | ||
|
|
8101c70c91 | ||
|
|
7f05227e56 | ||
|
|
323204014c | ||
|
|
8a56e14d7a | ||
|
|
77eba309b2 | ||
|
|
a2edc82b0c | ||
|
|
00203201f4 | ||
|
|
c779f4de8d | ||
|
|
fc8c533129 | ||
|
|
723ba5a507 | ||
|
|
2d0b5f5fea | ||
|
|
da8eb68d9d | ||
|
|
058b725ca9 | ||
|
|
1cd3e1bf3f | ||
|
|
c24913ea24 | ||
|
|
9761a68cd4 | ||
|
|
4e5556dccc | ||
|
|
9a46f3053a | ||
|
|
bc3c97398b | ||
|
|
50d4936e51 | ||
|
|
8c5a286574 | ||
|
|
0c0c65df78 | ||
|
|
5c741821a2 | ||
|
|
04d45e1f54 | ||
|
|
014163d7d3 | ||
|
|
2c435c0506 | ||
|
|
6bc613e93f | ||
|
|
b313f47660 | ||
|
|
d05b07a5b0 | ||
|
|
ab28e6a75a | ||
|
|
14ef6b5774 | ||
|
|
578a9fe457 | ||
|
|
ebaed9966b | ||
|
|
b8756c4737 | ||
|
|
88fb83bc57 | ||
|
|
05ed9d9a73 | ||
|
|
261bf88382 | ||
|
|
7b9a173f5a | ||
|
|
5164a11c15 | ||
|
|
0649f3dc32 | ||
|
|
9d5d2ab493 | ||
|
|
822a29aa76 | ||
|
|
ff276b4877 | ||
|
|
bd809e5af7 | ||
|
|
0aac5885fb | ||
|
|
bfb159b9b3 | ||
|
|
c45e78cf1d | ||
|
|
62d1f002dd | ||
|
|
84f8c6a4c6 | ||
|
|
c1f99cb094 | ||
|
|
c64eea7872 | ||
|
|
bebe3f710b | ||
|
|
0bfa53b58d | ||
|
|
e8d5c005a2 | ||
|
|
c58621e036 | ||
|
|
ff5b5149b3 | ||
|
|
81bcff0f8a | ||
|
|
bdc142ae51 | ||
|
|
7b01204eed | ||
|
|
734afd8bcf | ||
|
|
5ddc7b1177 | ||
|
|
59f6a6a2c2 | ||
|
|
3716ffb3b5 | ||
|
|
4312a4d3f4 | ||
|
|
dccab8fd21 | ||
|
|
0138bc9b3f | ||
|
|
a5dd4ffe36 | ||
|
|
fb420e88bd | ||
|
|
0d84804d81 | ||
|
|
b3168928c6 | ||
|
|
c1de85f700 | ||
|
|
387554acbb | ||
|
|
446d37b832 | ||
|
|
e9e6c4b988 | ||
|
|
2ed0656d1b | ||
|
|
fe35c42ead | ||
|
|
a16542e052 | ||
|
|
06cc47847d | ||
|
|
387e594f4a | ||
|
|
0d97f3374c | ||
|
|
ccb1bd7a74 | ||
|
|
2a3271b0d0 | ||
|
|
6a7159e9a1 | ||
|
|
cf0e73e75e | ||
|
|
34a68cd2ca | ||
|
|
cac0438d48 | ||
|
|
9302bac619 | ||
|
|
616046179f | ||
|
|
0c572a862c | ||
|
|
64b2bd4735 | ||
|
|
f337450348 | ||
|
|
bc29707e87 | ||
|
|
a20f5981e2 | ||
|
|
b693f20789 | ||
|
|
69d9bbb400 | ||
|
|
fc4ebf332d | ||
|
|
1a3d3cd31e | ||
|
|
056420c834 | ||
|
|
2af417bf49 | ||
|
|
cbcd413ed1 | ||
|
|
8c174551f5 | ||
|
|
6b463f9d9e | ||
|
|
239891070d | ||
|
|
5fc36bd45f | ||
|
|
2f331548e1 | ||
|
|
34262fb33c | ||
|
|
fa0254cb27 | ||
|
|
d91d1f5466 | ||
|
|
84e6623773 | ||
|
|
0f4f52fa1a | ||
|
|
b48a74f065 | ||
|
|
64f35feb13 | ||
|
|
670067ae3a | ||
|
|
c63be29770 | ||
|
|
4f8ae422ec | ||
|
|
f24a966f53 | ||
|
|
78fdfbfb09 | ||
|
|
bdb0cfe243 | ||
|
|
106c18480f | ||
|
|
97c61e0068 | ||
|
|
6c9e33b76c | ||
|
|
e5a7b6e795 | ||
|
|
affc98851c | ||
|
|
10d7436cf0 | ||
|
|
5fcda6a1a4 | ||
|
|
2c96f6ec8b | ||
|
|
377bb4b6cc | ||
|
|
150e28a672 | ||
|
|
cf21c8e906 | ||
|
|
7859b913bc | ||
|
|
2481bddf58 | ||
|
|
c547f3f26d | ||
|
|
a4bb58007f | ||
|
|
ed5c7bb836 | ||
|
|
af81dca3ff | ||
|
|
188f0e2451 | ||
|
|
0c43801a2c | ||
|
|
8f985118cd | ||
|
|
ec09e618ff | ||
|
|
799c4c9e3c | ||
|
|
25a6b4b6b9 | ||
|
|
714f2c6dc2 | ||
|
|
e04439ea14 | ||
|
|
24093fd0c5 | ||
|
|
c18d10c846 | ||
|
|
4fca85f63d | ||
|
|
dc0f26a1cd | ||
|
|
d4457a4028 | ||
|
|
6c032bb8f7 | ||
|
|
a3d0a50f4d | ||
|
|
519db10f61 | ||
|
|
00a23e6c11 | ||
|
|
6e89bef657 | ||
|
|
5f97e2de5a | ||
|
|
7b82d85b27 | ||
|
|
ac610c54eb | ||
|
|
63247d2d11 | ||
|
|
d3c44de5d2 | ||
|
|
ade07607a5 | ||
|
|
3760b14235 | ||
|
|
a054d2115d | ||
|
|
9d29303142 | ||
|
|
12e1d327ef | ||
|
|
09834d3954 | ||
|
|
8fda46e241 | ||
|
|
8993901641 | ||
|
|
2fe13fe442 | ||
|
|
1a4f366e4b | ||
|
|
2d332d0a13 | ||
|
|
d630a6ef7d | ||
|
|
0c22499534 | ||
|
|
4077f28285 | ||
|
|
4e031a6ea5 | ||
|
|
311d55ff45 | ||
|
|
64cdfcbeab | ||
|
|
6026d088c8 | ||
|
|
53e4d0dd87 | ||
|
|
2ced4abc24 | ||
|
|
b58c1ea5bc | ||
|
|
ca6aa5ebf1 | ||
|
|
f979e6da0f | ||
|
|
32121a38f4 | ||
|
|
2fde98a0d1 | ||
|
|
ead78bc3a3 | ||
|
|
00857df990 | ||
|
|
1a037c06bf | ||
|
|
0741439dd6 | ||
|
|
0e5447d6d2 | ||
|
|
680144b24f | ||
|
|
9a1823d805 | ||
|
|
e96865b55e | ||
|
|
8ba144f43a | ||
|
|
1f10dafbea | ||
|
|
da638a495c | ||
|
|
d83a871520 | ||
|
|
a0b1bdd154 | ||
|
|
34ff23d030 | ||
|
|
f7f304a2e0 | ||
|
|
32ad81d0ff | ||
|
|
ea3a62927d | ||
|
|
ba951b6470 | ||
|
|
6c56ea4c55 | ||
|
|
6a86a99fce | ||
|
|
f270cb3bde | ||
|
|
aa5b193746 | ||
|
|
adf515d50b | ||
|
|
da507b2eed | ||
|
|
adf6a81862 | ||
|
|
264564d006 | ||
|
|
02d31a2f2c | ||
|
|
59473bdf9f | ||
|
|
dd7441e0b4 | ||
|
|
ad833a6fbb | ||
|
|
9f196f2693 | ||
|
|
ac6934405e | ||
|
|
d8100cb9f2 | ||
|
|
2733ed4b1c | ||
|
|
018777472a | ||
|
|
20f6e01afe | ||
|
|
3904576574 | ||
|
|
c4acd8b361 | ||
|
|
8529512edb | ||
|
|
c20cffad5c | ||
|
|
07ed6a889e | ||
|
|
d67e0a4eef | ||
|
|
8e7fa26709 | ||
|
|
deacc7ad65 | ||
|
|
65361ed8dc | ||
|
|
bb1e830264 | ||
|
|
ca6b87d05e | ||
|
|
855568cab7 | ||
|
|
6b0db6699d | ||
|
|
ab9b393238 | ||
|
|
61b124ddf5 | ||
|
|
678b238c1c | ||
|
|
304fc458eb | ||
|
|
caad8ba44b | ||
|
|
db8a28f2c0 | ||
|
|
0f5b78442b | ||
|
|
a5e472dfe6 | ||
|
|
4b7036be0f | ||
|
|
6386e96134 | ||
|
|
b267374d20 | ||
|
|
342fc5b0e9 | ||
|
|
ff49411bc1 | ||
|
|
c80020f451 | ||
|
|
54c2f7d2b4 | ||
|
|
59ad9e3cd7 | ||
|
|
ead6bdecb6 | ||
|
|
104801cdca | ||
|
|
e6914f707a | ||
|
|
acca837142 | ||
|
|
330ca92045 | ||
|
|
499efa1d0a | ||
|
|
89e1d71aec | ||
|
|
2faae285db | ||
|
|
bdef619803 | ||
|
|
3f4f9e7354 | ||
|
|
3b67e425ed | ||
|
|
c8efc5fdd7 | ||
|
|
c2568ebd1f | ||
|
|
2996a1ddde | ||
|
|
2ea2106eca | ||
|
|
9f526610dd | ||
|
|
3259737774 | ||
|
|
360f24f609 | ||
|
|
60cbf79c9b | ||
|
|
d98017c140 | ||
|
|
c0ae7ea497 | ||
|
|
ac53b89421 | ||
|
|
46ba8ef5dd | ||
|
|
cc0f116194 | ||
|
|
8dc255f401 | ||
|
|
dd4bbc3546 | ||
|
|
db0aabcb88 | ||
|
|
a086fdaa5b | ||
|
|
ee6f8ae45d | ||
|
|
91802e6f10 | ||
|
|
d1797a021f | ||
|
|
3ae617c55b | ||
|
|
f6614c1c58 | ||
|
|
889de5e08a | ||
|
|
b97d1a4c6c | ||
|
|
cfde0d7867 | ||
|
|
399cef8fb2 | ||
|
|
58e447d8d0 | ||
|
|
43f339b91e | ||
|
|
9534443771 | ||
|
|
39755a2fce | ||
|
|
73be3641ea | ||
|
|
03b413a892 | ||
|
|
31c37662cf | ||
|
|
57a452a705 | ||
|
|
9d174cd87d | ||
|
|
9a3c39878d | ||
|
|
498c6e3d8b | ||
|
|
25cb3728ca | ||
|
|
fbe147ce61 | ||
|
|
eb287d9b8d | ||
|
|
4dd787be13 | ||
|
|
5d91b678bb | ||
|
|
b3ee3551ca | ||
|
|
2e5ef7f059 | ||
|
|
72646fc7ff | ||
|
|
01fec1ae02 | ||
|
|
3be1de230c | ||
|
|
e83e580f23 | ||
|
|
8e80117eb8 | ||
|
|
0c2d060d43 | ||
|
|
5157bc375d | ||
|
|
b35b560ca1 | ||
|
|
c0da41a18a | ||
|
|
d9f5588a1f | ||
|
|
61c3c252ea | ||
|
|
ed3ca78667 | ||
|
|
6fb7bb1c08 | ||
|
|
57cef527ba | ||
|
|
48a2a75211 | ||
|
|
3f0159cd89 | ||
|
|
7baae40b2d | ||
|
|
fc0bdce151 | ||
|
|
64de28427c | ||
|
|
c4ca3ca743 | ||
|
|
724511e244 | ||
|
|
e3646595ab | ||
|
|
d2ecd0c777 | ||
|
|
a24308baa5 | ||
|
|
aa8e85fbf4 | ||
|
|
11a892f991 | ||
|
|
cf255fc78e | ||
|
|
258de7a6fd | ||
|
|
fcfee31972 | ||
|
|
11b59b0a65 | ||
|
|
0bb2bc651b | ||
|
|
c63788b686 | ||
|
|
60e86f563c | ||
|
|
ce39e97b86 | ||
|
|
06aa378dfd | ||
|
|
43ebb93ff6 | ||
|
|
4fe2ed3df0 | ||
|
|
1a299d10ed | ||
|
|
d1dc6293e8 | ||
|
|
9f1ea57d3c | ||
|
|
917b0512bf | ||
|
|
b9b5b90330 | ||
|
|
216758dbc7 | ||
|
|
96dc085b35 | ||
|
|
b28a3b6a5a | ||
|
|
500ee54f04 | ||
|
|
dd95b199b7 | ||
|
|
3d9ac5a85e | ||
|
|
8503678fc7 | ||
|
|
37ce5e447f | ||
|
|
44d7903a4b | ||
|
|
a3c0cee2cc | ||
|
|
4cc9b1be2b | ||
|
|
f1fba1ed14 | ||
|
|
3bb42522b8 | ||
|
|
143cd8f856 | ||
|
|
4547f3c2b9 | ||
|
|
c3ff5d954a | ||
|
|
43ddee5d30 | ||
|
|
813a1984f9 | ||
|
|
9a5f5f9f8f | ||
|
|
cb55039ec9 | ||
|
|
a51a9700cf | ||
|
|
59d40c8b4e | ||
|
|
7adcba5b7f | ||
|
|
fe6de396f2 | ||
|
|
47af7a9023 | ||
|
|
b1f5367822 | ||
|
|
cd40d150c1 | ||
|
|
98160512b8 | ||
|
|
9a235f2256 | ||
|
|
754e7845f3 | ||
|
|
cbd6b67456 | ||
|
|
1f0e73906e | ||
|
|
f4d1c924d7 | ||
|
|
1a98312595 | ||
|
|
3bc4a6c9dc | ||
|
|
8b96b75960 | ||
|
|
d69dbd2793 | ||
|
|
cd5eb89ebc | ||
|
|
20c683fb4f | ||
|
|
c427e24360 | ||
|
|
feced5505a | ||
|
|
806eebd269 | ||
|
|
b394e15998 | ||
|
|
13d446e27e | ||
|
|
85d62f7e5e | ||
|
|
7515c180b9 | ||
|
|
36d44925cb | ||
|
|
c0f3f97811 | ||
|
|
4e72026ff9 | ||
|
|
74df2d22da | ||
|
|
68d053336b | ||
|
|
876b66b295 | ||
|
|
5eb6ba6e5c | ||
|
|
1dc14ed9f1 | ||
|
|
72bb6c4c99 | ||
|
|
a960eb471b | ||
|
|
e76d89f0db | ||
|
|
d2fdd3c753 | ||
|
|
30de1cdf5d | ||
|
|
6b151e6be5 | ||
|
|
f5450cdf50 | ||
|
|
4b9de8ac42 | ||
|
|
13a6e027ef | ||
|
|
fc77c3ce48 | ||
|
|
85c8f6c446 | ||
|
|
d349209dc9 | ||
|
|
c8779e2a6b | ||
|
|
b55606c37f | ||
|
|
12fb091bbc | ||
|
|
bc37d7169d | ||
|
|
9ae26f2645 | ||
|
|
6367729f98 | ||
|
|
b8b93af977 | ||
|
|
9f96d0c772 | ||
|
|
d5c940ba4c | ||
|
|
144e716cdf | ||
|
|
686040a316 | ||
|
|
d427072cc9 | ||
|
|
60a446123b | ||
|
|
c009f58980 | ||
|
|
79b10ba9a5 | ||
|
|
d05e0f23ea | ||
|
|
26b6118f50 | ||
|
|
0a90c2a95e | ||
|
|
d77fd5c13e | ||
|
|
4dec79a5fb | ||
|
|
b7e0ed9b87 | ||
|
|
0ab7caedd9 | ||
|
|
3824a591d5 | ||
|
|
3e6123f6f6 | ||
|
|
2763e1502b | ||
|
|
0b90622251 | ||
|
|
9b8895df39 | ||
|
|
f6c1453b5b | ||
|
|
7e9619b41b | ||
|
|
90aeb38427 | ||
|
|
6040187844 | ||
|
|
e72d0ed2c6 | ||
|
|
c3b4e2b237 | ||
|
|
7769764b0b | ||
|
|
b25146265d | ||
|
|
e0df59464e | ||
|
|
a67320e431 | ||
|
|
e41279fe6b | ||
|
|
29eaab59be | ||
|
|
541d53127a | ||
|
|
bdcf754d34 | ||
|
|
a67e85e092 | ||
|
|
24536fa296 | ||
|
|
38ed21edd2 | ||
|
|
5d2f2bdd1d | ||
|
|
6515403ae6 | ||
|
|
2f696b2ce7 | ||
|
|
61d51d971c | ||
|
|
90c7680743 | ||
|
|
b59bb998eb | ||
|
|
9845306b68 | ||
|
|
4eb683ab46 | ||
|
|
9d7feb176a | ||
|
|
eab0bf8f82 | ||
|
|
085a4c421b | ||
|
|
4fc4fb2fb3 | ||
|
|
0e5ed6a30c | ||
|
|
5cc59ae3ac | ||
|
|
161f4cbdff | ||
|
|
5b0f69b411 | ||
|
|
23f3f8d90c | ||
|
|
2ac855488b | ||
|
|
c08ddb61b3 | ||
|
|
fb55f2824e | ||
|
|
1b0c93e5ff | ||
|
|
19c8c36030 | ||
|
|
931118e1d8 | ||
|
|
61f1660b38 | ||
|
|
634cf900e6 | ||
|
|
a148f30070 | ||
|
|
3e0daa62c4 | ||
|
|
aac1bae899 | ||
|
|
f5f2f58664 | ||
|
|
f428a44bfc | ||
|
|
ce412e4404 | ||
|
|
7bd4b4558e | ||
|
|
619fb6633a | ||
|
|
bf64339890 | ||
|
|
d9787b168d | ||
|
|
4a81d9b6f7 | ||
|
|
4370c47f6e | ||
|
|
f69e6289b5 | ||
|
|
bfd0cd7108 | ||
|
|
df534736a1 | ||
|
|
93a97d5d6f | ||
|
|
3275875ae5 | ||
|
|
88f830a9e5 | ||
|
|
c609c4af14 | ||
|
|
94c8339e17 | ||
|
|
b742a776eb | ||
|
|
1899eed839 | ||
|
|
43d1a20d8c | ||
|
|
1692f32ffc | ||
|
|
e07869e71f | ||
|
|
c9dfb215ae | ||
|
|
98896f9a75 | ||
|
|
a14cc8cc4d | ||
|
|
e1940eec77 | ||
|
|
a1dfb42812 | ||
|
|
3b2fad6ea8 | ||
|
|
d9df27d574 | ||
|
|
ae45707c84 | ||
|
|
e22d7b31f9 | ||
|
|
0dcb341059 | ||
|
|
3ecb267594 | ||
|
|
2744b44d93 | ||
|
|
0528ef58b2 | ||
|
|
2a223363b8 | ||
|
|
de9a5eb71e | ||
|
|
2b0afd6acb | ||
|
|
3ab421cbe3 | ||
|
|
54dac39e83 | ||
|
|
2de212710a | ||
|
|
60fbecd4a7 | ||
|
|
82458b5f7e | ||
|
|
d0974cfdd2 | ||
|
|
da16e4ed99 | ||
|
|
55536bba4c | ||
|
|
388222c5c7 | ||
|
|
169c60883b | ||
|
|
285443f10f | ||
|
|
d83e579dfe | ||
|
|
486a07602b | ||
|
|
949ba589c0 | ||
|
|
b6a5d19871 | ||
|
|
ce002ce8c1 | ||
|
|
9c3e366d03 | ||
|
|
bb96e22e64 | ||
|
|
a691f2ebac | ||
|
|
7638822e49 | ||
|
|
a9cf728ee1 | ||
|
|
2c4a135c2b | ||
|
|
d686634c0b | ||
|
|
32b0003a72 | ||
|
|
c234b4c78f | ||
|
|
f2a242b09a | ||
|
|
a1efb28f0b | ||
|
|
d217ab39c2 | ||
|
|
31840328b7 | ||
|
|
54547a11d0 | ||
|
|
f99db8d754 | ||
|
|
1ce916182d | ||
|
|
09ba09f4e3 | ||
|
|
2ac85ac61d | ||
|
|
44e024a94e |
944
.editorconfig
944
.editorconfig
File diff suppressed because it is too large
Load Diff
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
patreon: orahosting
|
||||||
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -14,11 +14,14 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Remove System .NET
|
||||||
|
run: sudo apt-get remove -y dotnet*
|
||||||
|
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -39,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check Code
|
- name: Check Code
|
||||||
run: |
|
run: |
|
||||||
@@ -57,10 +60,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
|
|||||||
147
.github/workflows/documentation.yml
vendored
147
.github/workflows/documentation.yml
vendored
@@ -1,6 +1,9 @@
|
|||||||
name: Deploy Documentation
|
name: Deploy Documentation
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ bleed ]
|
||||||
|
tags: [ 'release-*', 'playtest-*' ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -12,18 +15,49 @@ permissions:
|
|||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
wiki:
|
prepare:
|
||||||
name: Update Wiki
|
name: Prepare version strings
|
||||||
if: github.repository == 'openra/openra'
|
if: github.repository == 'openra/openra'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
|
- name: Prepare environment variables
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
|
if [ "${{ github.ref_type }}" = "tag" ]; then
|
||||||
|
VERSION_TYPE=`echo "${GITHUB_REF#refs/tags/}" | cut -d"-" -f1`
|
||||||
|
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||||
|
echo "VERSION_TYPE=$VERSION_TYPE" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "GIT_TAG=bleed" >> $GITHUB_ENV
|
||||||
|
echo "VERSION_TYPE=bleed" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
VERSION_TYPE=`echo "${{ github.event.inputs.tag }}" | cut -d"-" -f1`
|
||||||
|
echo "GIT_TAG=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||||
|
echo "VERSION_TYPE=$VERSION_TYPE" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
outputs:
|
||||||
|
git_tag: ${{ env.GIT_TAG }}
|
||||||
|
version_type: ${{ env.VERSION_TYPE }}
|
||||||
|
|
||||||
|
wiki:
|
||||||
|
name: Update Wiki
|
||||||
|
needs: prepare
|
||||||
|
if: github.repository == 'openra/openra' && needs.prepare.outputs.version_type != 'bleed'
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Debug output
|
||||||
|
run: |
|
||||||
|
echo ${{ needs.prepare.outputs.git_tag }}
|
||||||
|
echo ${{ needs.prepare.outputs.version_type }}
|
||||||
|
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.tag }}
|
ref: ${{ needs.prepare.outputs.git_tag }}
|
||||||
|
|
||||||
- name: Install .NET 6
|
- name: Install .NET 6
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -32,49 +66,53 @@ jobs:
|
|||||||
make all
|
make all
|
||||||
|
|
||||||
- name: Clone Wiki
|
- name: Clone Wiki
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: openra/openra.wiki
|
repository: openra/openra.wiki
|
||||||
token: ${{ secrets.DOCS_TOKEN }}
|
token: ${{ secrets.DOCS_TOKEN }}
|
||||||
path: wiki
|
path: wiki
|
||||||
|
|
||||||
- name: Update Wiki (Playtest)
|
- name: Update Wiki (Playtest)
|
||||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
if: startsWith(needs.prepare.outputs.git_tag, 'playtest-')
|
||||||
env:
|
|
||||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
|
||||||
run: |
|
run: |
|
||||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
|
./utility.sh all --settings-docs "${{ needs.prepare.outputs.git_tag }}" > "wiki/Settings (playtest).md"
|
||||||
|
|
||||||
- name: Update Wiki (Release)
|
- name: Update Wiki (Release)
|
||||||
if: startsWith(github.event.inputs.tag, 'release-')
|
if: startsWith(needs.prepare.outputs.git_tag, 'release-')
|
||||||
env:
|
|
||||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
|
||||||
run: |
|
run: |
|
||||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
|
./utility.sh all --settings-docs "${{ needs.prepare.outputs.git_tag }}" > "wiki/Settings.md"
|
||||||
|
|
||||||
- name: Push Wiki
|
- name: Push Wiki
|
||||||
env:
|
|
||||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
|
||||||
run: |
|
run: |
|
||||||
cd wiki
|
cd wiki
|
||||||
git config --local user.email "actions@github.com"
|
git config --local user.email "actions@github.com"
|
||||||
git config --local user.name "GitHub Actions"
|
git config --local user.name "GitHub Actions"
|
||||||
git add --all
|
git status
|
||||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
git diff-index --quiet HEAD || \
|
||||||
git push origin master
|
(
|
||||||
|
git add --all && \
|
||||||
|
git commit -m "Update auto-generated documentation for ${{ needs.prepare.outputs.git_tag }}" && \
|
||||||
|
git push origin master
|
||||||
|
)
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
name: Update docs.openra.net
|
name: Update docs.openra.net
|
||||||
|
needs: prepare
|
||||||
if: github.repository == 'openra/openra'
|
if: github.repository == 'openra/openra'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
|
- name: Debug output
|
||||||
|
run: |
|
||||||
|
echo ${{ needs.prepare.outputs.git_tag }}
|
||||||
|
echo ${{ needs.prepare.outputs.version_type }}
|
||||||
|
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.tag }}
|
ref: ${{ needs.prepare.outputs.git_tag }}
|
||||||
|
|
||||||
- name: Install .NET 6
|
- name: Install .NET 6
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -82,62 +120,31 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
make all
|
make all
|
||||||
|
|
||||||
- name: Clone docs.openra.net (Playtest)
|
# version_type is release/playtest/bleed - the name of the target branch.
|
||||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
- name: Clone docs.openra.net
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: openra/docs
|
repository: openra/docs
|
||||||
token: ${{ secrets.DOCS_TOKEN }}
|
token: ${{ secrets.DOCS_TOKEN }}
|
||||||
path: docs
|
path: docs
|
||||||
ref: playtest
|
ref: ${{ needs.prepare.outputs.version_type }}
|
||||||
|
|
||||||
- name: Clone docs.openra.net (Release)
|
- name: Generate docs files
|
||||||
if: startsWith(github.event.inputs.tag, 'release-')
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
repository: openra/docs
|
|
||||||
token: ${{ secrets.DOCS_TOKEN }}
|
|
||||||
path: docs
|
|
||||||
ref: release
|
|
||||||
|
|
||||||
- name: Update docs.openra.net (Playtest)
|
|
||||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
|
||||||
env:
|
|
||||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
|
||||||
run: |
|
run: |
|
||||||
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
./utility.sh all --docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
||||||
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
|
./utility.sh all --weapon-docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
|
||||||
./utility.sh all --sprite-sequence-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
|
./utility.sh all --sprite-sequence-docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
|
||||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/lua.md"
|
./utility.sh all --lua-docs "${{ needs.prepare.outputs.git_tag }}" > "docs/api/lua.md"
|
||||||
|
|
||||||
- name: Update docs.openra.net (Release)
|
- name: Update docs.openra.net
|
||||||
if: startsWith(github.event.inputs.tag, 'release-')
|
|
||||||
env:
|
|
||||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
|
||||||
run: |
|
|
||||||
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
|
||||||
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
|
|
||||||
./utility.sh all --sprite-sequence-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
|
|
||||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/lua.md"
|
|
||||||
|
|
||||||
- name: Commit docs.openra.net
|
|
||||||
env:
|
|
||||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
|
||||||
run: |
|
run: |
|
||||||
cd docs
|
cd docs
|
||||||
git config --local user.email "actions@github.com"
|
git config --local user.email "actions@github.com"
|
||||||
git config --local user.name "GitHub Actions"
|
git config --local user.name "GitHub Actions"
|
||||||
git add api/*.md
|
git status
|
||||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
git diff-index --quiet HEAD || \
|
||||||
|
(
|
||||||
- name: Push docs.openra.net (Release)
|
git add api/*.md && \
|
||||||
if: startsWith(github.event.inputs.tag, 'release-')
|
git commit -m "Update auto-generated documentation for ${{ needs.prepare.outputs.git_tag }}" && \
|
||||||
run: |
|
git push origin ${{ needs.prepare.outputs.version_type }}
|
||||||
cd docs
|
)
|
||||||
git push origin release
|
|
||||||
|
|
||||||
- name: Push docs.openra.net (Playtest)
|
|
||||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
|
||||||
run: |
|
|
||||||
cd docs
|
|
||||||
git push origin playtest
|
|
||||||
|
|||||||
85
.github/workflows/itch.yml
vendored
85
.github/workflows/itch.yml
vendored
@@ -15,73 +15,56 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
if: github.repository == 'openra/openra'
|
if: github.repository == 'openra/openra'
|
||||||
steps:
|
steps:
|
||||||
- name: Download Packages
|
- name: Download Butler
|
||||||
|
env:
|
||||||
|
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
run: |
|
run: |
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
wget -cq -O butler-linux-amd64.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
|
||||||
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"
|
unzip butler-linux-amd64.zip
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
rm butler-linux-amd64.zip
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
chmod +x butler
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
./butler -V
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
./butler login
|
||||||
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
|
- name: Publish Windows Installer
|
||||||
uses: josephbmanley/butler-publish-itchio-action@master
|
|
||||||
env:
|
env:
|
||||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
CHANNEL: win
|
run: |
|
||||||
ITCH_GAME: openra
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
||||||
ITCH_USER: openra
|
./butler push "OpenRA-${{ github.event.inputs.tag }}-x64.exe" "openra/openra:win" --userversion ${{ github.event.inputs.tag }}
|
||||||
VERSION: ${{ github.event.inputs.tag }}
|
|
||||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe
|
|
||||||
|
|
||||||
- name: Publish Windows Itch Bundle
|
- name: Publish Windows Itch Bundle
|
||||||
uses: josephbmanley/butler-publish-itchio-action@master
|
|
||||||
env:
|
env:
|
||||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
CHANNEL: itch
|
run: |
|
||||||
ITCH_GAME: openra
|
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
||||||
ITCH_USER: openra
|
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
||||||
VERSION: ${{ github.event.inputs.tag }}
|
./butler push "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" "openra/openra:itch" --userversion ${{ github.event.inputs.tag }}
|
||||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
|
||||||
|
|
||||||
- name: Publish macOS Package
|
- name: Publish macOS Package
|
||||||
uses: josephbmanley/butler-publish-itchio-action@master
|
|
||||||
env:
|
env:
|
||||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
CHANNEL: macos
|
run: |
|
||||||
ITCH_GAME: openra
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
||||||
ITCH_USER: openra
|
./butler push "OpenRA-${{ github.event.inputs.tag }}.dmg" "openra/openra:macos" --userversion ${{ github.event.inputs.tag }}
|
||||||
VERSION: ${{ github.event.inputs.tag }}
|
|
||||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg
|
|
||||||
|
|
||||||
- name: Publish RA AppImage
|
- name: Publish RA AppImage
|
||||||
uses: josephbmanley/butler-publish-itchio-action@master
|
|
||||||
env:
|
env:
|
||||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
CHANNEL: linux-ra
|
run: |
|
||||||
ITCH_GAME: openra
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||||
ITCH_USER: openra
|
./butler push "OpenRA-Red-Alert-x86_64.AppImage" "openra/openra:linux-ra" --userversion ${{ github.event.inputs.tag }}
|
||||||
VERSION: ${{ github.event.inputs.tag }}
|
|
||||||
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
|
||||||
|
|
||||||
- name: Publish TD AppImage
|
- name: Publish TD AppImage
|
||||||
uses: josephbmanley/butler-publish-itchio-action@master
|
|
||||||
env:
|
env:
|
||||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
CHANNEL: linux-cnc
|
run: |
|
||||||
ITCH_GAME: openra
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||||
ITCH_USER: openra
|
./butler push "OpenRA-Tiberian-Dawn-x86_64.AppImage" "openra/openra:linux-cnc" --userversion ${{ github.event.inputs.tag }}
|
||||||
VERSION: ${{ github.event.inputs.tag }}
|
|
||||||
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
|
||||||
|
|
||||||
- name: Publish D2k AppImage
|
- name: Publish D2k AppImage
|
||||||
uses: josephbmanley/butler-publish-itchio-action@master
|
|
||||||
env:
|
env:
|
||||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
CHANNEL: linux-d2k
|
run: |
|
||||||
ITCH_GAME: openra
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||||
ITCH_USER: openra
|
./butler push "OpenRA-Dune-2000-x86_64.AppImage" "openra/openra:linux-d2k" --userversion ${{ github.event.inputs.tag }}
|
||||||
VERSION: ${{ github.event.inputs.tag }}
|
|
||||||
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
|
||||||
|
|||||||
54
.github/workflows/packaging.yml
vendored
54
.github/workflows/packaging.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||||
@@ -40,10 +40,10 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -53,27 +53,25 @@ jobs:
|
|||||||
- name: Package AppImages
|
- name: Package AppImages
|
||||||
run: |
|
run: |
|
||||||
mkdir -p build/linux
|
mkdir -p build/linux
|
||||||
sudo apt install libfuse2
|
sudo apt-get install -y desktop-file-utils
|
||||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||||
|
|
||||||
- name: Upload Packages
|
- name: Upload Packages
|
||||||
uses: svenstaro/upload-release-action@v2
|
env:
|
||||||
with:
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
shell: bash
|
||||||
tag: ${{ github.ref }}
|
run: |
|
||||||
overwrite: true
|
gh release upload ${{ github.ref_name }} build/linux/*
|
||||||
file_glob: true
|
|
||||||
file: build/linux/*
|
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
name: macOS Disk Image
|
name: macOS Disk Image
|
||||||
runs-on: macos-11
|
runs-on: macos-13
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -92,23 +90,21 @@ jobs:
|
|||||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||||
|
|
||||||
- name: Upload Package
|
- name: Upload Package
|
||||||
uses: svenstaro/upload-release-action@v2
|
env:
|
||||||
with:
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
shell: bash
|
||||||
tag: ${{ github.ref }}
|
run: |
|
||||||
overwrite: true
|
gh release upload ${{ github.ref_name }} build/macos/*
|
||||||
file_glob: true
|
|
||||||
file: build/macos/*
|
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
name: Windows Installers
|
name: Windows Installers
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -124,10 +120,8 @@ jobs:
|
|||||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||||
|
|
||||||
- name: Upload Packages
|
- name: Upload Packages
|
||||||
uses: svenstaro/upload-release-action@v2
|
env:
|
||||||
with:
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
shell: bash
|
||||||
tag: ${{ github.ref }}
|
run: |
|
||||||
overwrite: true
|
gh release upload ${{ github.ref_name }} build/windows/*
|
||||||
file_glob: true
|
|
||||||
file: build/windows/*
|
|
||||||
|
|||||||
60
AUTHORS
60
AUTHORS
@@ -1,5 +1,4 @@
|
|||||||
OpenRA wouldn't be where it is today without the
|
OpenRA wouldn't be where it is today without the hard work of many contributors.
|
||||||
hard work of many contributors.
|
|
||||||
|
|
||||||
The OpenRA developers are:
|
The OpenRA developers are:
|
||||||
* Gustas Kažukauskas (PunkPun)
|
* Gustas Kažukauskas (PunkPun)
|
||||||
@@ -98,6 +97,7 @@ Also thanks to:
|
|||||||
* Kanar
|
* Kanar
|
||||||
* Kenny Hoxworth (hoxworth)
|
* Kenny Hoxworth (hoxworth)
|
||||||
* Kevin Azzam (ChaoticMind)
|
* Kevin Azzam (ChaoticMind)
|
||||||
|
* Kevin Streser
|
||||||
* Krishnakanth Mallik
|
* Krishnakanth Mallik
|
||||||
* Kyle Smith (Smitty)
|
* Kyle Smith (Smitty)
|
||||||
* Kyrre Soerensen (zypres)
|
* Kyrre Soerensen (zypres)
|
||||||
@@ -150,6 +150,7 @@ Also thanks to:
|
|||||||
* Teemu Nieminen (Temeez)
|
* Teemu Nieminen (Temeez)
|
||||||
* Thomas Christlieb (ThomasChr)
|
* Thomas Christlieb (ThomasChr)
|
||||||
* Tim Mylemans (gecko)
|
* Tim Mylemans (gecko)
|
||||||
|
* Tinix
|
||||||
* Tirili
|
* Tirili
|
||||||
* Tomas Einarsson (Mesacer)
|
* Tomas Einarsson (Mesacer)
|
||||||
* Tom van Leth (tovl)
|
* Tom van Leth (tovl)
|
||||||
@@ -161,61 +162,42 @@ Also thanks to:
|
|||||||
* Wojciech Walaszek (Voidwalker)
|
* Wojciech Walaszek (Voidwalker)
|
||||||
* Wuschel
|
* Wuschel
|
||||||
|
|
||||||
Using GNU FreeFont distributed under the GNU GPL
|
Using GNU FreeFont distributed under the GNU GPL terms.
|
||||||
terms.
|
|
||||||
|
|
||||||
Using Simple DirectMedia Layer distributed under
|
Using Simple DirectMedia Layer distributed under the terms of the zlib license.
|
||||||
the terms of the zlib license.
|
|
||||||
|
|
||||||
Using FreeType distributed under the terms of the
|
Using FreeType distributed under the terms of the FreeType License.
|
||||||
FreeType License.
|
|
||||||
|
|
||||||
Using OpenAL Soft distributed under the GNU LGPL.
|
Using OpenAL Soft distributed under the GNU LGPL.
|
||||||
|
|
||||||
Using SDL2-CS and OpenAL-CS created by Ethan
|
Using SDL2-CS and OpenAL-CS created by Ethan Lee and released under the zlib license.
|
||||||
Lee and released under the zlib license.
|
|
||||||
|
|
||||||
Using Eluant created by Chris Howie and released
|
Using Eluant created by Chris Howie and released under the MIT license.
|
||||||
under the MIT license.
|
|
||||||
|
|
||||||
Using FuzzyLogicLibrary (fuzzynet) by Dmitry
|
Using FuzzyLogicLibrary (fuzzynet) by Dmitry Kaluzhny and released under the GNU GPL terms.
|
||||||
Kaluzhny and released under the GNU GPL terms.
|
|
||||||
|
|
||||||
Using Mono.Nat by Alan McGovern, Ben Motmans,
|
Using Mono.Nat by Alan McGovern, Ben Motmans, Nicholas Terry distributed under the MIT license.
|
||||||
Nicholas Terry distributed under the MIT license.
|
|
||||||
|
|
||||||
Using MP3Sharp by Robert Bruke and Zane Wagner
|
Using MP3Sharp by Robert Bruke and Zane Wagner licensed under the GNU LGPL Version 3.
|
||||||
licensed under the GNU LGPL Version 3.
|
|
||||||
|
|
||||||
Using TagLib# by Stephen Shaw licensed under the
|
Using TagLib# by Stephen Shaw licensed under the GNU LGPL Version 2.1.
|
||||||
GNU LGPL Version 2.1.
|
|
||||||
|
|
||||||
Using NVorbis by Andrew Ward distributed under
|
Using NVorbis by Andrew Ward distributed under the MIT license.
|
||||||
the MIT license.
|
|
||||||
|
|
||||||
Using ICSharpCode.SharpZipLib initially by Mike
|
Using ICSharpCode.SharpZipLib initially by Mike Krueger and distributed under the GNU GPL terms.
|
||||||
Krueger and distributed under the GNU GPL terms.
|
|
||||||
|
|
||||||
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
Using rix0rrr.BeaconLib developed by Rico Huijbers distributed under MIT License.
|
||||||
distributed under MIT License.
|
|
||||||
|
|
||||||
Using DiscordRichPresence developed by Lachee
|
Using DiscordRichPresence developed by Lachee distributed under MIT License.
|
||||||
distributed under MIT License.
|
|
||||||
|
|
||||||
Using Json.NET developed by James Newton-King
|
Using Json.NET developed by James Newton-King distributed under MIT License.
|
||||||
distributed under MIT License.
|
|
||||||
|
|
||||||
Using ANGLE distributed under the BS3 3-Clause license.
|
Using ANGLE distributed under the BS3 3-Clause license.
|
||||||
|
|
||||||
Using Pfim developed by Nick Babcock
|
Using Pfim developed by Nick Babcock distributed under the MIT license.
|
||||||
distributed under the MIT license.
|
|
||||||
|
|
||||||
Using Linguini by the Space Station 14 team
|
Using Linguini by the Space Station 14 team licensed under Apache and MIT terms.
|
||||||
licensed under Apache and MIT terms.
|
|
||||||
|
|
||||||
This site or product includes IP2Location LITE data
|
This site or product includes IP2Location LITE data available from https://www.ip2location.com.
|
||||||
available from http://www.ip2location.com.
|
|
||||||
|
|
||||||
Finally, special thanks goes to the original teams
|
Finally, special thanks goes to the original teams at Westwood Studios and EA for creating the classic games which OpenRA aims to reimagine.
|
||||||
at Westwood Studios and EA for creating the classic
|
|
||||||
games which OpenRA aims to reimagine.
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ members of the project's leadership.
|
|||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
available at [http://contributor-covenant.org/version/1/4][version]
|
available at [https://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
[homepage]: http://contributor-covenant.org
|
[homepage]: https://contributor-covenant.org
|
||||||
[version]: http://contributor-covenant.org/version/1/4/
|
[version]: https://contributor-covenant.org/version/1/4/
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Help us keep OpenRA open and inclusive. Please read and follow our [Code of Cond
|
|||||||
|
|
||||||
* [Coding standard](https://github.com/OpenRA/OpenRA/wiki/Coding-Standard)
|
* [Coding standard](https://github.com/OpenRA/OpenRA/wiki/Coding-Standard)
|
||||||
* [Branches and Releases](https://github.com/OpenRA/OpenRA/wiki/Branches-and-Releases)
|
* [Branches and Releases](https://github.com/OpenRA/OpenRA/wiki/Branches-and-Releases)
|
||||||
* [Licensing](http://www.gnu.org/licenses/quick-guide-gplv3.html)
|
* [Licensing](https://www.gnu.org/licenses/quick-guide-gplv3.html)
|
||||||
|
|
||||||
Please `git rebase` to the latest revision of the bleed branch.
|
Please `git rebase` to the latest revision of the bleed branch.
|
||||||
|
|
||||||
|
|||||||
8
COPYING
8
COPYING
@@ -1,7 +1,7 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
|
|||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
<http://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
The GNU General Public License does not permit incorporating your program
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
may consider it more useful to permit linking proprietary applications with
|
may consider it more useful to permit linking proprietary applications with
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License. But first, please read
|
Public License instead of this License. But first, please read
|
||||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
<https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<Optimize>false</Optimize>
|
<Optimize>false</Optimize>
|
||||||
<!-- Enable only for Debug builds to improve compile-time performance for Release builds -->
|
<!-- Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
|
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
|
||||||
rule to run in command line builds. https://github.com/dotnet/roslyn/issues/41640
|
rule to run in command line builds. https://github.com/dotnet/roslyn/issues/41640
|
||||||
Enable only for Debug builds to improve compile-time performance for Release builds -->
|
Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -51,8 +51,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<!-- StyleCop -->
|
<!-- StyleCop/Roslynator -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||||
|
<!-- Roslynator analyzers fail to run under Mono (AD0001) -->
|
||||||
|
<PackageReference Include="Roslynator.Analyzers" Version="4.2.0" PrivateAssets="All" Condition="'$(MSBuildRuntimeType)'!='Mono'" />
|
||||||
|
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.2.0" PrivateAssets="All" Condition="'$(MSBuildRuntimeType)'!='Mono'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Windows
|
|||||||
=======
|
=======
|
||||||
|
|
||||||
Compiling OpenRA requires the following dependencies:
|
Compiling OpenRA requires the following dependencies:
|
||||||
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
* [Windows PowerShell >= 4.0](https://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
||||||
* [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) (or via Visual Studio)
|
* [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) (or via Visual Studio)
|
||||||
|
|
||||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||||
@@ -25,7 +25,7 @@ To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if u
|
|||||||
|
|
||||||
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`.
|
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`.
|
||||||
|
|
||||||
If you choose to use system libraries, or your system is not x86_64, you will need to install [SDL 2](https://www.libsdl.org/download-2.0.php), [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm), [OpenAL](https://openal-soft.org/), and [liblua 5.1](http://luabinaries.sourceforge.net/download.html) before compiling OpenRA.
|
If you choose to use system libraries, or your system is not x86_64, you will need to install [SDL 2](https://www.libsdl.org/download-2.0.php), [FreeType](https://gnuwin32.sourceforge.net/packages/freetype.htm), [OpenAL](https://openal-soft.org/), and [liblua 5.1](https://luabinaries.sourceforge.net/download.html) before compiling OpenRA.
|
||||||
|
|
||||||
These can be installed using your package manager on various distros:
|
These can be installed using your package manager on various distros:
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -153,12 +153,12 @@ tests:
|
|||||||
|
|
||||||
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
||||||
#
|
#
|
||||||
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
|
version: VERSION mods/*/mod.yaml
|
||||||
ifeq ($(VERSION),)
|
ifeq ($(VERSION),)
|
||||||
$(error Unable to determine new version (requires git or override of variable VERSION))
|
$(error Unable to determine new version (requires git or override of variable VERSION))
|
||||||
endif
|
endif
|
||||||
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
|
@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'
|
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/*/mod.yaml'
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
|
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
|
||||||
|
|||||||
@@ -146,18 +146,22 @@ namespace OpenRA.Activities
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
/// Called every tick to run activity logic. Returns false if the activity should
|
/// Called every tick to run activity logic. Returns false if the activity should
|
||||||
/// remain active, or true if it is complete. Cancelled activities must ensure they
|
/// remain active, or true if it is complete. Cancelled activities must ensure they
|
||||||
/// return the actor to a consistent state before returning true.
|
/// return the actor to a consistent state before returning true.
|
||||||
///
|
/// </para>
|
||||||
|
/// <para>
|
||||||
/// Child activities can be queued using QueueChild, and these will be ticked
|
/// Child activities can be queued using QueueChild, and these will be ticked
|
||||||
/// instead of the parent while they are active. Activities that need to run logic
|
/// instead of the parent while they are active. Activities that need to run logic
|
||||||
/// in parallel with child activities should set ChildHasPriority to false and
|
/// in parallel with child activities should set ChildHasPriority to false and
|
||||||
/// manually call TickChildren.
|
/// manually call TickChildren.
|
||||||
///
|
/// </para>
|
||||||
|
/// <para>
|
||||||
/// Queuing one or more child activities and returning true is valid, and causes
|
/// Queuing one or more child activities and returning true is valid, and causes
|
||||||
/// the activity to be completed immediately (without ticking again) once the
|
/// the activity to be completed immediately (without ticking again) once the
|
||||||
/// children have completed.
|
/// children have completed.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool Tick(Actor self)
|
public virtual bool Tick(Actor self)
|
||||||
{
|
{
|
||||||
@@ -222,10 +226,11 @@ namespace OpenRA.Activities
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prints the activity tree, starting from the top or optionally from a given origin.
|
/// <para>Prints the activity tree, starting from the top or optionally from a given origin.</para>
|
||||||
///
|
/// <para>
|
||||||
/// Call this method from any place that's called during a tick, such as the Tick() method itself or
|
/// Call this method from any place that's called during a tick, such as the Tick() method itself or
|
||||||
/// the Before(First|Last)Run() methods. The origin activity will be marked in the output.
|
/// the Before(First|Last)Run() methods. The origin activity will be marked in the output.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="self">The actor performing this activity.</param>
|
/// <param name="self">The actor performing this activity.</param>
|
||||||
/// <param name="origin">Activity from which to start traversing, and which to mark. If null, mark the calling activity, and start traversal from the top.</param>
|
/// <param name="origin">Activity from which to start traversing, and which to mark. If null, mark the calling activity, and start traversal from the top.</param>
|
||||||
|
|||||||
@@ -71,7 +71,12 @@ namespace OpenRA
|
|||||||
public IEffectiveOwner EffectiveOwner { get; }
|
public IEffectiveOwner EffectiveOwner { get; }
|
||||||
public IOccupySpace OccupiesSpace { get; }
|
public IOccupySpace OccupiesSpace { get; }
|
||||||
public ITargetable[] Targetables { get; }
|
public ITargetable[] Targetables { get; }
|
||||||
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; private set; }
|
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; }
|
||||||
|
readonly ICrushable[] crushables;
|
||||||
|
public ICrushable[] Crushables
|
||||||
|
{
|
||||||
|
get => crushables ?? throw new InvalidOperationException($"Crushables for {Info.Name} are not initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsIdle => CurrentActivity == null;
|
public bool IsIdle => CurrentActivity == null;
|
||||||
public bool IsDead => Disposed || (health != null && health.IsDead);
|
public bool IsDead => Disposed || (health != null && health.IsDead);
|
||||||
@@ -155,6 +160,7 @@ namespace OpenRA
|
|||||||
var targetablesList = new List<ITargetable>();
|
var targetablesList = new List<ITargetable>();
|
||||||
var targetablePositionsList = new List<ITargetablePositions>();
|
var targetablePositionsList = new List<ITargetablePositions>();
|
||||||
var syncHashesList = new List<SyncHash>();
|
var syncHashesList = new List<SyncHash>();
|
||||||
|
var crushablesList = new List<ICrushable>();
|
||||||
|
|
||||||
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
||||||
{
|
{
|
||||||
@@ -181,6 +187,7 @@ namespace OpenRA
|
|||||||
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
||||||
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
||||||
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
||||||
|
{ if (trait is ICrushable t) crushablesList.Add(t); }
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveOrders = resolveOrdersList.ToArray();
|
resolveOrders = resolveOrdersList.ToArray();
|
||||||
@@ -195,6 +202,7 @@ namespace OpenRA
|
|||||||
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||||
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||||
SyncHashes = syncHashesList.ToArray();
|
SyncHashes = syncHashesList.ToArray();
|
||||||
|
crushables = crushablesList.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ using OpenRA.Scripting;
|
|||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public readonly struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
|
public readonly struct CPos : IEquatable<CPos>, IScriptBindable,
|
||||||
|
ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, ILuaToStringBinding
|
||||||
{
|
{
|
||||||
// Coordinates are packed in a 32 bit signed int
|
// Coordinates are packed in a 32 bit signed int
|
||||||
// X and Y are 12 bits (signed): -2048...2047
|
// X and Y are 12 bits (signed): -2048...2047
|
||||||
@@ -96,7 +97,8 @@ namespace OpenRA
|
|||||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
{
|
{
|
||||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec 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 ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments " +
|
||||||
|
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||||
|
|
||||||
return new LuaCustomClrObject(a + b);
|
return new LuaCustomClrObject(a + b);
|
||||||
}
|
}
|
||||||
@@ -105,7 +107,8 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var rightType = right.WrappedClrType();
|
var rightType = right.WrappedClrType();
|
||||||
if (!left.TryGetClrValue(out CPos a))
|
if (!left.TryGetClrValue(out CPos a))
|
||||||
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
|
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments " +
|
||||||
|
$"({left.WrappedClrType().Name}, {rightType.Name})");
|
||||||
|
|
||||||
if (rightType == typeof(CPos))
|
if (rightType == typeof(CPos))
|
||||||
{
|
{
|
||||||
@@ -118,7 +121,8 @@ namespace OpenRA
|
|||||||
return new LuaCustomClrObject(a - b);
|
return new LuaCustomClrObject(a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
|
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments " +
|
||||||
|
$"({left.WrappedClrType().Name}, {rightType.Name})");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
@@ -145,6 +149,8 @@ namespace OpenRA
|
|||||||
set => throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
|
set => throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LuaValue ToString(LuaRuntime runtime) => ToString();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ using OpenRA.Scripting;
|
|||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public readonly struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
|
public readonly struct CVec : IEquatable<CVec>, IScriptBindable,
|
||||||
|
ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaUnaryMinusBinding,
|
||||||
|
ILuaMultiplicationBinding, ILuaDivisionBinding, ILuaTableBinding, ILuaToStringBinding
|
||||||
{
|
{
|
||||||
public readonly int X, Y;
|
public readonly int X, Y;
|
||||||
|
|
||||||
@@ -61,14 +63,14 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static readonly CVec[] Directions =
|
public static readonly CVec[] Directions =
|
||||||
{
|
{
|
||||||
new CVec(-1, -1),
|
new(-1, -1),
|
||||||
new CVec(-1, 0),
|
new(-1, 0),
|
||||||
new CVec(-1, 1),
|
new(-1, 1),
|
||||||
new CVec(0, -1),
|
new(0, -1),
|
||||||
new CVec(0, 1),
|
new(0, 1),
|
||||||
new CVec(1, -1),
|
new(1, -1),
|
||||||
new CVec(1, 0),
|
new(1, 0),
|
||||||
new CVec(1, 1),
|
new(1, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
#region Scripting interface
|
#region Scripting interface
|
||||||
@@ -76,7 +78,8 @@ namespace OpenRA
|
|||||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
{
|
{
|
||||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec 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 ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments " +
|
||||||
|
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||||
|
|
||||||
return new LuaCustomClrObject(a + b);
|
return new LuaCustomClrObject(a + b);
|
||||||
}
|
}
|
||||||
@@ -84,16 +87,12 @@ namespace OpenRA
|
|||||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
{
|
{
|
||||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec 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 ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments " +
|
||||||
|
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||||
|
|
||||||
return new LuaCustomClrObject(a - b);
|
return new LuaCustomClrObject(a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaValue Minus(LuaRuntime runtime)
|
|
||||||
{
|
|
||||||
return new LuaCustomClrObject(-this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
{
|
{
|
||||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||||
@@ -102,6 +101,29 @@ namespace OpenRA
|
|||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LuaValue Minus(LuaRuntime runtime)
|
||||||
|
{
|
||||||
|
return new LuaCustomClrObject(-this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LuaValue Multiply(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
|
{
|
||||||
|
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out int b))
|
||||||
|
throw new LuaException("Attempted to call CVec.Multiply(CVec, integer) with invalid arguments " +
|
||||||
|
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||||
|
|
||||||
|
return new LuaCustomClrObject(a * b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LuaValue Divide(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
|
{
|
||||||
|
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out int b))
|
||||||
|
throw new LuaException("Attempted to call CVec.Multiply(CVec, integer) with invalid arguments " +
|
||||||
|
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||||
|
|
||||||
|
return new LuaCustomClrObject(a / b);
|
||||||
|
}
|
||||||
|
|
||||||
public LuaValue this[LuaRuntime runtime, LuaValue key]
|
public LuaValue this[LuaRuntime runtime, LuaValue key]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -110,6 +132,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
case "X": return X;
|
case "X": return X;
|
||||||
case "Y": return Y;
|
case "Y": return Y;
|
||||||
|
case "Length": return Length;
|
||||||
default: throw new LuaException($"CVec does not define a member '{key}'");
|
default: throw new LuaException($"CVec does not define a member '{key}'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,6 +140,8 @@ namespace OpenRA
|
|||||||
set => throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
set => throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LuaValue ToString(LuaRuntime runtime) => ToString();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ namespace OpenRA
|
|||||||
// Fixed byte pattern for the OID header
|
// Fixed byte pattern for the OID header
|
||||||
static readonly byte[] OIDHeader = { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 };
|
static readonly byte[] OIDHeader = { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 };
|
||||||
|
|
||||||
|
static readonly char[] HexUpperAlphabet = "0123456789ABCDEF".ToArray();
|
||||||
|
static readonly char[] HexLowerAlphabet = "0123456789abcdef".ToArray();
|
||||||
|
|
||||||
public static string PublicKeyFingerprint(RSAParameters parameters)
|
public static string PublicKeyFingerprint(RSAParameters parameters)
|
||||||
{
|
{
|
||||||
// Public key fingerprint is defined as the SHA1 of the modulus + exponent bytes
|
// Public key fingerprint is defined as the SHA1 of the modulus + exponent bytes
|
||||||
@@ -53,33 +56,33 @@ namespace OpenRA
|
|||||||
using (var s = new MemoryStream(data))
|
using (var s = new MemoryStream(data))
|
||||||
{
|
{
|
||||||
// SEQUENCE
|
// SEQUENCE
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
ReadTLVLength(s);
|
ReadTLVLength(s);
|
||||||
|
|
||||||
// SEQUENCE -> fixed header junk
|
// SEQUENCE -> fixed header junk
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
var headerLength = ReadTLVLength(s);
|
var headerLength = ReadTLVLength(s);
|
||||||
s.Position += headerLength;
|
s.Position += headerLength;
|
||||||
|
|
||||||
// SEQUENCE -> BIT_STRING
|
// SEQUENCE -> BIT_STRING
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
ReadTLVLength(s);
|
ReadTLVLength(s);
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
|
|
||||||
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
ReadTLVLength(s);
|
ReadTLVLength(s);
|
||||||
|
|
||||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
|
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
var modulusLength = ReadTLVLength(s);
|
var modulusLength = ReadTLVLength(s);
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
var modulus = s.ReadBytes(modulusLength - 1);
|
var modulus = s.ReadBytes(modulusLength - 1);
|
||||||
|
|
||||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
|
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
var exponentLength = ReadTLVLength(s);
|
var exponentLength = ReadTLVLength(s);
|
||||||
s.ReadByte();
|
s.ReadUInt8();
|
||||||
var exponent = s.ReadBytes(exponentLength - 1);
|
var exponent = s.ReadBytes(exponentLength - 1);
|
||||||
|
|
||||||
return new RSAParameters
|
return new RSAParameters
|
||||||
@@ -158,13 +161,13 @@ namespace OpenRA
|
|||||||
|
|
||||||
static int ReadTLVLength(Stream s)
|
static int ReadTLVLength(Stream s)
|
||||||
{
|
{
|
||||||
var length = s.ReadByte();
|
var length = s.ReadUInt8();
|
||||||
if (length < 0x80)
|
if (length < 0x80)
|
||||||
return length;
|
return length;
|
||||||
|
|
||||||
var data = new byte[4];
|
Span<byte> data = stackalloc byte[4];
|
||||||
s.ReadBytes(data, 0, Math.Min(length & 0x7F, 4));
|
s.ReadBytes(data[..Math.Min(length & 0x7F, 4)]);
|
||||||
return BitConverter.ToInt32(data.ToArray(), 0);
|
return BitConverter.ToInt32(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int TripletFullLength(int dataLength)
|
static int TripletFullLength(int dataLength)
|
||||||
@@ -249,19 +252,44 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static string SHA1Hash(Stream data)
|
public static string SHA1Hash(Stream data)
|
||||||
{
|
{
|
||||||
using (var csp = SHA1.Create())
|
using var csp = SHA1.Create();
|
||||||
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
return ToHex(csp.ComputeHash(data), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SHA1Hash(byte[] data)
|
public static string SHA1Hash(byte[] data)
|
||||||
{
|
{
|
||||||
using (var csp = SHA1.Create())
|
using var csp = SHA1.Create();
|
||||||
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
return ToHex(csp.ComputeHash(data), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SHA1Hash(string data)
|
public static string SHA1Hash(string data)
|
||||||
{
|
{
|
||||||
return SHA1Hash(Encoding.UTF8.GetBytes(data));
|
return SHA1Hash(Encoding.UTF8.GetBytes(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ToHex(ReadOnlySpan<byte> source, bool lowerCase = false)
|
||||||
|
{
|
||||||
|
if (source.Length == 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// excessively avoid stack overflow if source is too large (considering that we're allocating a new string)
|
||||||
|
var buffer = source.Length <= 256 ? stackalloc char[source.Length * 2] : new char[source.Length * 2];
|
||||||
|
return ToHexInternal(source, buffer, lowerCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
static string ToHexInternal(ReadOnlySpan<byte> source, Span<char> buffer, bool lowerCase)
|
||||||
|
{
|
||||||
|
var sourceIndex = 0;
|
||||||
|
var alphabet = lowerCase ? HexLowerAlphabet : HexUpperAlphabet;
|
||||||
|
|
||||||
|
for (var i = 0; i < buffer.Length; i += 2)
|
||||||
|
{
|
||||||
|
var b = source[sourceIndex++];
|
||||||
|
buffer[i] = alphabet[b >> 4];
|
||||||
|
buffer[i + 1] = alphabet[b & 0xF];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public readonly string Id;
|
public readonly string Id;
|
||||||
public readonly string Version;
|
public readonly string Version;
|
||||||
public readonly string Title;
|
|
||||||
public readonly string LaunchPath;
|
public readonly string LaunchPath;
|
||||||
public readonly string[] LaunchArgs;
|
public readonly string[] LaunchArgs;
|
||||||
public Sprite Icon { get; internal set; }
|
public Sprite Icon { get; internal set; }
|
||||||
@@ -66,6 +65,7 @@ namespace OpenRA
|
|||||||
// Several types of support directory types are available, depending on
|
// Several types of support directory types are available, depending on
|
||||||
// how the player has installed and launched the game.
|
// how the player has installed and launched the game.
|
||||||
// Read registration metadata from all of them
|
// Read registration metadata from all of them
|
||||||
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
|
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
|
||||||
{
|
{
|
||||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||||
@@ -76,7 +76,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
var yaml = MiniYaml.FromFile(path, stringPool: stringPool).First().Value;
|
||||||
LoadMod(yaml, path);
|
LoadMod(yaml, path);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -94,17 +94,17 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (sheetBuilder != null)
|
if (sheetBuilder != null)
|
||||||
{
|
{
|
||||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
var iconNode = yaml.NodeWithKeyOrDefault("Icon");
|
||||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||||
|
|
||||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
var icon2xNode = yaml.NodeWithKeyOrDefault("Icon2x");
|
||||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||||
|
|
||||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
var icon3xNode = yaml.NodeWithKeyOrDefault("Icon3x");
|
||||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||||
@@ -122,26 +122,29 @@ namespace OpenRA
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var key = ExternalMod.MakeKey(mod);
|
var key = ExternalMod.MakeKey(mod);
|
||||||
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new List<MiniYamlNode>()
|
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new[]
|
||||||
{
|
{
|
||||||
new MiniYamlNode("Id", mod.Id),
|
new MiniYamlNode("Id", mod.Id),
|
||||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||||
new MiniYamlNode("Title", mod.Metadata.Title),
|
|
||||||
new MiniYamlNode("LaunchPath", launchPath),
|
new MiniYamlNode("LaunchPath", launchPath),
|
||||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
var iconNodes = new List<MiniYamlNode>();
|
||||||
|
|
||||||
using (var stream = mod.Package.GetStream("icon.png"))
|
using (var stream = mod.Package.GetStream("icon.png"))
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
|
iconNodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||||
|
|
||||||
using (var stream = mod.Package.GetStream("icon-2x.png"))
|
using (var stream = mod.Package.GetStream("icon-2x.png"))
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
|
iconNodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||||
|
|
||||||
using (var stream = mod.Package.GetStream("icon-3x.png"))
|
using (var stream = mod.Package.GetStream("icon-3x.png"))
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
iconNodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||||
|
|
||||||
|
yaml = yaml.WithValue(yaml.Value.WithNodesAppended(iconNodes));
|
||||||
|
|
||||||
var sources = new HashSet<string>();
|
var sources = new HashSet<string>();
|
||||||
if (registration.HasFlag(ModRegistration.System))
|
if (registration.HasFlag(ModRegistration.System))
|
||||||
@@ -201,7 +204,7 @@ namespace OpenRA
|
|||||||
string modKey = null;
|
string modKey = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
var yaml = MiniYaml.FromFile(path).First().Value;
|
||||||
var m = FieldLoader.Load<ExternalMod>(yaml);
|
var m = FieldLoader.Load<ExternalMod>(yaml);
|
||||||
modKey = ExternalMod.MakeKey(m);
|
modKey = ExternalMod.MakeKey(m);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
@@ -22,15 +23,24 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static class Exts
|
public static class Exts
|
||||||
{
|
{
|
||||||
public static bool IsUppercase(this string str)
|
/// <summary>Returns <see cref="Color"/> of the <paramref name="actor"/>, taking <see cref="Actor.EffectiveOwner"/> into account.</summary>
|
||||||
|
public static Color OwnerColor(this Actor actor)
|
||||||
{
|
{
|
||||||
return string.Compare(str.ToUpperInvariant(), str, false) == 0;
|
var effectiveOwner = actor.EffectiveOwner;
|
||||||
|
if (effectiveOwner != null && effectiveOwner.Disguised && actor.World.RenderPlayer != null)
|
||||||
|
return effectiveOwner.Owner.Color;
|
||||||
|
|
||||||
|
return actor.Owner.Color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T WithDefault<T>(T def, Func<T> f)
|
public static string FormatInvariant(this string format, params object[] args)
|
||||||
{
|
{
|
||||||
try { return f(); }
|
return string.Format(CultureInfo.InvariantCulture, format, args);
|
||||||
catch { return def; }
|
}
|
||||||
|
|
||||||
|
public static string FormatCurrent(this string format, params object[] args)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
|
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
|
||||||
@@ -131,11 +141,35 @@ namespace OpenRA
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static T GetOrAdd<T>(this HashSet<T> set, T value)
|
||||||
|
{
|
||||||
|
if (!set.TryGetValue(value, out var ret))
|
||||||
|
set.Add(ret = value);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T GetOrAdd<T>(this HashSet<T> set, T value, Func<T, T> createFn)
|
||||||
|
{
|
||||||
|
if (!set.TryGetValue(value, out var ret))
|
||||||
|
set.Add(ret = createFn(value));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public static int IndexOf<T>(this T[] array, T value)
|
public static int IndexOf<T>(this T[] array, T value)
|
||||||
{
|
{
|
||||||
return Array.IndexOf(array, value);
|
return Array.IndexOf(array, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static T FirstOrDefault<T>(this T[] array, Predicate<T> match)
|
||||||
|
{
|
||||||
|
return Array.Find(array, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T FirstOrDefault<T>(this List<T> list, Predicate<T> match)
|
||||||
|
{
|
||||||
|
return list.Find(match);
|
||||||
|
}
|
||||||
|
|
||||||
public static T Random<T>(this IEnumerable<T> ts, MersenneTwister r)
|
public static T Random<T>(this IEnumerable<T> ts, MersenneTwister r)
|
||||||
{
|
{
|
||||||
return Random(ts, r, true);
|
return Random(ts, r, true);
|
||||||
@@ -148,7 +182,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
static T Random<T>(IEnumerable<T> ts, MersenneTwister r, bool throws)
|
static T Random<T>(IEnumerable<T> ts, MersenneTwister r, bool throws)
|
||||||
{
|
{
|
||||||
var xs = ts as ICollection<T>;
|
var xs = ts as IReadOnlyCollection<T>;
|
||||||
xs ??= ts.ToList();
|
xs ??= ts.ToList();
|
||||||
if (xs.Count == 0)
|
if (xs.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -303,9 +337,9 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Adjust for other rounding modes
|
// Adjust for other rounding modes
|
||||||
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
||||||
root += 1;
|
root++;
|
||||||
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
||||||
root += 1;
|
root++;
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -344,9 +378,9 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Adjust for other rounding modes
|
// Adjust for other rounding modes
|
||||||
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
||||||
root += 1;
|
root++;
|
||||||
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
||||||
root += 1;
|
root++;
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -356,6 +390,11 @@ namespace OpenRA
|
|||||||
return number * 46341 / 32768;
|
return number * 46341 / 32768;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int MultiplyBySqrtTwoOverTwo(int number)
|
||||||
|
{
|
||||||
|
return (int)(number * 23170L / 32768L);
|
||||||
|
}
|
||||||
|
|
||||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||||
{
|
{
|
||||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||||
@@ -394,15 +433,26 @@ namespace OpenRA
|
|||||||
public static Dictionary<TKey, TElement> ToDictionaryWithConflictLog<TSource, TKey, TElement>(
|
public static Dictionary<TKey, TElement> ToDictionaryWithConflictLog<TSource, TKey, TElement>(
|
||||||
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
|
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
|
||||||
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
||||||
|
{
|
||||||
|
var output = new Dictionary<TKey, TElement>();
|
||||||
|
IntoDictionaryWithConflictLog(source, keySelector, elementSelector, debugName, output, logKey, logValue);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void IntoDictionaryWithConflictLog<TSource, TKey, TElement>(
|
||||||
|
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
|
||||||
|
string debugName, Dictionary<TKey, TElement> output,
|
||||||
|
Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
||||||
{
|
{
|
||||||
// Fall back on ToString() if null functions are provided:
|
// Fall back on ToString() if null functions are provided:
|
||||||
logKey ??= s => s.ToString();
|
logKey ??= s => s.ToString();
|
||||||
logValue ??= s => s.ToString();
|
logValue ??= s => s.ToString();
|
||||||
|
|
||||||
// Try to build a dictionary and log all duplicates found (if any):
|
// Try to build a dictionary and log all duplicates found (if any):
|
||||||
var dupKeys = new Dictionary<TKey, List<string>>();
|
Dictionary<TKey, List<string>> dupKeys = null;
|
||||||
var capacity = source is ICollection<TSource> collection ? collection.Count : 0;
|
var capacity = source is ICollection<TSource> collection ? collection.Count : 0;
|
||||||
var d = new Dictionary<TKey, TElement>(capacity);
|
output.Clear();
|
||||||
|
output.EnsureCapacity(capacity);
|
||||||
foreach (var item in source)
|
foreach (var item in source)
|
||||||
{
|
{
|
||||||
var key = keySelector(item);
|
var key = keySelector(item);
|
||||||
@@ -413,14 +463,15 @@ namespace OpenRA
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check for a key conflict:
|
// Check for a key conflict:
|
||||||
if (!d.TryAdd(key, element))
|
if (!output.TryAdd(key, element))
|
||||||
{
|
{
|
||||||
|
dupKeys ??= new Dictionary<TKey, List<string>>();
|
||||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||||
{
|
{
|
||||||
// Log the initial conflicting value already inserted:
|
// Log the initial conflicting value already inserted:
|
||||||
dupKeyMessages = new List<string>
|
dupKeyMessages = new List<string>
|
||||||
{
|
{
|
||||||
logValue(d[key])
|
logValue(output[key])
|
||||||
};
|
};
|
||||||
dupKeys.Add(key, dupKeyMessages);
|
dupKeys.Add(key, dupKeyMessages);
|
||||||
}
|
}
|
||||||
@@ -431,15 +482,14 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If any duplicates were found, throw a descriptive error
|
// If any duplicates were found, throw a descriptive error
|
||||||
if (dupKeys.Count > 0)
|
if (dupKeys != null)
|
||||||
{
|
{
|
||||||
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => $"{logKey(p.Key)}: [{string.Join(",", p.Value)}]"));
|
var badKeysFormatted = new StringBuilder(
|
||||||
var msg = $"{debugName}, duplicate values found for the following keys: {badKeysFormatted}";
|
$"{debugName}, duplicate values found for the following keys: ");
|
||||||
throw new ArgumentException(msg);
|
foreach (var p in dupKeys)
|
||||||
|
badKeysFormatted.Append($"{logKey(p.Key)}: [{string.Join(",", p.Value)}]");
|
||||||
|
throw new ArgumentException(badKeysFormatted.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the dictionary we built:
|
|
||||||
return d;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color ColorLerp(float t, Color c1, Color c2)
|
public static Color ColorLerp(float t, Color c1, Color c2)
|
||||||
@@ -493,17 +543,22 @@ namespace OpenRA
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int ParseIntegerInvariant(string s)
|
public static byte ParseByteInvariant(string s)
|
||||||
{
|
|
||||||
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte ParseByte(string s)
|
|
||||||
{
|
{
|
||||||
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParseIntegerInvariant(string s, out int i)
|
public static short ParseInt16Invariant(string s)
|
||||||
|
{
|
||||||
|
return short.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ParseInt32Invariant(string s)
|
||||||
|
{
|
||||||
|
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParseInt32Invariant(string s, out int i)
|
||||||
{
|
{
|
||||||
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||||
}
|
}
|
||||||
@@ -513,6 +568,26 @@ namespace OpenRA
|
|||||||
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ToStringInvariant(this byte i)
|
||||||
|
{
|
||||||
|
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToStringInvariant(this byte i, string format)
|
||||||
|
{
|
||||||
|
return i.ToString(format, NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToStringInvariant(this int i)
|
||||||
|
{
|
||||||
|
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToStringInvariant(this int i, string format)
|
||||||
|
{
|
||||||
|
return i.ToString(format, NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsTraitEnabled<T>(this T trait)
|
public static bool IsTraitEnabled<T>(this T trait)
|
||||||
{
|
{
|
||||||
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
|
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
|
||||||
@@ -562,11 +637,6 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
return new LineSplitEnumerator(str.AsSpan(), separator);
|
return new LineSplitEnumerator(str.AsSpan(), separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParseInt32Invariant(string s, out int i)
|
|
||||||
{
|
|
||||||
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref struct LineSplitEnumerator
|
public ref struct LineSplitEnumerator
|
||||||
@@ -581,7 +651,7 @@ namespace OpenRA
|
|||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LineSplitEnumerator GetEnumerator() => this;
|
public readonly LineSplitEnumerator GetEnumerator() => this;
|
||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ namespace OpenRA
|
|||||||
{ typeof(WAngle), ParseWAngle },
|
{ typeof(WAngle), ParseWAngle },
|
||||||
{ typeof(WRot), ParseWRot },
|
{ typeof(WRot), ParseWRot },
|
||||||
{ typeof(CPos), ParseCPos },
|
{ typeof(CPos), ParseCPos },
|
||||||
|
{ typeof(CPos[]), ParseCPosArray },
|
||||||
{ typeof(CVec), ParseCVec },
|
{ typeof(CVec), ParseCVec },
|
||||||
{ typeof(CVec[]), ParseCVecArray },
|
{ typeof(CVec[]), ParseCVecArray },
|
||||||
{ typeof(BooleanExpression), ParseBooleanExpression },
|
{ typeof(BooleanExpression), ParseBooleanExpression },
|
||||||
@@ -118,7 +119,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
|
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
if (Exts.TryParseInt32Invariant(value, out var res))
|
||||||
{
|
{
|
||||||
if (res >= 0 && res < BoxedInts.Length)
|
if (res >= 0 && res < BoxedInts.Length)
|
||||||
return BoxedInts[res];
|
return BoxedInts[res];
|
||||||
@@ -195,11 +196,11 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3)
|
if (parts.Length == 3
|
||||||
{
|
&& WDist.TryParse(parts[0], out var rx)
|
||||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
&& WDist.TryParse(parts[1], out var ry)
|
||||||
return new WVec(rx, ry, rz);
|
&& WDist.TryParse(parts[2], out var rz))
|
||||||
}
|
return new WVec(rx, ry, rz);
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -219,8 +220,8 @@ namespace OpenRA
|
|||||||
for (var i = 0; i < vecs.Length; ++i)
|
for (var i = 0; i < vecs.Length; ++i)
|
||||||
{
|
{
|
||||||
if (WDist.TryParse(parts[3 * i], out var rx)
|
if (WDist.TryParse(parts[3 * i], out var rx)
|
||||||
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
||||||
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||||
vecs[i] = new WVec(rx, ry, rz);
|
vecs[i] = new WVec(rx, ry, rz);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,13 +236,11 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3)
|
if (parts.Length == 3
|
||||||
{
|
&& WDist.TryParse(parts[0], out var rx)
|
||||||
if (WDist.TryParse(parts[0], out var rx)
|
&& WDist.TryParse(parts[1], out var ry)
|
||||||
&& WDist.TryParse(parts[1], out var ry)
|
&& WDist.TryParse(parts[2], out var rz))
|
||||||
&& WDist.TryParse(parts[2], out var rz))
|
return new WPos(rx, ry, rz);
|
||||||
return new WPos(rx, ry, rz);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -249,7 +248,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
|
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
if (Exts.TryParseInt32Invariant(value, out var res))
|
||||||
return new WAngle(res);
|
return new WAngle(res);
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
@@ -259,13 +258,11 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3)
|
if (parts.Length == 3
|
||||||
{
|
&& Exts.TryParseInt32Invariant(parts[0], out var rr)
|
||||||
if (Exts.TryParseIntegerInvariant(parts[0], out var rr)
|
&& Exts.TryParseInt32Invariant(parts[1], out var rp)
|
||||||
&& Exts.TryParseIntegerInvariant(parts[1], out var rp)
|
&& Exts.TryParseInt32Invariant(parts[2], out var ry))
|
||||||
&& Exts.TryParseIntegerInvariant(parts[2], out var ry))
|
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -278,10 +275,33 @@ namespace OpenRA
|
|||||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 3)
|
if (parts.Length == 3)
|
||||||
return new CPos(
|
return new CPos(
|
||||||
Exts.ParseIntegerInvariant(parts[0]),
|
Exts.ParseInt32Invariant(parts[0]),
|
||||||
Exts.ParseIntegerInvariant(parts[1]),
|
Exts.ParseInt32Invariant(parts[1]),
|
||||||
Exts.ParseByte(parts[2]));
|
Exts.ParseByteInvariant(parts[2]));
|
||||||
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
return new CPos(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static object ParseCPosArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
var parts = value.Split(SplitComma);
|
||||||
|
|
||||||
|
if (parts.Length % 2 != 0)
|
||||||
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
|
|
||||||
|
var vecs = new CPos[parts.Length / 2];
|
||||||
|
for (var i = 0; i < vecs.Length; i++)
|
||||||
|
{
|
||||||
|
if (int.TryParse(parts[2 * i], out var rx)
|
||||||
|
&& int.TryParse(parts[2 * i + 1], out var ry))
|
||||||
|
vecs[i] = new CPos(rx, ry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -292,7 +312,7 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
return new CVec(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -385,7 +405,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
var ints = new int2[parts.Length / 2];
|
var ints = new int2[parts.Length / 2];
|
||||||
for (var i = 0; i < ints.Length; i++)
|
for (var i = 0; i < ints.Length; i++)
|
||||||
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
|
ints[i] = new int2(Exts.ParseInt32Invariant(parts[2 * i]), Exts.ParseInt32Invariant(parts[2 * i + 1]));
|
||||||
|
|
||||||
return ints;
|
return ints;
|
||||||
}
|
}
|
||||||
@@ -398,7 +418,7 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
return new Size(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -412,7 +432,7 @@ namespace OpenRA
|
|||||||
if (parts.Length != 2)
|
if (parts.Length != 2)
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
|
|
||||||
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
return new int2(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -460,10 +480,10 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
return new Rectangle(
|
return new Rectangle(
|
||||||
Exts.ParseIntegerInvariant(parts[0]),
|
Exts.ParseInt32Invariant(parts[0]),
|
||||||
Exts.ParseIntegerInvariant(parts[1]),
|
Exts.ParseInt32Invariant(parts[1]),
|
||||||
Exts.ParseIntegerInvariant(parts[2]),
|
Exts.ParseInt32Invariant(parts[2]),
|
||||||
Exts.ParseIntegerInvariant(parts[3]));
|
Exts.ParseInt32Invariant(parts[3]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -500,7 +520,7 @@ namespace OpenRA
|
|||||||
if (yaml == null)
|
if (yaml == null)
|
||||||
return Activator.CreateInstance(fieldType);
|
return Activator.CreateInstance(fieldType);
|
||||||
|
|
||||||
var dict = Activator.CreateInstance(fieldType, yaml.Nodes.Count);
|
var dict = Activator.CreateInstance(fieldType, yaml.Nodes.Length);
|
||||||
var arguments = fieldType.GetGenericArguments();
|
var arguments = fieldType.GetGenericArguments();
|
||||||
var addMethod = fieldType.GetMethod(nameof(Dictionary<object, object>.Add), arguments);
|
var addMethod = fieldType.GetMethod(nameof(Dictionary<object, object>.Add), arguments);
|
||||||
var addArgs = new object[2];
|
var addArgs = new object[2];
|
||||||
@@ -531,7 +551,7 @@ namespace OpenRA
|
|||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var innerType = fieldType.GetGenericArguments().First();
|
var innerType = fieldType.GetGenericArguments()[0];
|
||||||
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||||
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using System.ComponentModel;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
@@ -58,7 +59,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
return new MiniYaml(
|
return new MiniYaml(
|
||||||
null,
|
null,
|
||||||
fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))).ToList());
|
fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MiniYamlNode SaveField(object o, string field)
|
public static MiniYamlNode SaveField(object o, string field)
|
||||||
@@ -84,7 +85,7 @@ namespace OpenRA
|
|||||||
// This is only for documentation generation
|
// This is only for documentation generation
|
||||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||||
{
|
{
|
||||||
var result = "";
|
var result = new StringBuilder();
|
||||||
var dict = (System.Collections.IDictionary)v;
|
var dict = (System.Collections.IDictionary)v;
|
||||||
foreach (var kvp in dict)
|
foreach (var kvp in dict)
|
||||||
{
|
{
|
||||||
@@ -94,10 +95,10 @@ namespace OpenRA
|
|||||||
var formattedKey = FormatValue(key);
|
var formattedKey = FormatValue(key);
|
||||||
var formattedValue = FormatValue(value);
|
var formattedValue = FormatValue(value);
|
||||||
|
|
||||||
result += $"{formattedKey}: {formattedValue}{Environment.NewLine}";
|
result.Append($"{formattedKey}: {formattedValue}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v is DateTime d)
|
if (v is DateTime d)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using ICSharpCode.SharpZipLib.Checksum;
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
@@ -45,12 +46,13 @@ namespace OpenRA.FileFormats
|
|||||||
var data = new List<byte>();
|
var data = new List<byte>();
|
||||||
Type = SpriteFrameType.Rgba32;
|
Type = SpriteFrameType.Rgba32;
|
||||||
|
|
||||||
|
byte bitDepth = 8;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
||||||
var type = Encoding.UTF8.GetString(s.ReadBytes(4));
|
var type = s.ReadASCII(4);
|
||||||
var content = s.ReadBytes(length);
|
var content = s.ReadBytes(length);
|
||||||
/*var crc = */s.ReadInt32();
|
s.ReadInt32(); // crc
|
||||||
|
|
||||||
if (!headerParsed && type != "IHDR")
|
if (!headerParsed && type != "IHDR")
|
||||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||||
@@ -66,7 +68,7 @@ namespace OpenRA.FileFormats
|
|||||||
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||||
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||||
|
|
||||||
var bitDepth = ms.ReadUInt8();
|
bitDepth = ms.ReadUInt8();
|
||||||
var colorType = (PngColorType)ms.ReadUInt8();
|
var colorType = (PngColorType)ms.ReadUInt8();
|
||||||
if (IsPaletted(bitDepth, colorType))
|
if (IsPaletted(bitDepth, colorType))
|
||||||
Type = SpriteFrameType.Indexed8;
|
Type = SpriteFrameType.Indexed8;
|
||||||
@@ -76,7 +78,7 @@ namespace OpenRA.FileFormats
|
|||||||
Data = new byte[Width * Height * PixelStride];
|
Data = new byte[Width * Height * PixelStride];
|
||||||
|
|
||||||
var compression = ms.ReadUInt8();
|
var compression = ms.ReadUInt8();
|
||||||
/*var filter = */ms.ReadUInt8();
|
ms.ReadUInt8(); // filter
|
||||||
var interlace = ms.ReadUInt8();
|
var interlace = ms.ReadUInt8();
|
||||||
|
|
||||||
if (compression != 0)
|
if (compression != 0)
|
||||||
@@ -92,8 +94,8 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
case "PLTE":
|
case "PLTE":
|
||||||
{
|
{
|
||||||
Palette = new Color[256];
|
Palette = new Color[length / 3];
|
||||||
for (var i = 0; i < length / 3; i++)
|
for (var i = 0; i < Palette.Length; i++)
|
||||||
{
|
{
|
||||||
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
|
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
|
||||||
Palette[i] = Color.FromArgb(r, g, b);
|
Palette[i] = Color.FromArgb(r, g, b);
|
||||||
@@ -136,14 +138,34 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
var pxStride = PixelStride;
|
var pxStride = PixelStride;
|
||||||
var rowStride = Width * pxStride;
|
var rowStride = Width * pxStride;
|
||||||
|
var pixelsPerByte = 8 / bitDepth;
|
||||||
|
var sourceRowStride = Exts.IntegerDivisionRoundingAwayFromZero(rowStride, pixelsPerByte);
|
||||||
|
|
||||||
Span<byte> prevLine = new byte[rowStride];
|
Span<byte> prevLine = new byte[rowStride];
|
||||||
for (var y = 0; y < Height; y++)
|
for (var y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
var filter = (PngFilter)ds.ReadUInt8();
|
var filter = (PngFilter)ds.ReadUInt8();
|
||||||
ds.ReadBytes(Data, y * rowStride, rowStride);
|
ds.ReadBytes(Data, y * rowStride, sourceRowStride);
|
||||||
var line = Data.AsSpan(y * rowStride, rowStride);
|
var line = Data.AsSpan(y * rowStride, rowStride);
|
||||||
|
|
||||||
|
// If the source has a bit depth of 1, 2 or 4 it packs multiple pixels per byte.
|
||||||
|
// Unpack to bit depth of 8, yielding 1 pixel per byte.
|
||||||
|
// This makes life easier for consumers of palleted data.
|
||||||
|
if (bitDepth < 8)
|
||||||
|
{
|
||||||
|
var mask = 0xFF >> (8 - bitDepth);
|
||||||
|
for (var i = sourceRowStride - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var packed = line[i];
|
||||||
|
for (var j = 0; j < pixelsPerByte; j++)
|
||||||
|
{
|
||||||
|
var dest = i * pixelsPerByte + j;
|
||||||
|
if (dest < line.Length) // Guard against last byte being only partially packed
|
||||||
|
line[dest] = (byte)(packed >> (8 - (j + 1) * bitDepth) & mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (filter)
|
switch (filter)
|
||||||
{
|
{
|
||||||
case PngFilter.None:
|
case PngFilter.None:
|
||||||
@@ -269,7 +291,7 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
static bool IsPaletted(byte bitDepth, PngColorType colorType)
|
static bool IsPaletted(byte bitDepth, PngColorType colorType)
|
||||||
{
|
{
|
||||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
if (bitDepth <= 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))
|
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))
|
||||||
@@ -287,10 +309,10 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
var typeBytes = Encoding.ASCII.GetBytes(type);
|
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||||
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
||||||
output.WriteArray(typeBytes);
|
output.Write(typeBytes);
|
||||||
|
|
||||||
var data = input.ReadAllBytes();
|
var data = input.ReadAllBytes();
|
||||||
output.WriteArray(data);
|
output.Write(data);
|
||||||
|
|
||||||
var crc32 = new Crc32();
|
var crc32 = new Crc32();
|
||||||
crc32.Update(typeBytes);
|
crc32.Update(typeBytes);
|
||||||
@@ -302,7 +324,7 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
using (var output = new MemoryStream())
|
using (var output = new MemoryStream())
|
||||||
{
|
{
|
||||||
output.WriteArray(Signature);
|
output.Write(Signature);
|
||||||
using (var header = new MemoryStream())
|
using (var header = new MemoryStream())
|
||||||
{
|
{
|
||||||
header.Write(IPAddress.HostToNetworkOrder(Width));
|
header.Write(IPAddress.HostToNetworkOrder(Width));
|
||||||
@@ -350,13 +372,14 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
using (var data = new MemoryStream())
|
using (var data = new MemoryStream())
|
||||||
{
|
{
|
||||||
using (var compressed = new DeflaterOutputStream(data))
|
using (var compressed = new DeflaterOutputStream(data, new Deflater(Deflater.BEST_COMPRESSION)))
|
||||||
{
|
{
|
||||||
var rowStride = Width * PixelStride;
|
var rowStride = Width * PixelStride;
|
||||||
for (var y = 0; y < Height; y++)
|
for (var y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
// Write uncompressed scanlines for simplicity
|
// Assuming no filtering for simplicity
|
||||||
compressed.WriteByte(0);
|
const byte FilterType = 0;
|
||||||
|
compressed.WriteByte(FilterType);
|
||||||
compressed.Write(Data, y * rowStride, rowStride);
|
compressed.Write(Data, y * rowStride, rowStride);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +394,7 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
using (var text = new MemoryStream())
|
using (var text = new MemoryStream())
|
||||||
{
|
{
|
||||||
text.WriteArray(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
text.Write(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
||||||
WritePngChunk(output, "tEXt", text);
|
WritePngChunk(output, "tEXt", text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ namespace OpenRA.FileFormats
|
|||||||
throw new NotSupportedException($"Metadata version {version} is not supported");
|
throw new NotSupportedException($"Metadata version {version} is not supported");
|
||||||
|
|
||||||
// Read game info (max 100K limit as a safeguard against corrupted files)
|
// Read game info (max 100K limit as a safeguard against corrupted files)
|
||||||
var data = fs.ReadString(Encoding.UTF8, 1024 * 100);
|
var data = fs.ReadLengthPrefixedString(Encoding.UTF8, 1024 * 100);
|
||||||
GameInfo = GameInformation.Deserialize(data);
|
GameInfo = GameInformation.Deserialize(data, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(BinaryWriter writer)
|
public void Write(BinaryWriter writer)
|
||||||
@@ -62,7 +62,7 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
// Write lobby info data
|
// Write lobby info data
|
||||||
writer.Flush();
|
writer.Flush();
|
||||||
dataLength += writer.BaseStream.WriteString(Encoding.UTF8, GameInfo.Serialize());
|
dataLength += writer.BaseStream.WriteLengthPrefixedString(Encoding.UTF8, GameInfo.Serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write total length & end marker
|
// Write total length & end marker
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace OpenRA.FileSystem
|
|||||||
bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename);
|
bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename);
|
||||||
bool TryOpen(string filename, out Stream s);
|
bool TryOpen(string filename, out Stream s);
|
||||||
bool Exists(string filename);
|
bool Exists(string filename);
|
||||||
bool IsExternalModFile(string filename);
|
bool IsExternalFile(string filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileSystem : IReadOnlyFileSystem
|
public class FileSystem : IReadOnlyFileSystem
|
||||||
@@ -83,14 +83,14 @@ namespace OpenRA.FileSystem
|
|||||||
|
|
||||||
public void Mount(string name, string explicitName = null)
|
public void Mount(string name, string explicitName = null)
|
||||||
{
|
{
|
||||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
var optional = name.StartsWith('~');
|
||||||
if (optional)
|
if (optional)
|
||||||
name = name[1..];
|
name = name[1..];
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IReadOnlyPackage package;
|
IReadOnlyPackage package;
|
||||||
if (name.StartsWith("$", StringComparison.Ordinal))
|
if (name.StartsWith('$'))
|
||||||
{
|
{
|
||||||
name = name[1..];
|
name = name[1..];
|
||||||
|
|
||||||
@@ -109,10 +109,8 @@ namespace OpenRA.FileSystem
|
|||||||
|
|
||||||
Mount(package, explicitName);
|
Mount(package, explicitName);
|
||||||
}
|
}
|
||||||
catch
|
catch when (optional)
|
||||||
{
|
{
|
||||||
if (!optional)
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,9 +159,7 @@ namespace OpenRA.FileSystem
|
|||||||
explicitMounts.Remove(key);
|
explicitMounts.Remove(key);
|
||||||
|
|
||||||
// Mod packages aren't owned by us, so we shouldn't dispose them
|
// Mod packages aren't owned by us, so we shouldn't dispose them
|
||||||
if (modPackages.Contains(package))
|
if (!modPackages.Remove(package))
|
||||||
modPackages.Remove(package);
|
|
||||||
else
|
|
||||||
package.Dispose();
|
package.Dispose();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -185,11 +181,13 @@ namespace OpenRA.FileSystem
|
|||||||
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadFromManifest(Manifest manifest)
|
public void TrimExcess()
|
||||||
{
|
{
|
||||||
UnmountAll();
|
mountedPackages.TrimExcess();
|
||||||
foreach (var kv in manifest.Packages)
|
explicitMounts.TrimExcess();
|
||||||
Mount(kv.Key, kv.Value);
|
modPackages.TrimExcess();
|
||||||
|
foreach (var packages in fileIndex.Values)
|
||||||
|
packages.TrimExcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream GetFromCache(string filename)
|
Stream GetFromCache(string filename)
|
||||||
@@ -226,14 +224,11 @@ namespace OpenRA.FileSystem
|
|||||||
public bool TryOpen(string filename, out Stream s)
|
public bool TryOpen(string filename, out Stream s)
|
||||||
{
|
{
|
||||||
var explicitSplit = filename.IndexOf('|');
|
var explicitSplit = filename.IndexOf('|');
|
||||||
if (explicitSplit > 0)
|
if (explicitSplit > 0 && explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||||
{
|
{
|
||||||
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
|
||||||
{
|
if (s != null)
|
||||||
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
|
return true;
|
||||||
if (s != null)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s = GetFromCache(filename);
|
s = GetFromCache(filename);
|
||||||
@@ -262,64 +257,20 @@ namespace OpenRA.FileSystem
|
|||||||
public bool Exists(string filename)
|
public bool Exists(string filename)
|
||||||
{
|
{
|
||||||
var explicitSplit = filename.IndexOf('|');
|
var explicitSplit = filename.IndexOf('|');
|
||||||
if (explicitSplit > 0)
|
if (explicitSplit > 0 &&
|
||||||
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage) &&
|
||||||
if (explicitPackage.Contains(filename[(explicitSplit + 1)..]))
|
explicitPackage.Contains(filename[(explicitSplit + 1)..]))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return fileIndex.ContainsKey(filename);
|
return fileIndex.ContainsKey(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the given filename references an external mod via an explicit mount.
|
/// Returns true if the given filename references any file outside the mod mount.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsExternalModFile(string filename)
|
public bool IsExternalFile(string filename)
|
||||||
{
|
{
|
||||||
var explicitSplit = filename.IndexOf('|');
|
return !filename.StartsWith($"{modID}|", StringComparison.Ordinal);
|
||||||
if (explicitSplit < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (installedMods[modID].Package == explicitPackage)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return modPackages.Contains(explicitPackage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolves a filesystem for an assembly, accounting for explicit and mod mounts.
|
|
||||||
/// Assemblies must exist in the native OS file system (not inside an OpenRA-defined package).
|
|
||||||
/// </summary>
|
|
||||||
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
|
||||||
{
|
|
||||||
var explicitSplit = path.IndexOf('|');
|
|
||||||
if (explicitSplit > 0 && !path.StartsWith("^"))
|
|
||||||
{
|
|
||||||
var parent = path[..explicitSplit];
|
|
||||||
var filename = path[(explicitSplit + 1)..];
|
|
||||||
|
|
||||||
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
|
|
||||||
if (parentPath == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
if (!installedMods.TryGetValue(parentPath[1..], out var mod))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (mod.Package is not Folder)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
path = Path.Combine(mod.Package.Name, filename);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
path = Path.Combine(parentPath, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolvedPath = Platform.ResolvePath(path);
|
|
||||||
return File.Exists(resolvedPath) ? resolvedPath : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ResolveCaseInsensitivePath(string path)
|
public static string ResolveCaseInsensitivePath(string path)
|
||||||
@@ -335,7 +286,8 @@ namespace OpenRA.FileSystem
|
|||||||
if (name == ".")
|
if (name == ".")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
resolved = Directory.GetFileSystemEntries(resolved).FirstOrDefault(e => e.Equals(Path.Combine(resolved, name), StringComparison.InvariantCultureIgnoreCase));
|
resolved = Directory.GetFileSystemEntries(resolved)
|
||||||
|
.FirstOrDefault(e => e.Equals(Path.Combine(resolved, name), StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
if (resolved == null)
|
if (resolved == null)
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ namespace OpenRA.FileSystem
|
|||||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||||
// full parent path too. We need to be careful to not add a second path
|
// full parent path too. We need to be careful to not add a second path
|
||||||
// prefix to these hacked packages.
|
// prefix to these hacked packages.
|
||||||
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
var filePath = filename.StartsWith(Name, StringComparison.Ordinal) ? filename : Path.Combine(Name, filename);
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
||||||
using (var s = File.Create(filePath))
|
using (var s = File.Create(filePath))
|
||||||
@@ -98,7 +98,7 @@ namespace OpenRA.FileSystem
|
|||||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||||
// full parent path too. We need to be careful to not add a second path
|
// full parent path too. We need to be careful to not add a second path
|
||||||
// prefix to these hacked packages.
|
// prefix to these hacked packages.
|
||||||
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
var filePath = filename.StartsWith(Name, StringComparison.Ordinal) ? filename : Path.Combine(Name, filename);
|
||||||
if (Directory.Exists(filePath))
|
if (Directory.Exists(filePath))
|
||||||
Directory.Delete(filePath, true);
|
Directory.Delete(filePath, true);
|
||||||
else if (File.Exists(filePath))
|
else if (File.Exists(filePath))
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace OpenRA.FileSystem
|
|||||||
{
|
{
|
||||||
const uint ZipSignature = 0x04034b50;
|
const uint ZipSignature = 0x04034b50;
|
||||||
|
|
||||||
class ReadOnlyZipFile : IReadOnlyPackage
|
public class ReadOnlyZipFile : IReadOnlyPackage
|
||||||
{
|
{
|
||||||
public string Name { get; protected set; }
|
public string Name { get; protected set; }
|
||||||
protected ZipFile pkg;
|
protected ZipFile pkg;
|
||||||
@@ -68,6 +68,7 @@ namespace OpenRA.FileSystem
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
pkg?.Close();
|
pkg?.Close();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||||
@@ -93,7 +94,7 @@ namespace OpenRA.FileSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
public sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
||||||
{
|
{
|
||||||
readonly MemoryStream pkgStream = new();
|
readonly MemoryStream pkgStream = new();
|
||||||
|
|
||||||
@@ -113,14 +114,15 @@ namespace OpenRA.FileSystem
|
|||||||
pkgStream.Position = 0;
|
pkgStream.Position = 0;
|
||||||
pkg = new ZipFile(pkgStream);
|
pkg = new ZipFile(pkgStream);
|
||||||
Name = filename;
|
Name = filename;
|
||||||
|
|
||||||
|
// Remove subfields that can break ZIP updating.
|
||||||
|
foreach (ZipEntry entry in pkg)
|
||||||
|
entry.ExtraData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Commit()
|
void Commit()
|
||||||
{
|
{
|
||||||
var pos = pkgStream.Position;
|
File.WriteAllBytes(Name, pkgStream.ToArray());
|
||||||
pkgStream.Position = 0;
|
|
||||||
File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length));
|
|
||||||
pkgStream.Position = pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(string filename, byte[] contents)
|
public void Update(string filename, byte[] contents)
|
||||||
@@ -147,7 +149,7 @@ namespace OpenRA.FileSystem
|
|||||||
|
|
||||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||||
{
|
{
|
||||||
if (path.EndsWith("/", StringComparison.Ordinal))
|
if (path.EndsWith('/'))
|
||||||
path = path[..^1];
|
path = path[..^1];
|
||||||
|
|
||||||
Name = path;
|
Name = path;
|
||||||
|
|||||||
167
OpenRA.Game/FluentBundle.cs
Normal file
167
OpenRA.Game/FluentBundle.cs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright (c) The OpenRA Developers and Contributors
|
||||||
|
* 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.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using Linguini.Bundle;
|
||||||
|
using Linguini.Bundle.Builder;
|
||||||
|
using Linguini.Shared.Types.Bundle;
|
||||||
|
using Linguini.Syntax.Parser;
|
||||||
|
using Linguini.Syntax.Parser.Error;
|
||||||
|
using OpenRA.FileSystem;
|
||||||
|
using OpenRA.Traits;
|
||||||
|
|
||||||
|
namespace OpenRA
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public sealed class FluentReferenceAttribute : Attribute
|
||||||
|
{
|
||||||
|
public readonly bool Optional;
|
||||||
|
public readonly string[] RequiredVariableNames;
|
||||||
|
public readonly LintDictionaryReference DictionaryReference;
|
||||||
|
|
||||||
|
public FluentReferenceAttribute() { }
|
||||||
|
|
||||||
|
public FluentReferenceAttribute(params string[] requiredVariableNames)
|
||||||
|
{
|
||||||
|
RequiredVariableNames = requiredVariableNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FluentReferenceAttribute(LintDictionaryReference dictionaryReference = LintDictionaryReference.None)
|
||||||
|
{
|
||||||
|
DictionaryReference = dictionaryReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FluentReferenceAttribute(bool optional)
|
||||||
|
{
|
||||||
|
Optional = optional;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FluentBundle
|
||||||
|
{
|
||||||
|
readonly Linguini.Bundle.FluentBundle bundle;
|
||||||
|
|
||||||
|
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem)
|
||||||
|
: this(culture, paths, fileSystem, error => Log.Write("debug", error.Message)) { }
|
||||||
|
|
||||||
|
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, string text)
|
||||||
|
: this(culture, paths, fileSystem, text, error => Log.Write("debug", error.Message)) { }
|
||||||
|
|
||||||
|
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, Action<ParseError> onError)
|
||||||
|
: this(culture, paths, fileSystem, null, onError) { }
|
||||||
|
|
||||||
|
public FluentBundle(string culture, string text, Action<ParseError> onError)
|
||||||
|
: this(culture, null, null, text, onError) { }
|
||||||
|
|
||||||
|
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, string text, Action<ParseError> onError)
|
||||||
|
{
|
||||||
|
bundle = LinguiniBuilder.Builder()
|
||||||
|
.CultureInfo(new CultureInfo(culture))
|
||||||
|
.SkipResources()
|
||||||
|
.SetUseIsolating(false)
|
||||||
|
.UseConcurrent()
|
||||||
|
.UncheckedBuild();
|
||||||
|
|
||||||
|
if (paths != null)
|
||||||
|
{
|
||||||
|
foreach (var path in paths)
|
||||||
|
{
|
||||||
|
var stream = fileSystem.Open(path);
|
||||||
|
using (var reader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
var parser = new LinguiniParser(reader);
|
||||||
|
var resource = parser.Parse();
|
||||||
|
foreach (var error in resource.Errors)
|
||||||
|
onError(error);
|
||||||
|
|
||||||
|
bundle.AddResourceOverriding(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
var parser = new LinguiniParser(text);
|
||||||
|
var resource = parser.Parse();
|
||||||
|
foreach (var error in resource.Errors)
|
||||||
|
onError(error);
|
||||||
|
|
||||||
|
bundle.AddResourceOverriding(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetMessage(string key, object[] args = null)
|
||||||
|
{
|
||||||
|
if (!TryGetMessage(key, out var message, args))
|
||||||
|
message = key;
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetMessage(string key, out string value, object[] args = null)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!HasMessage(key))
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, IFluentType> fluentArgs = null;
|
||||||
|
if (args != null)
|
||||||
|
{
|
||||||
|
if (args.Length % 2 != 0)
|
||||||
|
throw new ArgumentException("Expected a comma separated list of name, value arguments " +
|
||||||
|
"but the number of arguments is not a multiple of two", nameof(args));
|
||||||
|
|
||||||
|
fluentArgs = new Dictionary<string, IFluentType>();
|
||||||
|
for (var i = 0; i < args.Length; i += 2)
|
||||||
|
{
|
||||||
|
var argKey = args[i] as string;
|
||||||
|
if (string.IsNullOrEmpty(argKey))
|
||||||
|
throw new ArgumentException($"Expected the argument at index {i} to be a non-empty string", nameof(args));
|
||||||
|
|
||||||
|
var argValue = args[i + 1];
|
||||||
|
if (argValue == null)
|
||||||
|
throw new ArgumentNullException(nameof(args), $"Expected the argument at index {i + 1} to be a non-null value");
|
||||||
|
|
||||||
|
fluentArgs.Add(argKey, argValue.ToFluentType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = bundle.TryGetAttrMessage(key, fluentArgs, out var errors, out value);
|
||||||
|
foreach (var error in errors)
|
||||||
|
Log.Write("debug", $"FluentBundle of {key}: {error}");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Log.Write("debug", $"FluentBundle of {key}: threw exception");
|
||||||
|
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasMessage(string key)
|
||||||
|
{
|
||||||
|
return bundle.HasAttrMessage(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ using Linguini.Shared.Types.Bundle;
|
|||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public static class TranslationExts
|
public static class FluentExts
|
||||||
{
|
{
|
||||||
public static IFluentType ToFluentType(this object value)
|
public static IFluentType ToFluentType(this object value)
|
||||||
{
|
{
|
||||||
93
OpenRA.Game/FluentProvider.cs
Normal file
93
OpenRA.Game/FluentProvider.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright (c) The OpenRA Developers and Contributors
|
||||||
|
* 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.Text;
|
||||||
|
using OpenRA.FileSystem;
|
||||||
|
|
||||||
|
namespace OpenRA
|
||||||
|
{
|
||||||
|
public static class FluentProvider
|
||||||
|
{
|
||||||
|
// Ensure thread-safety.
|
||||||
|
static readonly object SyncObject = new();
|
||||||
|
static FluentBundle modFluentBundle;
|
||||||
|
static FluentBundle mapFluentBundle;
|
||||||
|
|
||||||
|
public static void Initialize(ModData modData, IReadOnlyFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
lock (SyncObject)
|
||||||
|
{
|
||||||
|
modFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, modData.Manifest.FluentMessages, fileSystem);
|
||||||
|
if (fileSystem is Map map && map.FluentMessageDefinitions != null)
|
||||||
|
{
|
||||||
|
var files = Array.Empty<string>();
|
||||||
|
if (map.FluentMessageDefinitions.Value != null)
|
||||||
|
files = FieldLoader.GetValue<string[]>("value", map.FluentMessageDefinitions.Value);
|
||||||
|
|
||||||
|
string text = null;
|
||||||
|
if (map.FluentMessageDefinitions.Nodes.Length > 0)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var node in map.FluentMessageDefinitions.Nodes)
|
||||||
|
if (node.Key == "base64")
|
||||||
|
builder.Append(Encoding.UTF8.GetString(Convert.FromBase64String(node.Value.Value)));
|
||||||
|
|
||||||
|
text = builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
mapFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, files, fileSystem, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetMessage(string key, params object[] args)
|
||||||
|
{
|
||||||
|
lock (SyncObject)
|
||||||
|
{
|
||||||
|
// By prioritizing mod-level fluent bundles we prevent maps from overwriting string keys. We do not want to
|
||||||
|
// allow maps to change the UI nor any other strings not exposed to the map.
|
||||||
|
if (modFluentBundle.TryGetMessage(key, out var message, args))
|
||||||
|
return message;
|
||||||
|
|
||||||
|
if (mapFluentBundle != null)
|
||||||
|
return mapFluentBundle.GetMessage(key, args);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetMessage(string key, out string message, params object[] args)
|
||||||
|
{
|
||||||
|
lock (SyncObject)
|
||||||
|
{
|
||||||
|
// By prioritizing mod-level bundle we prevent maps from overwriting string keys. We do not want to
|
||||||
|
// allow maps to change the UI nor any other strings not exposed to the map.
|
||||||
|
if (modFluentBundle.TryGetMessage(key, out message, args))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (mapFluentBundle != null && mapFluentBundle.TryGetMessage(key, out message, args))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Should only be used by <see cref="MapPreview"/>.</summary>
|
||||||
|
internal static bool TryGetModMessage(string key, out string message, params object[] args)
|
||||||
|
{
|
||||||
|
lock (SyncObject)
|
||||||
|
{
|
||||||
|
return modFluentBundle.TryGetMessage(key, out message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static class Game
|
public static class Game
|
||||||
{
|
{
|
||||||
[TranslationReference("filename")]
|
[FluentReference("filename")]
|
||||||
const string SavedScreenshot = "notification-saved-screenshot";
|
const string SavedScreenshot = "notification-saved-screenshot";
|
||||||
|
|
||||||
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
|
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
|
||||||
@@ -180,6 +180,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static event Action BeforeGameStart = () => { };
|
public static event Action BeforeGameStart = () => { };
|
||||||
|
public static event Action AfterGameStart = () => { };
|
||||||
internal static void StartGame(string mapUID, WorldType type)
|
internal static void StartGame(string mapUID, WorldType type)
|
||||||
{
|
{
|
||||||
// Dispose of the old world before creating a new one.
|
// Dispose of the old world before creating a new one.
|
||||||
@@ -223,6 +224,12 @@ namespace OpenRA
|
|||||||
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
||||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
|
|
||||||
|
// PostLoadComplete is designed for anything that should trigger at the very end of loading.
|
||||||
|
// e.g. audio notifications that the game is starting.
|
||||||
|
OrderManager.World.PostLoadComplete(worldRenderer);
|
||||||
|
|
||||||
|
AfterGameStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestartGame()
|
public static void RestartGame()
|
||||||
@@ -361,22 +368,7 @@ namespace OpenRA
|
|||||||
Settings.Game.Platform = p;
|
Settings.Game.Platform = p;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
|
var platform = CreatePlatform(p);
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
var loader = new AssemblyLoader(rendererPath);
|
|
||||||
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
|
||||||
|
|
||||||
#else
|
|
||||||
// NOTE: This is currently the only use of System.Reflection in this file, so would give an unused using error if we import it above
|
|
||||||
var assembly = System.Reflection.Assembly.LoadFile(rendererPath);
|
|
||||||
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (platformType == null)
|
|
||||||
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
|
|
||||||
|
|
||||||
var platform = (IPlatform)platformType.GetConstructor(Type.EmptyTypes).Invoke(null);
|
|
||||||
Renderer = new Renderer(platform, Settings.Graphics);
|
Renderer = new Renderer(platform, Settings.Graphics);
|
||||||
Sound = new Sound(platform, Settings.Sound);
|
Sound = new Sound(platform, Settings.Sound);
|
||||||
|
|
||||||
@@ -403,7 +395,7 @@ namespace OpenRA
|
|||||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||||
Console.WriteLine("Internal mods:");
|
Console.WriteLine("Internal mods:");
|
||||||
foreach (var mod in Mods)
|
foreach (var mod in Mods)
|
||||||
Console.WriteLine($"\t{mod.Key}: {mod.Value.Metadata.Title} ({mod.Value.Metadata.Version})");
|
Console.WriteLine($"\t{mod.Key} ({mod.Value.Metadata.Version})");
|
||||||
|
|
||||||
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
||||||
|
|
||||||
@@ -416,7 +408,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Sanitize input from platform-specific launchers
|
// Sanitize input from platform-specific launchers
|
||||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||||
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
if (launchPath != null && launchPath[0] == '"' && launchPath.Last() == '"')
|
||||||
launchPath = launchPath[1..^1];
|
launchPath = launchPath[1..^1];
|
||||||
|
|
||||||
// Metadata registration requires an explicit launch path
|
// Metadata registration requires an explicit launch path
|
||||||
@@ -428,11 +420,31 @@ namespace OpenRA
|
|||||||
|
|
||||||
Console.WriteLine("External mods:");
|
Console.WriteLine("External mods:");
|
||||||
foreach (var mod in ExternalMods)
|
foreach (var mod in ExternalMods)
|
||||||
Console.WriteLine($"\t{mod.Key}: {mod.Value.Title} ({mod.Value.Version})");
|
Console.WriteLine($"\t{mod.Key} ({mod.Value.Version})");
|
||||||
|
|
||||||
InitializeMod(modID, args);
|
InitializeMod(modID, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IPlatform CreatePlatform(string platformName)
|
||||||
|
{
|
||||||
|
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + platformName + ".dll");
|
||||||
|
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
var loader = new AssemblyLoader(rendererPath);
|
||||||
|
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||||
|
|
||||||
|
#else
|
||||||
|
// NOTE: This is currently the only use of System.Reflection in this file, so would give an unused using error if we import it above
|
||||||
|
var assembly = System.Reflection.Assembly.LoadFile(rendererPath);
|
||||||
|
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (platformType == null)
|
||||||
|
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
|
||||||
|
|
||||||
|
return (IPlatform)platformType.GetConstructor(Type.EmptyTypes).Invoke(null);
|
||||||
|
}
|
||||||
|
|
||||||
public static void InitializeMod(string mod, Arguments args)
|
public static void InitializeMod(string mod, Arguments args)
|
||||||
{
|
{
|
||||||
// Clear static state if we have switched mods
|
// Clear static state if we have switched mods
|
||||||
@@ -484,11 +496,11 @@ namespace OpenRA
|
|||||||
Renderer.InitializeDepthBuffer(grid);
|
Renderer.InitializeDepthBuffer(grid);
|
||||||
|
|
||||||
Cursor?.Dispose();
|
Cursor?.Dispose();
|
||||||
Cursor = new CursorManager(ModData.CursorProvider);
|
Cursor = new CursorManager(ModData.CursorProvider, ModData.Manifest.CursorSheetSize);
|
||||||
|
|
||||||
var metadata = ModData.Manifest.Metadata;
|
var metadata = ModData.Manifest.Metadata;
|
||||||
if (!string.IsNullOrEmpty(metadata.WindowTitle))
|
if (!string.IsNullOrEmpty(metadata.WindowTitleTranslated))
|
||||||
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
|
Renderer.Window.SetWindowTitle(metadata.WindowTitleTranslated);
|
||||||
|
|
||||||
PerfHistory.Items["render"].HasNormalTick = false;
|
PerfHistory.Items["render"].HasNormalTick = false;
|
||||||
PerfHistory.Items["batches"].HasNormalTick = false;
|
PerfHistory.Items["batches"].HasNormalTick = false;
|
||||||
@@ -524,10 +536,11 @@ namespace OpenRA
|
|||||||
.Where(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Shellmap))
|
.Where(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Shellmap))
|
||||||
.Select(m => m.Uid);
|
.Select(m => m.Uid);
|
||||||
|
|
||||||
if (!shellmaps.Any())
|
var shellmap = shellmaps.RandomOrDefault(CosmeticRandom);
|
||||||
|
if (shellmap == null)
|
||||||
throw new InvalidDataException("No valid shellmaps available");
|
throw new InvalidDataException("No valid shellmaps available");
|
||||||
|
|
||||||
return shellmaps.Random(CosmeticRandom);
|
return shellmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SwitchToExternalMod(ExternalMod mod, string[] launchArguments = null, Action onFailed = null)
|
public static void SwitchToExternalMod(ExternalMod mod, string[] launchArguments = null, Action onFailed = null)
|
||||||
@@ -582,7 +595,7 @@ namespace OpenRA
|
|||||||
Log.Write("debug", "Taking screenshot " + path);
|
Log.Write("debug", "Taking screenshot " + path);
|
||||||
|
|
||||||
Renderer.SaveScreenshot(path);
|
Renderer.SaveScreenshot(path);
|
||||||
TextNotificationsManager.Debug(TranslationProvider.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
|
TextNotificationsManager.Debug(FluentProvider.GetMessage(SavedScreenshot, "filename", filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,7 +614,10 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (orderManager.LastTickTime.ShouldAdvance(tick))
|
if (orderManager.LastTickTime.ShouldAdvance(tick))
|
||||||
{
|
{
|
||||||
using (new PerfSample("tick_time"))
|
if (orderManager.GameStarted && orderManager.LocalFrameNumber == 0)
|
||||||
|
PerfHistory.Reset(); // Remove history that occurred whilst the new game was loading.
|
||||||
|
|
||||||
|
using (var sample = new PerfSample("tick_time"))
|
||||||
{
|
{
|
||||||
orderManager.LastTickTime.AdvanceTickTime(tick);
|
orderManager.LastTickTime.AdvanceTickTime(tick);
|
||||||
|
|
||||||
@@ -610,7 +626,11 @@ namespace OpenRA
|
|||||||
Sync.RunUnsynced(world, orderManager.TickImmediate);
|
Sync.RunUnsynced(world, orderManager.TickImmediate);
|
||||||
|
|
||||||
if (world == null)
|
if (world == null)
|
||||||
|
{
|
||||||
|
if (orderManager.GameStarted)
|
||||||
|
PerfHistory.Reset(); // Remove old history when a new game starts.
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (orderManager.TryTick())
|
if (orderManager.TryTick())
|
||||||
{
|
{
|
||||||
@@ -664,7 +684,7 @@ namespace OpenRA
|
|||||||
// Prepare renderables (i.e. render voxels) before calling BeginFrame
|
// Prepare renderables (i.e. render voxels) before calling BeginFrame
|
||||||
using (new PerfSample("render_prepare"))
|
using (new PerfSample("render_prepare"))
|
||||||
{
|
{
|
||||||
Renderer.WorldModelRenderer.BeginFrame();
|
worldRenderer?.BeginFrame();
|
||||||
|
|
||||||
// World rendering is disabled while the loading screen is displayed
|
// World rendering is disabled while the loading screen is displayed
|
||||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||||
@@ -674,7 +694,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ui.PrepareRenderables();
|
Ui.PrepareRenderables();
|
||||||
Renderer.WorldModelRenderer.EndFrame();
|
worldRenderer?.EndFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// worldRenderer is null during the initial install/download screen
|
// worldRenderer is null during the initial install/download screen
|
||||||
@@ -778,7 +798,7 @@ namespace OpenRA
|
|||||||
var logicWorld = worldRenderer?.World;
|
var logicWorld = worldRenderer?.World;
|
||||||
|
|
||||||
// ReplayTimestep = 0 means the replay is paused: we need to keep logicInterval as UI.Timestep to avoid breakage
|
// ReplayTimestep = 0 means the replay is paused: we need to keep logicInterval as UI.Timestep to avoid breakage
|
||||||
if (logicWorld != null && !(logicWorld.IsReplay && logicWorld.ReplayTimestep == 0))
|
if (logicWorld != null && (!logicWorld.IsReplay || logicWorld.ReplayTimestep != 0))
|
||||||
logicInterval = logicWorld == OrderManager.World ? OrderManager.SuggestedTimestep : logicWorld.Timestep;
|
logicInterval = logicWorld == OrderManager.World ? OrderManager.SuggestedTimestep : logicWorld.Timestep;
|
||||||
|
|
||||||
// Ideal time between screen updates
|
// Ideal time between screen updates
|
||||||
@@ -913,15 +933,15 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var endpoints = new List<IPEndPoint>
|
var endpoints = new List<IPEndPoint>
|
||||||
{
|
{
|
||||||
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
|
new(IPAddress.IPv6Any, settings.ListenPort),
|
||||||
new IPEndPoint(IPAddress.Any, settings.ListenPort)
|
new(IPAddress.Any, settings.ListenPort)
|
||||||
};
|
};
|
||||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
||||||
|
|
||||||
return server.GetEndpointForLocalConnection();
|
return server.GetEndpointForLocalConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ConnectionTarget CreateLocalServer(string map)
|
public static ConnectionTarget CreateLocalServer(string map, bool isSkirmish = false)
|
||||||
{
|
{
|
||||||
var settings = new ServerSettings()
|
var settings = new ServerSettings()
|
||||||
{
|
{
|
||||||
@@ -935,9 +955,9 @@ namespace OpenRA
|
|||||||
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
||||||
var endpoints = new List<IPEndPoint>
|
var endpoints = new List<IPEndPoint>
|
||||||
{
|
{
|
||||||
new IPEndPoint(IPAddress.Loopback, 0)
|
new(IPAddress.Loopback, 0)
|
||||||
};
|
};
|
||||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
|
server = new Server.Server(endpoints, settings, ModData, isSkirmish ? ServerType.Skirmish : ServerType.Local);
|
||||||
|
|
||||||
return server.GetEndpointForLocalConnection();
|
return server.GetEndpointForLocalConnection();
|
||||||
}
|
}
|
||||||
@@ -965,7 +985,7 @@ namespace OpenRA
|
|||||||
Order.Command($"state {Session.ClientState.Ready}")
|
Order.Command($"state {Session.ClientState.Ready}")
|
||||||
};
|
};
|
||||||
|
|
||||||
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.Package.Name) == launchMap);
|
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.PackageName) == launchMap);
|
||||||
if (map == null)
|
if (map == null)
|
||||||
throw new ArgumentException($"Could not find map '{launchMap}'.");
|
throw new ArgumentException($"Could not find map '{launchMap}'.");
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public class GameInformation
|
public class GameInformation
|
||||||
{
|
{
|
||||||
|
[FluentReference("name", "number")]
|
||||||
|
const string EnumeratedBotName = "enumerated-bot-name";
|
||||||
|
|
||||||
public string Mod;
|
public string Mod;
|
||||||
public string Version;
|
public string Version;
|
||||||
|
|
||||||
@@ -49,13 +52,13 @@ namespace OpenRA
|
|||||||
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
|
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameInformation Deserialize(string data)
|
public static GameInformation Deserialize(string data, string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var info = new GameInformation();
|
var info = new GameInformation();
|
||||||
|
|
||||||
var nodes = MiniYaml.FromString(data);
|
var nodes = MiniYaml.FromString(data, path);
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var keyParts = node.Key.Split('@');
|
var keyParts = node.Key.Split('@');
|
||||||
@@ -85,7 +88,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var nodes = new List<MiniYamlNode>
|
var nodes = new List<MiniYamlNode>
|
||||||
{
|
{
|
||||||
new MiniYamlNode("Root", FieldSaver.Save(this))
|
new("Root", FieldSaver.Save(this))
|
||||||
};
|
};
|
||||||
|
|
||||||
for (var i = 0; i < Players.Count; i++)
|
for (var i = 0; i < Players.Count; i++)
|
||||||
@@ -118,11 +121,12 @@ namespace OpenRA
|
|||||||
Name = runtimePlayer.PlayerName,
|
Name = runtimePlayer.PlayerName,
|
||||||
IsHuman = !runtimePlayer.IsBot,
|
IsHuman = !runtimePlayer.IsBot,
|
||||||
IsBot = runtimePlayer.IsBot,
|
IsBot = runtimePlayer.IsBot,
|
||||||
|
BotType = runtimePlayer.BotType,
|
||||||
FactionName = runtimePlayer.Faction.Name,
|
FactionName = runtimePlayer.Faction.Name,
|
||||||
FactionId = runtimePlayer.Faction.InternalName,
|
FactionId = runtimePlayer.Faction.InternalName,
|
||||||
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
||||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||||
Color = runtimePlayer.Color,
|
Color = OpenRA.Player.GetColor(runtimePlayer),
|
||||||
Team = client.Team,
|
Team = client.Team,
|
||||||
Handicap = client.Handicap,
|
Handicap = client.Handicap,
|
||||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||||
@@ -143,6 +147,19 @@ namespace OpenRA
|
|||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ResolvedPlayerName(Player player)
|
||||||
|
{
|
||||||
|
if (player.IsBot)
|
||||||
|
{
|
||||||
|
var number = Players.Where(p => p.BotType == player.BotType).ToList().IndexOf(player) + 1;
|
||||||
|
return FluentProvider.GetMessage(EnumeratedBotName,
|
||||||
|
"name", FluentProvider.GetMessage(player.Name),
|
||||||
|
"number", number);
|
||||||
|
}
|
||||||
|
|
||||||
|
return player.Name;
|
||||||
|
}
|
||||||
|
|
||||||
public class Player
|
public class Player
|
||||||
{
|
{
|
||||||
#region Start-up information
|
#region Start-up information
|
||||||
@@ -153,6 +170,7 @@ namespace OpenRA
|
|||||||
public string Name;
|
public string Name;
|
||||||
public bool IsHuman;
|
public bool IsHuman;
|
||||||
public bool IsBot;
|
public bool IsBot;
|
||||||
|
public string BotType;
|
||||||
|
|
||||||
/// <summary>The faction's display name.</summary>
|
/// <summary>The faction's display name.</summary>
|
||||||
public string FactionName;
|
public string FactionName;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace OpenRA
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ActorInfo
|
public class ActorInfo
|
||||||
{
|
{
|
||||||
public const string AbstractActorPrefix = "^";
|
public const char AbstractActorPrefix = '^';
|
||||||
public const char TraitInstanceSeparator = '@';
|
public const char TraitInstanceSeparator = '@';
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -33,7 +33,7 @@ namespace OpenRA
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
readonly TypeDictionary traits = new();
|
readonly TypeDictionary traits = new();
|
||||||
List<TraitInfo> constructOrderCache = null;
|
TraitInfo[] constructOrderCache = null;
|
||||||
|
|
||||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||||
{
|
{
|
||||||
@@ -130,6 +130,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Continue resolving traits as long as possible.
|
// Continue resolving traits as long as possible.
|
||||||
// Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass.
|
// Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass.
|
||||||
|
#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection
|
||||||
var readyToResolve = more.ToList();
|
var readyToResolve = more.ToList();
|
||||||
while (readyToResolve.Count != 0)
|
while (readyToResolve.Count != 0)
|
||||||
{
|
{
|
||||||
@@ -138,6 +139,7 @@ namespace OpenRA
|
|||||||
readyToResolve.Clear();
|
readyToResolve.Clear();
|
||||||
readyToResolve.AddRange(more);
|
readyToResolve.AddRange(more);
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CA1851
|
||||||
|
|
||||||
if (unresolved.Count != 0)
|
if (unresolved.Count != 0)
|
||||||
{
|
{
|
||||||
@@ -160,7 +162,7 @@ namespace OpenRA
|
|||||||
throw new YamlException(exceptionString);
|
throw new YamlException(exceptionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructOrderCache = resolved.Select(r => r.Trait).ToList();
|
constructOrderCache = resolved.Select(r => r.Trait).ToArray();
|
||||||
return constructOrderCache;
|
return constructOrderCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +187,7 @@ namespace OpenRA
|
|||||||
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
||||||
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
||||||
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
||||||
public IEnumerable<T> TraitInfos<T>() where T : ITraitInfoInterface { return traits.WithInterface<T>(); }
|
public IReadOnlyCollection<T> TraitInfos<T>() where T : ITraitInfoInterface { return traits.WithInterface<T>(); }
|
||||||
|
|
||||||
public BitSet<TargetableType> GetAllTargetTypes()
|
public BitSet<TargetableType> GetAllTargetTypes()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
|
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
|
||||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix));
|
||||||
|
|
||||||
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
|
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
|
||||||
k => new WeaponInfo(k.Value));
|
k => new WeaponInfo(k.Value));
|
||||||
@@ -182,7 +182,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
|
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
|
||||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix));
|
||||||
|
|
||||||
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
|
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
|
||||||
k => new WeaponInfo(k.Value));
|
k => new WeaponInfo(k.Value));
|
||||||
@@ -226,10 +226,10 @@ namespace OpenRA
|
|||||||
|
|
||||||
static bool AnyCustomYaml(MiniYaml yaml)
|
static bool AnyCustomYaml(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
return yaml != null && (yaml.Value != null || yaml.Nodes.Count > 0);
|
return yaml != null && (yaml.Value != null || yaml.Nodes.Length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool AnyFlaggedTraits(ModData modData, List<MiniYamlNode> actors)
|
static bool AnyFlaggedTraits(ModData modData, IEnumerable<MiniYamlNode> actors)
|
||||||
{
|
{
|
||||||
foreach (var actorNode in actors)
|
foreach (var actorNode in actors)
|
||||||
{
|
{
|
||||||
@@ -260,18 +260,18 @@ namespace OpenRA
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Any trait overrides that aren't explicitly whitelisted are flagged
|
// Any trait overrides that aren't explicitly whitelisted are flagged
|
||||||
if (mapRules != null)
|
if (mapRules == null)
|
||||||
{
|
return false;
|
||||||
if (AnyFlaggedTraits(modData, mapRules.Nodes))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (mapRules.Value != null)
|
if (AnyFlaggedTraits(modData, mapRules.Nodes))
|
||||||
{
|
return true;
|
||||||
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
|
|
||||||
foreach (var f in mapFiles)
|
if (mapRules.Value != null)
|
||||||
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
|
{
|
||||||
return true;
|
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
|
||||||
}
|
foreach (var f in mapFiles)
|
||||||
|
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -40,16 +40,16 @@ namespace OpenRA.GameRules
|
|||||||
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||||
{
|
{
|
||||||
var ret = new Dictionary<string, SoundPool>();
|
var ret = new Dictionary<string, SoundPool>();
|
||||||
var classifiction = y.Nodes.First(x => x.Key == key);
|
var classifiction = y.NodeWithKey(key);
|
||||||
foreach (var t in classifiction.Value.Nodes)
|
foreach (var t in classifiction.Value.Nodes)
|
||||||
{
|
{
|
||||||
var volumeModifier = 1f;
|
var volumeModifier = 1f;
|
||||||
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.VolumeModifier));
|
var volumeModifierNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.VolumeModifier));
|
||||||
if (volumeModifierNode != null)
|
if (volumeModifierNode != null)
|
||||||
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
||||||
|
|
||||||
var interruptType = SoundPool.DefaultInterruptType;
|
var interruptType = SoundPool.DefaultInterruptType;
|
||||||
var interruptTypeNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.InterruptType));
|
var interruptTypeNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.InterruptType));
|
||||||
if (interruptTypeNode != null)
|
if (interruptTypeNode != null)
|
||||||
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
||||||
|
|
||||||
|
|||||||
@@ -139,13 +139,14 @@ namespace OpenRA.GameRules
|
|||||||
{
|
{
|
||||||
// Resolve any weapon-level yaml inheritance or removals
|
// Resolve any weapon-level yaml inheritance or removals
|
||||||
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
||||||
content.Nodes = MiniYaml.Merge(new[] { content.Nodes });
|
content = content.WithNodes(MiniYaml.Merge(new IReadOnlyCollection<MiniYamlNode>[] { content.Nodes }));
|
||||||
FieldLoader.Load(this, content);
|
FieldLoader.Load(this, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object LoadProjectile(MiniYaml yaml)
|
static object LoadProjectile(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
var proj = yaml.NodeWithKeyOrDefault("Projectile")?.Value;
|
||||||
|
if (proj == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||||
@@ -159,7 +160,7 @@ namespace OpenRA.GameRules
|
|||||||
static object LoadWarheads(MiniYaml yaml)
|
static object LoadWarheads(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var retList = new List<IWarhead>();
|
var retList = new List<IWarhead>();
|
||||||
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
|
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead", StringComparison.Ordinal)))
|
||||||
{
|
{
|
||||||
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
|
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
|
|||||||
@@ -10,13 +10,12 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public class GameSpeed
|
public class GameSpeed
|
||||||
{
|
{
|
||||||
[TranslationReference]
|
[FluentReference]
|
||||||
[FieldLoader.Require]
|
[FieldLoader.Require]
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ namespace OpenRA
|
|||||||
static object LoadSpeeds(MiniYaml y)
|
static object LoadSpeeds(MiniYaml y)
|
||||||
{
|
{
|
||||||
var ret = new Dictionary<string, GameSpeed>();
|
var ret = new Dictionary<string, GameSpeed>();
|
||||||
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
|
var speedsNode = y.NodeWithKeyOrDefault("Speeds");
|
||||||
if (speedsNode == null)
|
if (speedsNode == null)
|
||||||
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
|
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace OpenRA.Graphics
|
|||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public bool IsDecoration { get; set; }
|
public bool IsDecoration { get; set; }
|
||||||
|
|
||||||
|
readonly Map map;
|
||||||
readonly SequenceSet sequences;
|
readonly SequenceSet sequences;
|
||||||
readonly Func<WAngle> facingFunc;
|
readonly Func<WAngle> facingFunc;
|
||||||
readonly Func<bool> paused;
|
readonly Func<bool> paused;
|
||||||
@@ -43,6 +44,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||||
{
|
{
|
||||||
|
map = world.Map;
|
||||||
sequences = world.Map.Sequences;
|
sequences = world.Map.Sequences;
|
||||||
Name = name.ToLowerInvariant();
|
Name = name.ToLowerInvariant();
|
||||||
this.facingFunc = facingFunc;
|
this.facingFunc = facingFunc;
|
||||||
@@ -58,13 +60,18 @@ namespace OpenRA.Graphics
|
|||||||
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
|
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
|
||||||
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
||||||
var (image, rotation) = CurrentSequence.GetSpriteWithRotation(CurrentFrame, facingFunc());
|
var (image, rotation) = CurrentSequence.GetSpriteWithRotation(CurrentFrame, facingFunc());
|
||||||
var imageRenderable = new SpriteRenderable(image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration,
|
var imageRenderable = new SpriteRenderable(
|
||||||
rotation);
|
image, pos, offset, CurrentSequence.ZOffset + zOffset, palette,
|
||||||
|
CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration, rotation);
|
||||||
|
|
||||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||||
if (shadow != null)
|
if (shadow != null)
|
||||||
{
|
{
|
||||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
var height = map.DistanceAboveTerrain(pos).Length;
|
||||||
|
|
||||||
|
var shadowRenderable = new SpriteRenderable(
|
||||||
|
shadow, pos, offset - new WVec(0, 0, height), CurrentSequence.ShadowZOffset + zOffset + height, palette,
|
||||||
|
CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
||||||
true, rotation);
|
true, rotation);
|
||||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,11 +77,12 @@ namespace OpenRA.Graphics
|
|||||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||||
|
|
||||||
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
||||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool)));
|
||||||
|
|
||||||
foreach (var c in chrome)
|
foreach (var c in chrome)
|
||||||
if (!c.Key.StartsWith("^", StringComparison.Ordinal))
|
if (!c.Key.StartsWith('^'))
|
||||||
LoadCollection(c.Key, c.Value);
|
LoadCollection(c.Key, c.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,14 +228,18 @@ namespace OpenRA.Graphics
|
|||||||
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], 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.PanelSides) ? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density) : null)
|
sprites = sides
|
||||||
|
.Select(x =>
|
||||||
|
ps.HasSide(x.PanelSides)
|
||||||
|
? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density)
|
||||||
|
: null)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// PERF: We don't need to search for images if there are no definitions.
|
// PERF: We don't need to search for images if there are no definitions.
|
||||||
// PERF: It's more efficient to send an empty array rather than an array of 9 nulls.
|
// PERF: It's more efficient to send an empty array rather than an array of 9 nulls.
|
||||||
if (!collection.Regions.Any())
|
if (collection.Regions.Count == 0)
|
||||||
return Array.Empty<Sprite>();
|
return Array.Empty<Sprite>();
|
||||||
|
|
||||||
// Support manual definitions for unusual dialog layouts
|
// Support manual definitions for unusual dialog layouts
|
||||||
|
|||||||
@@ -11,11 +11,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public sealed class CursorManager
|
public sealed class CursorManager : IDisposable
|
||||||
{
|
{
|
||||||
sealed class Cursor
|
sealed class Cursor
|
||||||
{
|
{
|
||||||
@@ -38,13 +39,16 @@ namespace OpenRA.Graphics
|
|||||||
readonly bool hardwareCursorsDisabled = false;
|
readonly bool hardwareCursorsDisabled = false;
|
||||||
bool hardwareCursorsDoubled = false;
|
bool hardwareCursorsDoubled = false;
|
||||||
|
|
||||||
public CursorManager(CursorProvider cursorProvider)
|
public CursorManager(CursorProvider cursorProvider, int cursorSheetSize)
|
||||||
{
|
{
|
||||||
hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors;
|
hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors;
|
||||||
|
|
||||||
graphicSettings = Game.Settings.Graphics;
|
graphicSettings = Game.Settings.Graphics;
|
||||||
sheetBuilder = new SheetBuilder(SheetType.BGRA);
|
sheetBuilder = new SheetBuilder(SheetType.BGRA, cursorSheetSize);
|
||||||
foreach (var kv in cursorProvider.Cursors)
|
|
||||||
|
// Sort the cursors for better packing onto the sheet.
|
||||||
|
foreach (var kv in cursorProvider.Cursors
|
||||||
|
.OrderBy(kvp => kvp.Value.Frames.Max(f => f.Size.Height)))
|
||||||
{
|
{
|
||||||
var frames = kv.Value.Frames;
|
var frames = kv.Value.Frames;
|
||||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||||
@@ -91,10 +95,6 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
|
|
||||||
CreateOrUpdateHardwareCursors();
|
CreateOrUpdateHardwareCursors();
|
||||||
|
|
||||||
foreach (var s in sheetBuilder.AllSheets)
|
|
||||||
s.ReleaseBuffer();
|
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +128,8 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sheetBuilder.Current.ReleaseBuffer();
|
||||||
|
|
||||||
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,6 +231,10 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
var width = frame.Size.Width;
|
var width = frame.Size.Width;
|
||||||
var height = frame.Size.Height;
|
var height = frame.Size.Height;
|
||||||
|
|
||||||
|
if (width == 0 || height == 0)
|
||||||
|
return Array.Empty<byte>();
|
||||||
|
|
||||||
var data = new byte[4 * width * height];
|
var data = new byte[4 * width * height];
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ namespace OpenRA.Graphics
|
|||||||
public CursorProvider(ModData modData)
|
public CursorProvider(ModData modData)
|
||||||
{
|
{
|
||||||
var fileSystem = modData.DefaultFileSystem;
|
var fileSystem = modData.DefaultFileSystem;
|
||||||
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
|
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
|
||||||
s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool)));
|
||||||
|
|
||||||
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
|
var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value;
|
||||||
|
|
||||||
// Overwrite previous definitions if there are duplicates
|
// Overwrite previous definitions if there are duplicates
|
||||||
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
||||||
@@ -35,14 +36,14 @@ namespace OpenRA.Graphics
|
|||||||
if (p.Palette != null)
|
if (p.Palette != null)
|
||||||
pals[p.Palette] = p;
|
pals[p.Palette] = p;
|
||||||
|
|
||||||
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
|
Palettes = cursorsYaml.Nodes.Select(n => n.Value.Value)
|
||||||
.Where(p => p != null)
|
.Where(p => p != null)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
|
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
|
||||||
|
|
||||||
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
||||||
var cursors = new Dictionary<string, CursorSequence>();
|
var cursors = new Dictionary<string, CursorSequence>();
|
||||||
foreach (var s in nodesDict["Cursors"].Nodes)
|
foreach (var s in cursorsYaml.Nodes)
|
||||||
foreach (var sequence in s.Value.Nodes)
|
foreach (var sequence in s.Value.Nodes)
|
||||||
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
var d = info.ToDictionary();
|
var d = info.ToDictionary();
|
||||||
|
|
||||||
Start = Exts.ParseIntegerInvariant(d["Start"].Value);
|
Start = Exts.ParseInt32Invariant(d["Start"].Value);
|
||||||
Palette = palette;
|
Palette = palette;
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
||||||
@@ -38,9 +38,9 @@ namespace OpenRA.Graphics
|
|||||||
(d.TryGetValue("End", out yaml) && yaml.Value == "*"))
|
(d.TryGetValue("End", out yaml) && yaml.Value == "*"))
|
||||||
Length = Frames.Length;
|
Length = Frames.Length;
|
||||||
else if (d.TryGetValue("Length", out yaml))
|
else if (d.TryGetValue("Length", out yaml))
|
||||||
Length = Exts.ParseIntegerInvariant(yaml.Value);
|
Length = Exts.ParseInt32Invariant(yaml.Value);
|
||||||
else if (d.TryGetValue("End", out yaml))
|
else if (d.TryGetValue("End", out yaml))
|
||||||
Length = Exts.ParseIntegerInvariant(yaml.Value) - Start;
|
Length = Exts.ParseInt32Invariant(yaml.Value) - Start;
|
||||||
else
|
else
|
||||||
Length = 1;
|
Length = 1;
|
||||||
|
|
||||||
@@ -54,13 +54,13 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
if (d.TryGetValue("X", out yaml))
|
if (d.TryGetValue("X", out yaml))
|
||||||
{
|
{
|
||||||
Exts.TryParseIntegerInvariant(yaml.Value, out var x);
|
Exts.TryParseInt32Invariant(yaml.Value, out var x);
|
||||||
Hotspot = Hotspot.WithX(x);
|
Hotspot = Hotspot.WithX(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d.TryGetValue("Y", out yaml))
|
if (d.TryGetValue("Y", out yaml))
|
||||||
{
|
{
|
||||||
Exts.TryParseIntegerInvariant(yaml.Value, out var y);
|
Exts.TryParseInt32Invariant(yaml.Value, out var y);
|
||||||
Hotspot = Hotspot.WithY(y);
|
Hotspot = Hotspot.WithY(y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,10 @@ namespace OpenRA.Graphics
|
|||||||
public void ReplacePalette(string name, IPalette p)
|
public void ReplacePalette(string name, IPalette p)
|
||||||
{
|
{
|
||||||
if (mutablePalettes.ContainsKey(name))
|
if (mutablePalettes.ContainsKey(name))
|
||||||
|
{
|
||||||
|
palettes[name] = new ImmutablePalette(p);
|
||||||
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p));
|
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p));
|
||||||
|
}
|
||||||
else if (palettes.ContainsKey(name))
|
else if (palettes.ContainsKey(name))
|
||||||
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
|
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
|
||||||
else
|
else
|
||||||
|
|||||||
56
OpenRA.Game/Graphics/MarkerTileRenderable.cs
Normal file
56
OpenRA.Game/Graphics/MarkerTileRenderable.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright (c) The OpenRA Developers and Contributors
|
||||||
|
* 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.Linq;
|
||||||
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
|
namespace OpenRA.Graphics
|
||||||
|
{
|
||||||
|
public class MarkerTileRenderable : IRenderable, IFinalizedRenderable
|
||||||
|
{
|
||||||
|
readonly CPos pos;
|
||||||
|
readonly Color color;
|
||||||
|
|
||||||
|
public MarkerTileRenderable(CPos pos, Color color)
|
||||||
|
{
|
||||||
|
this.pos = pos;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WPos Pos => WPos.Zero;
|
||||||
|
public int ZOffset => 0;
|
||||||
|
public bool IsDecoration => true;
|
||||||
|
|
||||||
|
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||||
|
|
||||||
|
public IRenderable OffsetBy(in WVec vec)
|
||||||
|
{
|
||||||
|
return new MarkerTileRenderable(pos, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRenderable AsDecoration() { return this; }
|
||||||
|
|
||||||
|
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||||
|
public void Render(WorldRenderer wr)
|
||||||
|
{
|
||||||
|
var map = wr.World.Map;
|
||||||
|
var r = map.Grid.Ramps[map.Ramp[pos]];
|
||||||
|
var wpos = map.CenterOfCell(pos) - new WVec(0, 0, r.CenterHeightOffset);
|
||||||
|
|
||||||
|
var corners = r.Corners.Select(corner => wr.Viewport.WorldToViewPx(wr.Screen3DPosition(wpos + corner))).ToList();
|
||||||
|
|
||||||
|
Game.Renderer.RgbaColorRenderer.FillRect(corners[0], corners[1], corners[2], corners[3], color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RenderDebugGeometry(WorldRenderer wr) { }
|
||||||
|
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,8 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using OpenRA.FileSystem;
|
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
using OpenRA.Traits;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
@@ -30,6 +29,14 @@ namespace OpenRA.Graphics
|
|||||||
Rectangle AggregateBounds { get; }
|
Rectangle AggregateBounds { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IModelWidget
|
||||||
|
{
|
||||||
|
public string Palette { get; }
|
||||||
|
public float Scale { get; }
|
||||||
|
public void Setup(Func<bool> isVisible, Func<string> getPalette, Func<string> getPlayerPalette,
|
||||||
|
Func<float> getScale, Func<IModel> getVoxel, Func<WRot> getRotation);
|
||||||
|
}
|
||||||
|
|
||||||
public readonly struct ModelRenderData
|
public readonly struct ModelRenderData
|
||||||
{
|
{
|
||||||
public readonly int Start;
|
public readonly int Start;
|
||||||
@@ -44,51 +51,13 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IModelCache : IDisposable
|
public interface IModelCacheInfo : ITraitInfoInterface { }
|
||||||
|
|
||||||
|
public interface IModelCache
|
||||||
{
|
{
|
||||||
IModel GetModel(string model);
|
IModel GetModel(string model);
|
||||||
IModel GetModelSequence(string model, string sequence);
|
IModel GetModelSequence(string model, string sequence);
|
||||||
bool HasModelSequence(string model, string sequence);
|
bool HasModelSequence(string model, string sequence);
|
||||||
IVertexBuffer<Vertex> VertexBuffer { get; }
|
IVertexBuffer<ModelVertex> VertexBuffer { get; }
|
||||||
}
|
|
||||||
|
|
||||||
public interface IModelSequenceLoader
|
|
||||||
{
|
|
||||||
Action<string> OnMissingModelError { get; set; }
|
|
||||||
IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PlaceholderModelSequenceLoader : IModelSequenceLoader
|
|
||||||
{
|
|
||||||
public Action<string> OnMissingModelError { get; set; }
|
|
||||||
|
|
||||||
sealed class PlaceholderModelCache : IModelCache
|
|
||||||
{
|
|
||||||
public IVertexBuffer<Vertex> VertexBuffer => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public void Dispose() { }
|
|
||||||
|
|
||||||
public IModel GetModel(string model)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IModel GetModelSequence(string model, string sequence)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasModelSequence(string model, string sequence)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlaceholderModelSequenceLoader(ModData modData) { }
|
|
||||||
|
|
||||||
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)
|
|
||||||
{
|
|
||||||
return new PlaceholderModelCache();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
OpenRA.Game/Graphics/ModelVertex.cs
Normal file
53
OpenRA.Game/Graphics/ModelVertex.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright (c) The OpenRA Developers and Contributors
|
||||||
|
* 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.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace OpenRA.Graphics
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public readonly struct ModelVertex
|
||||||
|
{
|
||||||
|
// 3d position
|
||||||
|
public readonly float X, Y, Z;
|
||||||
|
|
||||||
|
// Primary and secondary texture coordinates or RGBA color
|
||||||
|
public readonly float S, T, U, V;
|
||||||
|
|
||||||
|
// Palette and channel flags
|
||||||
|
public readonly float P, C;
|
||||||
|
|
||||||
|
public ModelVertex(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) { }
|
||||||
|
|
||||||
|
public ModelVertex(float x, float y, float z, float s, float t, float u, float v, float p, float c)
|
||||||
|
{
|
||||||
|
X = x; Y = y; Z = z;
|
||||||
|
S = s; T = t;
|
||||||
|
U = u; V = v;
|
||||||
|
P = p; C = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ModelShaderBindings : ShaderBindings
|
||||||
|
{
|
||||||
|
public ModelShaderBindings()
|
||||||
|
: base("model")
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||||
|
{
|
||||||
|
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
|
||||||
|
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
|
||||||
|
new ShaderVertexAttribute("aVertexTexMetadata", ShaderVertexAttributeType.Float, 2, 28),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public static Color GetColor(this IPalette palette, int index)
|
public static Color GetColor(this IPalette palette, int index)
|
||||||
{
|
{
|
||||||
return Color.FromArgb((int)palette[index]);
|
return Color.FromArgb(palette[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IPalette AsReadOnly(this IPalette palette)
|
public static IPalette AsReadOnly(this IPalette palette)
|
||||||
@@ -103,7 +103,7 @@ namespace OpenRA.Graphics
|
|||||||
: this(p)
|
: this(p)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Palette.Size; i++)
|
for (var i = 0; i < Palette.Size; i++)
|
||||||
colors[i] = (uint)r.GetRemappedColor(this.GetColor(i), i).ToArgb();
|
colors[i] = r.GetRemappedColor(this.GetColor(i), i).ToArgb();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutablePalette(IPalette p)
|
public ImmutablePalette(IPalette p)
|
||||||
@@ -142,7 +142,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public void SetColor(int index, Color color)
|
public void SetColor(int index, Color color)
|
||||||
{
|
{
|
||||||
colors[index] = (uint)color.ToArgb();
|
colors[index] = color.ToArgb();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetFromPalette(IPalette p)
|
public void SetFromPalette(IPalette p)
|
||||||
@@ -153,7 +153,7 @@ namespace OpenRA.Graphics
|
|||||||
public void ApplyRemap(IPaletteRemap r)
|
public void ApplyRemap(IPaletteRemap r)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Palette.Size; i++)
|
for (var i = 0; i < Palette.Size; i++)
|
||||||
colors[i] = (uint)r.GetRemappedColor(this.GetColor(i), i).ToArgb();
|
colors[i] = r.GetRemappedColor(this.GetColor(i), i).ToArgb();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,19 +13,17 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
public sealed class PaletteReference
|
public sealed class PaletteReference
|
||||||
{
|
{
|
||||||
readonly float index;
|
|
||||||
readonly HardwarePalette hardwarePalette;
|
readonly HardwarePalette hardwarePalette;
|
||||||
|
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
public IPalette Palette { get; internal set; }
|
public IPalette Palette { get; internal set; }
|
||||||
public float TextureIndex => index / hardwarePalette.Height;
|
public int TextureIndex { get; }
|
||||||
public float TextureMidIndex => (index + 0.5f) / hardwarePalette.Height;
|
|
||||||
|
|
||||||
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
|
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Palette = palette;
|
Palette = palette;
|
||||||
this.index = index;
|
TextureIndex = index;
|
||||||
this.hardwarePalette = hardwarePalette;
|
this.hardwarePalette = hardwarePalette;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ namespace OpenRA
|
|||||||
Automatic,
|
Automatic,
|
||||||
ANGLE,
|
ANGLE,
|
||||||
Modern,
|
Modern,
|
||||||
Embedded,
|
Embedded
|
||||||
Legacy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPlatform
|
public interface IPlatform
|
||||||
{
|
{
|
||||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
|
IPlatformWindow CreateWindow(
|
||||||
|
Size size, WindowMode windowMode, float scaleModifier, int vertexBatchSize, int indexBatchSize, int videoDisplay, GLProfile profile);
|
||||||
ISoundEngine CreateSound(string device);
|
ISoundEngine CreateSound(string device);
|
||||||
IFont CreateFont(byte[] data);
|
IFont CreateFont(byte[] data);
|
||||||
}
|
}
|
||||||
@@ -83,16 +83,18 @@ namespace OpenRA
|
|||||||
|
|
||||||
public interface IGraphicsContext : IDisposable
|
public interface IGraphicsContext : IDisposable
|
||||||
{
|
{
|
||||||
IVertexBuffer<Vertex> CreateVertexBuffer(int size);
|
IVertexBuffer<T> CreateVertexBuffer<T>(int size) where T : struct;
|
||||||
Vertex[] CreateVertices(int size);
|
T[] CreateVertices<T>(int size) where T : struct;
|
||||||
|
IIndexBuffer CreateIndexBuffer(uint[] indices);
|
||||||
ITexture CreateTexture();
|
ITexture CreateTexture();
|
||||||
IFrameBuffer CreateFrameBuffer(Size s);
|
IFrameBuffer CreateFrameBuffer(Size s);
|
||||||
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
|
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
|
||||||
IShader CreateShader(string name);
|
IShader CreateShader(IShaderBindings shaderBindings);
|
||||||
void EnableScissor(int x, int y, int width, int height);
|
void EnableScissor(int x, int y, int width, int height);
|
||||||
void DisableScissor();
|
void DisableScissor();
|
||||||
void Present();
|
void Present();
|
||||||
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
|
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
|
||||||
|
void DrawElements(int numIndices, int offset);
|
||||||
void Clear();
|
void Clear();
|
||||||
void EnableDepthBuffer();
|
void EnableDepthBuffer();
|
||||||
void DisableDepthBuffer();
|
void DisableDepthBuffer();
|
||||||
@@ -102,7 +104,14 @@ namespace OpenRA
|
|||||||
string GLVersion { get; }
|
string GLVersion { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IVertexBuffer<T> : IDisposable
|
public interface IRenderer
|
||||||
|
{
|
||||||
|
void BeginFrame();
|
||||||
|
void EndFrame();
|
||||||
|
void SetPalette(HardwarePalette palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IVertexBuffer<T> : IDisposable where T : struct
|
||||||
{
|
{
|
||||||
void Bind();
|
void Bind();
|
||||||
void SetData(T[] vertices, int length);
|
void SetData(T[] vertices, int length);
|
||||||
@@ -114,6 +123,11 @@ namespace OpenRA
|
|||||||
void SetData(T[] vertices, int offset, int start, int length);
|
void SetData(T[] vertices, int offset, int start, int length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IIndexBuffer : IDisposable
|
||||||
|
{
|
||||||
|
void Bind();
|
||||||
|
}
|
||||||
|
|
||||||
public interface IShader
|
public interface IShader
|
||||||
{
|
{
|
||||||
void SetBool(string name, bool value);
|
void SetBool(string name, bool value);
|
||||||
@@ -124,6 +138,17 @@ namespace OpenRA
|
|||||||
void SetTexture(string param, ITexture texture);
|
void SetTexture(string param, ITexture texture);
|
||||||
void SetMatrix(string param, float[] mtx);
|
void SetMatrix(string param, float[] mtx);
|
||||||
void PrepareRender();
|
void PrepareRender();
|
||||||
|
void Bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IShaderBindings
|
||||||
|
{
|
||||||
|
string VertexShaderName { get; }
|
||||||
|
string VertexShaderCode { get; }
|
||||||
|
string FragmentShaderName { get; }
|
||||||
|
string FragmentShaderCode { get; }
|
||||||
|
int Stride { get; }
|
||||||
|
ShaderVertexAttribute[] Attributes { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TextureScaleFilter { Nearest, Linear }
|
public enum TextureScaleFilter { Nearest, Linear }
|
||||||
@@ -132,6 +157,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
void SetData(byte[] colors, int width, int height);
|
void SetData(byte[] colors, int width, int height);
|
||||||
void SetFloatData(float[] data, int width, int height);
|
void SetFloatData(float[] data, int width, int height);
|
||||||
|
void SetDataFromReadBuffer(Rectangle rect);
|
||||||
byte[] GetData();
|
byte[] GetData();
|
||||||
Size Size { get; }
|
Size Size { get; }
|
||||||
TextureScaleFilter ScaleFilter { get; set; }
|
TextureScaleFilter ScaleFilter { get; set; }
|
||||||
|
|||||||
64
OpenRA.Game/Graphics/RenderPostProcessPassVertex.cs
Normal file
64
OpenRA.Game/Graphics/RenderPostProcessPassVertex.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright (c) The OpenRA Developers and Contributors
|
||||||
|
* 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.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace OpenRA.Graphics
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public readonly struct RenderPostProcessPassVertex
|
||||||
|
{
|
||||||
|
public readonly float X, Y;
|
||||||
|
|
||||||
|
public RenderPostProcessPassVertex(float x, float y)
|
||||||
|
{
|
||||||
|
X = x; Y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public readonly struct RenderPostProcessPassTexturedVertex
|
||||||
|
{
|
||||||
|
// 3d position
|
||||||
|
public readonly float X, Y;
|
||||||
|
public readonly float S, T;
|
||||||
|
|
||||||
|
public RenderPostProcessPassTexturedVertex(float x, float y, float s, float t)
|
||||||
|
{
|
||||||
|
X = x; Y = y;
|
||||||
|
S = s; T = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RenderPostProcessPassShaderBindings : ShaderBindings
|
||||||
|
{
|
||||||
|
public RenderPostProcessPassShaderBindings(string name)
|
||||||
|
: base("postprocess", "postprocess_" + name) { }
|
||||||
|
|
||||||
|
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||||
|
{
|
||||||
|
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RenderPostProcessPassTexturedShaderBindings : ShaderBindings
|
||||||
|
{
|
||||||
|
public RenderPostProcessPassTexturedShaderBindings(string name)
|
||||||
|
: base("postprocess_textured", "postprocess_textured_" + name)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||||
|
{
|
||||||
|
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0),
|
||||||
|
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 2, 8),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ namespace OpenRA.Graphics
|
|||||||
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
|
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
|
||||||
|
|
||||||
readonly SpriteRenderer parent;
|
readonly SpriteRenderer parent;
|
||||||
readonly Vertex[] vertices = new Vertex[6];
|
readonly Vertex[] vertices = new Vertex[4];
|
||||||
|
|
||||||
public RgbaColorRenderer(SpriteRenderer parent)
|
public RgbaColorRenderer(SpriteRenderer parent)
|
||||||
{
|
{
|
||||||
@@ -45,14 +45,12 @@ namespace OpenRA.Graphics
|
|||||||
var eb = endColor.B / 255.0f;
|
var eb = endColor.B / 255.0f;
|
||||||
var ea = endColor.A / 255.0f;
|
var ea = endColor.A / 255.0f;
|
||||||
|
|
||||||
vertices[0] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
|
vertices[0] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0);
|
||||||
vertices[1] = new Vertex(start + corner + Offset, sr, sg, sb, sa, 0, 0);
|
vertices[1] = new Vertex(start + corner + Offset, sr, sg, sb, sa, 0);
|
||||||
vertices[2] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
|
vertices[2] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0);
|
||||||
vertices[3] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
|
vertices[3] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0);
|
||||||
vertices[4] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0, 0);
|
|
||||||
vertices[5] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
|
|
||||||
|
|
||||||
parent.DrawRGBAVertices(vertices, blendMode);
|
parent.DrawRGBAQuad(vertices, blendMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||||
@@ -66,13 +64,11 @@ namespace OpenRA.Graphics
|
|||||||
var b = color.B / 255.0f;
|
var b = color.B / 255.0f;
|
||||||
var a = color.A / 255.0f;
|
var a = color.A / 255.0f;
|
||||||
|
|
||||||
vertices[0] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
vertices[0] = new Vertex(start - corner + Offset, r, g, b, a, 0);
|
||||||
vertices[1] = new Vertex(start + corner + Offset, r, g, b, a, 0, 0);
|
vertices[1] = new Vertex(start + corner + Offset, r, g, b, a, 0);
|
||||||
vertices[2] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
vertices[2] = new Vertex(end + corner + Offset, r, g, b, a, 0);
|
||||||
vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
vertices[3] = new Vertex(end - corner + Offset, r, g, b, a, 0);
|
||||||
vertices[4] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0);
|
parent.DrawRGBAQuad(vertices, blendMode);
|
||||||
vertices[5] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
|
||||||
parent.DrawRGBAVertices(vertices, blendMode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -157,13 +153,11 @@ namespace OpenRA.Graphics
|
|||||||
var cd = closed || i < limit - 1 ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
var cd = closed || i < limit - 1 ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
||||||
|
|
||||||
// Fill segment
|
// Fill segment
|
||||||
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0);
|
||||||
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0, 0);
|
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0);
|
||||||
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0);
|
||||||
vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
vertices[3] = new Vertex(cd + Offset, r, g, b, a, 0);
|
||||||
vertices[4] = new Vertex(cd + Offset, r, g, b, a, 0, 0);
|
parent.DrawRGBAQuad(vertices, blendMode);
|
||||||
vertices[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
|
||||||
parent.DrawRGBAVertices(vertices, blendMode);
|
|
||||||
|
|
||||||
// Advance line segment
|
// Advance line segment
|
||||||
end = next;
|
end = next;
|
||||||
@@ -200,20 +194,6 @@ namespace OpenRA.Graphics
|
|||||||
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
|
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color, BlendMode blendMode = BlendMode.Alpha)
|
|
||||||
{
|
|
||||||
color = Util.PremultiplyAlpha(color);
|
|
||||||
var cr = color.R / 255.0f;
|
|
||||||
var cg = color.G / 255.0f;
|
|
||||||
var cb = color.B / 255.0f;
|
|
||||||
var ca = color.A / 255.0f;
|
|
||||||
|
|
||||||
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
|
||||||
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
|
|
||||||
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
|
||||||
parent.DrawRGBAVertices(vertices, blendMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||||
{
|
{
|
||||||
var tr = new float3(br.X, tl.Y, tl.Z);
|
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||||
@@ -229,25 +209,22 @@ namespace OpenRA.Graphics
|
|||||||
var cb = color.B / 255.0f;
|
var cb = color.B / 255.0f;
|
||||||
var ca = color.A / 255.0f;
|
var ca = color.A / 255.0f;
|
||||||
|
|
||||||
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0);
|
||||||
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
|
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0);
|
||||||
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0);
|
||||||
vertices[3] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
vertices[3] = new Vertex(d + Offset, cr, cg, cb, ca, 0);
|
||||||
vertices[4] = new Vertex(d + Offset, cr, cg, cb, ca, 0, 0);
|
parent.DrawRGBAQuad(vertices, blendMode);
|
||||||
vertices[5] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
|
||||||
parent.DrawRGBAVertices(vertices, blendMode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor, BlendMode blendMode = BlendMode.Alpha)
|
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d,
|
||||||
|
Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor, BlendMode blendMode = BlendMode.Alpha)
|
||||||
{
|
{
|
||||||
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
||||||
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
||||||
vertices[2] = VertexWithColor(c + Offset, bottomRightColor);
|
vertices[2] = VertexWithColor(c + Offset, bottomRightColor);
|
||||||
vertices[3] = VertexWithColor(c + Offset, bottomRightColor);
|
vertices[3] = VertexWithColor(d + Offset, bottomLeftColor);
|
||||||
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
|
|
||||||
vertices[5] = VertexWithColor(a + Offset, topLeftColor);
|
|
||||||
|
|
||||||
parent.DrawRGBAVertices(vertices, blendMode);
|
parent.DrawRGBAQuad(vertices, blendMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Vertex VertexWithColor(in float3 xyz, Color color)
|
static Vertex VertexWithColor(in float3 xyz, Color color)
|
||||||
@@ -258,7 +235,7 @@ namespace OpenRA.Graphics
|
|||||||
var cb = color.B / 255.0f;
|
var cb = color.B / 255.0f;
|
||||||
var ca = color.A / 255.0f;
|
var ca = color.A / 255.0f;
|
||||||
|
|
||||||
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
|
return new Vertex(xyz, cr, cg, cb, ca, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillEllipse(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
public void FillEllipse(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ namespace OpenRA.Graphics
|
|||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
// Nodes starting with ^ are inheritable but never loaded directly
|
// Nodes starting with ^ are inheritable but never loaded directly
|
||||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
||||||
|
|||||||
70
OpenRA.Game/Graphics/ShaderBindings.cs
Normal file
70
OpenRA.Game/Graphics/ShaderBindings.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright (c) The OpenRA Developers and Contributors
|
||||||
|
* 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace OpenRA.Graphics
|
||||||
|
{
|
||||||
|
public enum ShaderVertexAttributeType
|
||||||
|
{
|
||||||
|
// Assign the underlying OpenGL type values
|
||||||
|
// to simplify enum use in the shader
|
||||||
|
Float = 0x1406, // GL_FLOAT
|
||||||
|
Int = 0x1404, // GL_INT
|
||||||
|
UInt = 0x1405 // GL_UNSIGNED_INT
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct ShaderVertexAttribute
|
||||||
|
{
|
||||||
|
public readonly string Name;
|
||||||
|
public readonly ShaderVertexAttributeType Type;
|
||||||
|
public readonly int Components;
|
||||||
|
public readonly int Offset;
|
||||||
|
|
||||||
|
public ShaderVertexAttribute(string name, ShaderVertexAttributeType type, int components, int offset)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
Components = components;
|
||||||
|
Offset = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ShaderBindings : IShaderBindings
|
||||||
|
{
|
||||||
|
public string VertexShaderName { get; }
|
||||||
|
public string VertexShaderCode { get; }
|
||||||
|
public string FragmentShaderName { get; }
|
||||||
|
public string FragmentShaderCode { get; }
|
||||||
|
public int Stride { get; }
|
||||||
|
|
||||||
|
public abstract ShaderVertexAttribute[] Attributes { get; }
|
||||||
|
|
||||||
|
protected ShaderBindings(string name)
|
||||||
|
: this(name, name) { }
|
||||||
|
|
||||||
|
protected ShaderBindings(string vertexName, string fragmentName)
|
||||||
|
{
|
||||||
|
Stride = Attributes.Sum(a => a.Components * 4);
|
||||||
|
VertexShaderName = vertexName;
|
||||||
|
VertexShaderCode = GetShaderCode(VertexShaderName + ".vert");
|
||||||
|
FragmentShaderName = fragmentName;
|
||||||
|
FragmentShaderCode = GetShaderCode(FragmentShaderName + ".frag");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetShaderCode(string filename)
|
||||||
|
{
|
||||||
|
var filepath = Path.Combine(Platform.EngineDir, "glsl", filename);
|
||||||
|
return File.ReadAllText(filepath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -132,6 +132,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
if (!Buffered)
|
if (!Buffered)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
releaseBufferOnCommit = true;
|
releaseBufferOnCommit = true;
|
||||||
|
|
||||||
@@ -140,6 +141,29 @@ namespace OpenRA.Graphics
|
|||||||
GetTexture();
|
GetTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ReleaseBufferAndTryTransferTo(Sheet destination)
|
||||||
|
{
|
||||||
|
if (Size != destination.Size)
|
||||||
|
throw new ArgumentException("Destination sheet does not have the same size", nameof(destination));
|
||||||
|
|
||||||
|
var buffer = data;
|
||||||
|
ReleaseBuffer();
|
||||||
|
|
||||||
|
// We aren't commiting data to the GPU, so let's not delete our data.
|
||||||
|
if (Game.Renderer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Only transfer if the destination has no data that would be lost by overwriting.
|
||||||
|
if (buffer != null && destination.data == null && destination.texture == null)
|
||||||
|
{
|
||||||
|
Array.Clear(buffer, 0, buffer.Length);
|
||||||
|
destination.data = buffer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
texture?.Dispose();
|
texture?.Dispose();
|
||||||
|
|||||||
@@ -82,16 +82,16 @@ namespace OpenRA.Graphics
|
|||||||
this.margin = margin;
|
this.margin = margin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); }
|
public Sprite Add(ISpriteFrame frame, bool premultiplied = false) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset, premultiplied); }
|
||||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size) { return Add(src, type, size, 0, float3.Zero); }
|
public Sprite Add(byte[] src, SpriteFrameType type, Size size, bool premultiplied = false) { return Add(src, type, size, 0, float3.Zero, premultiplied); }
|
||||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset)
|
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset, bool premultiplied = false)
|
||||||
{
|
{
|
||||||
// Don't bother allocating empty sprites
|
// Don't bother allocating empty sprites
|
||||||
if (size.Width == 0 || size.Height == 0)
|
if (size.Width == 0 || size.Height == 0)
|
||||||
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
||||||
|
|
||||||
var rect = Allocate(size, zRamp, spriteOffset);
|
var rect = Allocate(size, zRamp, spriteOffset);
|
||||||
Util.FastCopyIntoChannel(rect, src, type);
|
Util.FastCopyIntoChannel(rect, src, type, premultiplied);
|
||||||
Current.CommitBufferedData();
|
Current.CommitBufferedData();
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
@@ -130,8 +130,13 @@ namespace OpenRA.Graphics
|
|||||||
var next = NextChannel(CurrentChannel);
|
var next = NextChannel(CurrentChannel);
|
||||||
if (next == null)
|
if (next == null)
|
||||||
{
|
{
|
||||||
Current.ReleaseBuffer();
|
var previous = Current;
|
||||||
Current = allocateSheet();
|
Current = allocateSheet();
|
||||||
|
|
||||||
|
// Reuse the backing buffer between sheets where possible.
|
||||||
|
// This avoids allocating additional buffers which the GC must clean up.
|
||||||
|
previous.ReleaseBufferAndTryTransferTo(Current);
|
||||||
|
|
||||||
sheets.Add(Current);
|
sheets.Add(Current);
|
||||||
CurrentChannel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
CurrentChannel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||||
}
|
}
|
||||||
@@ -142,7 +147,9 @@ namespace OpenRA.Graphics
|
|||||||
p = int2.Zero;
|
p = int2.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rect = new Sprite(Current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height), zRamp, spriteOffset, CurrentChannel, BlendMode.Alpha, scale);
|
var rect = new Sprite(
|
||||||
|
Current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height),
|
||||||
|
zRamp, spriteOffset, CurrentChannel, BlendMode.Alpha, scale);
|
||||||
p += new int2(imageSize.Width + margin, 0);
|
p += new int2(imageSize.Width + margin, 0);
|
||||||
|
|
||||||
return rect;
|
return rect;
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ namespace OpenRA.Graphics
|
|||||||
// in rendering a line of texels that sample outside the sprite rectangle.
|
// in rendering a line of texels that sample outside the sprite rectangle.
|
||||||
// Insetting the texture coordinates by a small fraction of a pixel avoids this
|
// Insetting the texture coordinates by a small fraction of a pixel avoids this
|
||||||
// with negligible impact on the 1:1 rendering case.
|
// with negligible impact on the 1:1 rendering case.
|
||||||
var inset = 1 / 128f;
|
const float Inset = 1 / 128f;
|
||||||
Left = (Math.Min(bounds.Left, bounds.Right) + inset) / sheet.Size.Width;
|
Left = (Math.Min(bounds.Left, bounds.Right) + Inset) / sheet.Size.Width;
|
||||||
Top = (Math.Min(bounds.Top, bounds.Bottom) + inset) / sheet.Size.Height;
|
Top = (Math.Min(bounds.Top, bounds.Bottom) + Inset) / sheet.Size.Height;
|
||||||
Right = (Math.Max(bounds.Left, bounds.Right) - inset) / sheet.Size.Width;
|
Right = (Math.Max(bounds.Left, bounds.Right) - Inset) / sheet.Size.Width;
|
||||||
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - inset) / sheet.Size.Height;
|
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - Inset) / sheet.Size.Height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,28 +14,30 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Primitives;
|
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
|
public delegate ISpriteFrame AdjustFrame(ISpriteFrame input, int index, int total);
|
||||||
|
|
||||||
public sealed class SpriteCache : IDisposable
|
public sealed class SpriteCache : IDisposable
|
||||||
{
|
{
|
||||||
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
|
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
|
||||||
readonly ISpriteLoader[] loaders;
|
readonly ISpriteLoader[] loaders;
|
||||||
readonly IReadOnlyFileSystem fileSystem;
|
readonly IReadOnlyFileSystem fileSystem;
|
||||||
|
|
||||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> spriteReservations = new();
|
readonly Dictionary<
|
||||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> frameReservations = new();
|
int,
|
||||||
|
(int[] Frames, MiniYamlNode.SourceLocation Location, AdjustFrame AdjustFrame, bool Premultiplied)> spriteReservations = new();
|
||||||
readonly Dictionary<string, List<int>> reservationsByFilename = new();
|
readonly Dictionary<string, List<int>> reservationsByFilename = new();
|
||||||
|
|
||||||
readonly Dictionary<int, ISpriteFrame[]> resolvedFrames = new();
|
|
||||||
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
|
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
|
||||||
|
|
||||||
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
|
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
|
||||||
|
|
||||||
int nextReservationToken = 1;
|
int nextReservationToken = 1;
|
||||||
|
|
||||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int bgraSheetSize, int indexedSheetSize, int bgraSheetMargin = 1, int indexedSheetMargin = 1)
|
public SpriteCache(
|
||||||
|
IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int bgraSheetSize, int indexedSheetSize, int bgraSheetMargin = 1, int indexedSheetMargin = 1)
|
||||||
{
|
{
|
||||||
SheetBuilders = new Dictionary<SheetType, SheetBuilder>
|
SheetBuilders = new Dictionary<SheetType, SheetBuilder>
|
||||||
{
|
{
|
||||||
@@ -47,97 +49,110 @@ namespace OpenRA.Graphics
|
|||||||
this.loaders = loaders;
|
this.loaders = loaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location,
|
||||||
|
AdjustFrame adjustFrame = null, bool premultiplied = false)
|
||||||
{
|
{
|
||||||
var token = nextReservationToken++;
|
var token = nextReservationToken++;
|
||||||
spriteReservations[token] = (frames?.ToArray(), location);
|
spriteReservations[token] = (frames?.ToArray(), location, adjustFrame, premultiplied);
|
||||||
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ReserveFrames(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders)
|
||||||
{
|
{
|
||||||
var token = nextReservationToken++;
|
|
||||||
frameReservations[token] = (frames?.ToArray(), location);
|
|
||||||
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders, out TypeDictionary metadata)
|
|
||||||
{
|
|
||||||
metadata = null;
|
|
||||||
if (!fileSystem.TryOpen(filename, out var stream))
|
if (!fileSystem.TryOpen(filename, out var stream))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
using (stream)
|
using (stream)
|
||||||
{
|
{
|
||||||
foreach (var loader in loaders)
|
foreach (var loader in loaders)
|
||||||
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
|
if (loader.TryParseSprite(stream, filename, out var frames, out _))
|
||||||
return frames;
|
return frames;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ISpriteFrame[] LoadFramesUncached(string filename)
|
||||||
|
{
|
||||||
|
return GetFrames(fileSystem, filename, loaders);
|
||||||
|
}
|
||||||
|
|
||||||
public void LoadReservations(ModData modData)
|
public void LoadReservations(ModData modData)
|
||||||
{
|
{
|
||||||
foreach (var sb in SheetBuilders.Values)
|
var pendingResolve = new List<(
|
||||||
sb.Current.CreateBuffer();
|
string Filename,
|
||||||
|
int FrameIndex,
|
||||||
var spriteCache = new Dictionary<int, Sprite>();
|
bool Premultiplied,
|
||||||
|
AdjustFrame AdjustFrame,
|
||||||
|
ISpriteFrame Frame,
|
||||||
|
Sprite[] SpritesForToken)>();
|
||||||
foreach (var (filename, tokens) in reservationsByFilename)
|
foreach (var (filename, tokens) in reservationsByFilename)
|
||||||
{
|
{
|
||||||
modData.LoadScreen?.Display();
|
modData.LoadScreen?.Display();
|
||||||
var loadedFrames = GetFrames(fileSystem, filename, loaders, out _);
|
var loadedFrames = GetFrames(fileSystem, filename, loaders);
|
||||||
foreach (var token in tokens)
|
foreach (var token in tokens)
|
||||||
{
|
{
|
||||||
if (frameReservations.TryGetValue(token, out var r))
|
if (spriteReservations.TryGetValue(token, out var rs))
|
||||||
{
|
|
||||||
if (loadedFrames != null)
|
|
||||||
{
|
|
||||||
if (r.Frames != null)
|
|
||||||
{
|
|
||||||
var resolved = new ISpriteFrame[loadedFrames.Length];
|
|
||||||
foreach (var i in r.Frames)
|
|
||||||
resolved[i] = loadedFrames[i];
|
|
||||||
resolvedFrames[token] = resolved;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
resolvedFrames[token] = loadedFrames;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
resolvedFrames[token] = null;
|
|
||||||
missingFiles[token] = (filename, r.Location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spriteReservations.TryGetValue(token, out r))
|
|
||||||
{
|
{
|
||||||
if (loadedFrames != null)
|
if (loadedFrames != null)
|
||||||
{
|
{
|
||||||
var resolved = new Sprite[loadedFrames.Length];
|
var resolved = new Sprite[loadedFrames.Length];
|
||||||
var frames = r.Frames ?? Enumerable.Range(0, loadedFrames.Length);
|
|
||||||
foreach (var i in frames)
|
|
||||||
resolved[i] = spriteCache.GetOrAdd(i,
|
|
||||||
f => SheetBuilders[SheetBuilder.FrameTypeToSheetType(loadedFrames[f].Type)].Add(loadedFrames[f]));
|
|
||||||
|
|
||||||
resolvedSprites[token] = resolved;
|
resolvedSprites[token] = resolved;
|
||||||
|
if (rs.Frames != null && rs.Frames.Any(i => i >= loadedFrames.Length))
|
||||||
|
throw new InvalidOperationException($"{rs.Location}: {filename} does not contain frames: " +
|
||||||
|
string.Join(',', rs.Frames.Where(f => f >= loadedFrames.Length)));
|
||||||
|
|
||||||
|
var frames = rs.Frames ?? Enumerable.Range(0, loadedFrames.Length);
|
||||||
|
var total = rs.Frames?.Length ?? loadedFrames.Length;
|
||||||
|
|
||||||
|
var j = 0;
|
||||||
|
foreach (var i in frames)
|
||||||
|
{
|
||||||
|
var frame = loadedFrames[i];
|
||||||
|
if (rs.AdjustFrame != null)
|
||||||
|
frame = rs.AdjustFrame(frame, j++, total);
|
||||||
|
pendingResolve.Add((filename, i, rs.Premultiplied, rs.AdjustFrame, frame, resolved));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
resolvedSprites[token] = null;
|
resolvedSprites[token] = null;
|
||||||
missingFiles[token] = (filename, r.Location);
|
missingFiles[token] = (filename, rs.Location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spriteCache.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spriteReservations.Clear();
|
spriteReservations.Clear();
|
||||||
frameReservations.Clear();
|
spriteReservations.TrimExcess();
|
||||||
reservationsByFilename.Clear();
|
reservationsByFilename.Clear();
|
||||||
|
reservationsByFilename.TrimExcess();
|
||||||
|
|
||||||
|
// When the sheet builder is adding sprites, it reserves height for the tallest sprite seen along the row.
|
||||||
|
// We can achieve better sheet packing by keeping sprites with similar heights together.
|
||||||
|
var orderedPendingResolve = pendingResolve.OrderBy(x => x.Frame.Size.Height);
|
||||||
|
|
||||||
|
var spriteCache = new Dictionary<(
|
||||||
|
string Filename,
|
||||||
|
int FrameIndex,
|
||||||
|
bool Premultiplied,
|
||||||
|
AdjustFrame AdjustFrame),
|
||||||
|
Sprite>(pendingResolve.Count);
|
||||||
|
foreach (var (filename, frameIndex, premultiplied, adjustFrame, frame, spritesForToken) in orderedPendingResolve)
|
||||||
|
{
|
||||||
|
// Premultiplied and non-premultiplied sprites must be cached separately
|
||||||
|
// to cover the case where the same image is requested in both versions.
|
||||||
|
spritesForToken[frameIndex] = spriteCache.GetOrAdd(
|
||||||
|
(filename, frameIndex, premultiplied, adjustFrame),
|
||||||
|
_ =>
|
||||||
|
{
|
||||||
|
var sheetBuilder = SheetBuilders[SheetBuilder.FrameTypeToSheetType(frame.Type)];
|
||||||
|
return sheetBuilder.Add(frame, premultiplied);
|
||||||
|
});
|
||||||
|
|
||||||
|
modData.LoadScreen?.Display();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var sb in SheetBuilders.Values)
|
foreach (var sb in SheetBuilders.Values)
|
||||||
sb.Current.ReleaseBuffer();
|
sb.Current.ReleaseBuffer();
|
||||||
@@ -145,18 +160,11 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public Sprite[] ResolveSprites(int token)
|
public Sprite[] ResolveSprites(int token)
|
||||||
{
|
{
|
||||||
var resolved = resolvedSprites[token];
|
if (!resolvedSprites.Remove(token, out var resolved))
|
||||||
resolvedSprites.Remove(token);
|
throw new InvalidOperationException($"{nameof(token)} {token} has either already been resolved, or was never reserved via {nameof(ReserveSprites)}");
|
||||||
if (missingFiles.TryGetValue(token, out var r))
|
|
||||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
|
||||||
|
|
||||||
return resolved;
|
resolvedSprites.TrimExcess();
|
||||||
}
|
|
||||||
|
|
||||||
public ISpriteFrame[] ResolveFrames(int token)
|
|
||||||
{
|
|
||||||
var resolved = resolvedFrames[token];
|
|
||||||
resolvedFrames.Remove(token);
|
|
||||||
if (missingFiles.TryGetValue(token, out var r))
|
if (missingFiles.TryGetValue(token, out var r))
|
||||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
float deviceScale;
|
float deviceScale;
|
||||||
|
|
||||||
public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
|
public SpriteFont(IPlatform platform, string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
|
||||||
{
|
{
|
||||||
if (builder.Type != SheetType.BGRA)
|
if (builder.Type != SheetType.BGRA)
|
||||||
throw new ArgumentException("The sheet builder must create BGRA sheets.", nameof(builder));
|
throw new ArgumentException("The sheet builder must create BGRA sheets.", nameof(builder));
|
||||||
@@ -36,7 +36,7 @@ namespace OpenRA.Graphics
|
|||||||
this.size = size;
|
this.size = size;
|
||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
|
|
||||||
font = Game.Renderer.CreateFont(data);
|
font = platform.CreateFont(data);
|
||||||
glyphs = new Cache<char, GlyphInfo>(CreateGlyph);
|
glyphs = new Cache<char, GlyphInfo>(CreateGlyph);
|
||||||
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
|
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
|
||||||
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
|
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
|
||||||
|
|||||||
@@ -22,20 +22,30 @@ namespace OpenRA.Graphics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum SpriteFrameType
|
public enum SpriteFrameType
|
||||||
{
|
{
|
||||||
// 8 bit index into an external palette
|
/// <summary>
|
||||||
|
/// 8 bit index into an external palette.
|
||||||
|
/// </summary>
|
||||||
Indexed8,
|
Indexed8,
|
||||||
|
|
||||||
// 32 bit color such as returned by Color.ToArgb() or the bmp file format
|
/// <summary>
|
||||||
// (remember that little-endian systems place the little bits in the first byte!)
|
/// 32 bit color such as returned by Color.ToArgb() or the bmp file format
|
||||||
|
/// (remember that little-endian systems place the little bits in the first byte).
|
||||||
|
/// </summary>
|
||||||
Bgra32,
|
Bgra32,
|
||||||
|
|
||||||
// Like BGRA, but without an alpha channel
|
/// <summary>
|
||||||
|
/// Like BGRA, but without an alpha channel.
|
||||||
|
/// </summary>
|
||||||
Bgr24,
|
Bgr24,
|
||||||
|
|
||||||
// 32 bit color in big-endian format, like png
|
/// <summary>
|
||||||
|
/// 32 bit color in big-endian format, like png.
|
||||||
|
/// </summary>
|
||||||
Rgba32,
|
Rgba32,
|
||||||
|
|
||||||
// Like RGBA, but without an alpha channel
|
/// <summary>
|
||||||
|
/// Like RGBA, but without an alpha channel.
|
||||||
|
/// </summary>
|
||||||
Rgb24
|
Rgb24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
@@ -19,6 +20,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
public const int SheetCount = 8;
|
public const int SheetCount = 8;
|
||||||
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
|
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
|
||||||
|
static readonly int UintSize = Marshal.SizeOf<uint>();
|
||||||
|
|
||||||
readonly Renderer renderer;
|
readonly Renderer renderer;
|
||||||
readonly IShader shader;
|
readonly IShader shader;
|
||||||
@@ -27,21 +29,21 @@ namespace OpenRA.Graphics
|
|||||||
readonly Sheet[] sheets = new Sheet[SheetCount];
|
readonly Sheet[] sheets = new Sheet[SheetCount];
|
||||||
|
|
||||||
BlendMode currentBlend = BlendMode.Alpha;
|
BlendMode currentBlend = BlendMode.Alpha;
|
||||||
int nv = 0;
|
int vertexCount = 0;
|
||||||
int ns = 0;
|
int sheetCount = 0;
|
||||||
|
|
||||||
public SpriteRenderer(Renderer renderer, IShader shader)
|
public SpriteRenderer(Renderer renderer, IShader shader)
|
||||||
{
|
{
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
this.shader = shader;
|
this.shader = shader;
|
||||||
vertices = renderer.Context.CreateVertices(renderer.TempBufferSize);
|
vertices = renderer.Context.CreateVertices<Vertex>(renderer.TempVertexBufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Flush()
|
public void Flush()
|
||||||
{
|
{
|
||||||
if (nv > 0)
|
if (vertexCount > 0)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < ns; i++)
|
for (var i = 0; i < sheetCount; i++)
|
||||||
{
|
{
|
||||||
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
|
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
|
||||||
sheets[i] = null;
|
sheets[i] = null;
|
||||||
@@ -50,12 +52,11 @@ namespace OpenRA.Graphics
|
|||||||
renderer.Context.SetBlendMode(currentBlend);
|
renderer.Context.SetBlendMode(currentBlend);
|
||||||
shader.PrepareRender();
|
shader.PrepareRender();
|
||||||
|
|
||||||
// PERF: The renderer may choose to replace vertices with a different temporary buffer.
|
renderer.DrawQuadBatch(ref vertices, shader, vertexCount);
|
||||||
renderer.DrawBatch(ref vertices, nv, PrimitiveType.TriangleList);
|
|
||||||
renderer.Context.SetBlendMode(BlendMode.None);
|
renderer.Context.SetBlendMode(BlendMode.None);
|
||||||
|
|
||||||
nv = 0;
|
vertexCount = 0;
|
||||||
ns = 0;
|
sheetCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
renderer.CurrentBatchRenderer = this;
|
renderer.CurrentBatchRenderer = this;
|
||||||
|
|
||||||
if (s.BlendMode != currentBlend || nv + 6 > renderer.TempBufferSize)
|
if (s.BlendMode != currentBlend || vertexCount + 4 > renderer.TempVertexBufferSize)
|
||||||
Flush();
|
Flush();
|
||||||
|
|
||||||
currentBlend = s.BlendMode;
|
currentBlend = s.BlendMode;
|
||||||
@@ -71,7 +72,7 @@ namespace OpenRA.Graphics
|
|||||||
// Check if the sheet (or secondary data sheet) have already been mapped
|
// Check if the sheet (or secondary data sheet) have already been mapped
|
||||||
var sheet = s.Sheet;
|
var sheet = s.Sheet;
|
||||||
var sheetIndex = 0;
|
var sheetIndex = 0;
|
||||||
for (; sheetIndex < ns; sheetIndex++)
|
for (; sheetIndex < sheetCount; sheetIndex++)
|
||||||
if (sheets[sheetIndex] == sheet)
|
if (sheets[sheetIndex] == sheet)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ namespace OpenRA.Graphics
|
|||||||
if (ss != null)
|
if (ss != null)
|
||||||
{
|
{
|
||||||
var secondarySheet = ss.SecondarySheet;
|
var secondarySheet = ss.SecondarySheet;
|
||||||
for (; secondarySheetIndex < ns; secondarySheetIndex++)
|
for (; secondarySheetIndex < sheetCount; secondarySheetIndex++)
|
||||||
if (sheets[secondarySheetIndex] == secondarySheet)
|
if (sheets[secondarySheetIndex] == secondarySheet)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -99,22 +100,22 @@ namespace OpenRA.Graphics
|
|||||||
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
|
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sheetIndex >= ns)
|
if (sheetIndex >= sheetCount)
|
||||||
{
|
{
|
||||||
sheets[sheetIndex] = sheet;
|
sheets[sheetIndex] = sheet;
|
||||||
ns++;
|
sheetCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secondarySheetIndex >= ns && ss != null)
|
if (secondarySheetIndex >= sheetCount && ss != null)
|
||||||
{
|
{
|
||||||
sheets[secondarySheetIndex] = ss.SecondarySheet;
|
sheets[secondarySheetIndex] = ss.SecondarySheet;
|
||||||
ns++;
|
sheetCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new int2(sheetIndex, secondarySheetIndex);
|
return new int2(sheetIndex, secondarySheetIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float ResolveTextureIndex(Sprite s, PaletteReference pal)
|
static int ResolveTextureIndex(Sprite s, PaletteReference pal)
|
||||||
{
|
{
|
||||||
if (pal == null)
|
if (pal == null)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -128,20 +129,20 @@ namespace OpenRA.Graphics
|
|||||||
return pal.TextureIndex;
|
return pal.TextureIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
|
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
|
||||||
{
|
{
|
||||||
var samplers = SetRenderStateForSprite(s);
|
var samplers = SetRenderStateForSprite(s);
|
||||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
|
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, vertexCount, scale * s.Size, float3.Ones,
|
||||||
1f, rotation);
|
1f, rotation);
|
||||||
nv += 6;
|
vertexCount += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
|
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
|
||||||
{
|
{
|
||||||
var samplers = SetRenderStateForSprite(s);
|
var samplers = SetRenderStateForSprite(s);
|
||||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
|
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, vertexCount, scale * s.Size, float3.Ones,
|
||||||
1f, rotation);
|
1f, rotation);
|
||||||
nv += 6;
|
vertexCount += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
|
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
|
||||||
@@ -149,13 +150,13 @@ namespace OpenRA.Graphics
|
|||||||
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, rotation);
|
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha,
|
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha,
|
||||||
float rotation = 0f)
|
float rotation = 0f)
|
||||||
{
|
{
|
||||||
var samplers = SetRenderStateForSprite(s);
|
var samplers = SetRenderStateForSprite(s);
|
||||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha,
|
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, vertexCount, scale * s.Size, tint, alpha,
|
||||||
rotation);
|
rotation);
|
||||||
nv += 6;
|
vertexCount += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
|
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
|
||||||
@@ -164,14 +165,14 @@ namespace OpenRA.Graphics
|
|||||||
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation);
|
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
||||||
{
|
{
|
||||||
var samplers = SetRenderStateForSprite(s);
|
var samplers = SetRenderStateForSprite(s);
|
||||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv);
|
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, vertexCount);
|
||||||
nv += 6;
|
vertexCount += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, IEnumerable<Sheet> sheets, BlendMode blendMode)
|
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, IIndexBuffer indices, int start, int length, IEnumerable<Sheet> sheets, BlendMode blendMode)
|
||||||
{
|
{
|
||||||
var i = 0;
|
var i = 0;
|
||||||
foreach (var s in sheets)
|
foreach (var s in sheets)
|
||||||
@@ -185,7 +186,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
renderer.Context.SetBlendMode(blendMode);
|
renderer.Context.SetBlendMode(blendMode);
|
||||||
shader.PrepareRender();
|
shader.PrepareRender();
|
||||||
renderer.DrawBatch(buffer, start, length, type);
|
renderer.DrawQuadBatch(buffer, indices, shader, length, UintSize * start);
|
||||||
renderer.Context.SetBlendMode(BlendMode.None);
|
renderer.Context.SetBlendMode(BlendMode.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,29 +197,32 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For RGBAColorRenderer
|
// For RGBAColorRenderer
|
||||||
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode)
|
internal void DrawRGBAQuad(Vertex[] v, BlendMode blendMode)
|
||||||
{
|
{
|
||||||
renderer.CurrentBatchRenderer = this;
|
renderer.CurrentBatchRenderer = this;
|
||||||
|
|
||||||
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize)
|
if (currentBlend != blendMode || vertexCount + 4 > renderer.TempVertexBufferSize)
|
||||||
Flush();
|
Flush();
|
||||||
|
|
||||||
currentBlend = blendMode;
|
currentBlend = blendMode;
|
||||||
Array.Copy(v, 0, vertices, nv, v.Length);
|
|
||||||
nv += v.Length;
|
Array.Copy(v, 0, vertices, vertexCount, v.Length);
|
||||||
|
vertexCount += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPalette(ITexture palette, ITexture colorShifts)
|
public void SetPalette(HardwarePalette palette)
|
||||||
{
|
{
|
||||||
shader.SetTexture("Palette", palette);
|
shader.SetTexture("Palette", palette.Texture);
|
||||||
shader.SetTexture("ColorShifts", colorShifts);
|
shader.SetTexture("ColorShifts", palette.ColorShifts);
|
||||||
|
shader.SetVec("PaletteRows", palette.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
|
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
|
||||||
{
|
{
|
||||||
// Calculate the scale (r1) and offset (r2) that convert from OpenRA viewport pixels
|
// OpenGL only renders x and y coordinates inside [-1, 1] range. We project world coordinates
|
||||||
// to OpenGL normalized device coordinates (NDC). OpenGL expects coordinates to vary from [-1, 1],
|
// using p1 to values [0, 2] and then subtract by 1 using p2, where p stands for projection. It's
|
||||||
// so we rescale viewport pixels to the range [0, 2] using r1 then subtract 1 using r2.
|
// standard practice for shaders to use a projection matrix, but as we project orthographically
|
||||||
|
// we are able to send less data to the GPU.
|
||||||
var width = 2f / (downscale * sheetSize.Width);
|
var width = 2f / (downscale * sheetSize.Width);
|
||||||
var height = 2f / (downscale * sheetSize.Height);
|
var height = 2f / (downscale * sheetSize.Height);
|
||||||
|
|
||||||
@@ -239,8 +243,8 @@ namespace OpenRA.Graphics
|
|||||||
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
|
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
|
||||||
shader.SetVec("DepthTextureScale", 128 * depth);
|
shader.SetVec("DepthTextureScale", 128 * depth);
|
||||||
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
|
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
|
||||||
shader.SetVec("r1", width, height, -depth);
|
shader.SetVec("p1", width, height, -depth);
|
||||||
shader.SetVec("r2", -1, -1, depthMargin != 0f ? 1 : 0);
|
shader.SetVec("p2", -1, -1, depthMargin != 0f ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDepthPreview(bool enabled, float contrast, float offset)
|
public void SetDepthPreview(bool enabled, float contrast, float offset)
|
||||||
@@ -249,9 +253,9 @@ namespace OpenRA.Graphics
|
|||||||
shader.SetVec("DepthPreviewParams", contrast, offset);
|
shader.SetVec("DepthPreviewParams", contrast, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAntialiasingPixelsPerTexel(float pxPerTx)
|
public void EnablePixelArtScaling(bool enabled)
|
||||||
{
|
{
|
||||||
shader.SetVec("AntialiasPixelsPerTexel", pxPerTx);
|
shader.SetBool("EnablePixelArtScaling", enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public sealed class TerrainSpriteLayer : IDisposable
|
public sealed class TerrainSpriteLayer : IDisposable
|
||||||
{
|
{
|
||||||
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
|
// PERF: we can reuse the IndexBuffer as all layers have the same size.
|
||||||
|
static readonly ConditionalWeakTable<World, IndexBufferRc> IndexBuffers = new();
|
||||||
|
readonly IndexBufferRc indexBufferWrapper;
|
||||||
|
|
||||||
public readonly BlendMode BlendMode;
|
public readonly BlendMode BlendMode;
|
||||||
|
|
||||||
@@ -28,7 +31,8 @@ namespace OpenRA.Graphics
|
|||||||
readonly Vertex[] vertices;
|
readonly Vertex[] vertices;
|
||||||
readonly bool[] ignoreTint;
|
readonly bool[] ignoreTint;
|
||||||
readonly HashSet<int> dirtyRows = new();
|
readonly HashSet<int> dirtyRows = new();
|
||||||
readonly int rowStride;
|
readonly int indexRowStride;
|
||||||
|
readonly int vertexRowStride;
|
||||||
readonly bool restrictToBounds;
|
readonly bool restrictToBounds;
|
||||||
|
|
||||||
readonly WorldRenderer worldRenderer;
|
readonly WorldRenderer worldRenderer;
|
||||||
@@ -43,19 +47,25 @@ namespace OpenRA.Graphics
|
|||||||
this.emptySprite = emptySprite;
|
this.emptySprite = emptySprite;
|
||||||
sheets = new Sheet[SpriteRenderer.SheetCount];
|
sheets = new Sheet[SpriteRenderer.SheetCount];
|
||||||
BlendMode = blendMode;
|
BlendMode = blendMode;
|
||||||
|
|
||||||
map = world.Map;
|
map = world.Map;
|
||||||
rowStride = 6 * map.MapSize.X;
|
|
||||||
|
|
||||||
vertices = new Vertex[rowStride * map.MapSize.Y];
|
vertexRowStride = 4 * map.MapSize.X;
|
||||||
|
vertices = new Vertex[vertexRowStride * map.MapSize.Y];
|
||||||
|
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer<Vertex>(vertices.Length);
|
||||||
|
|
||||||
|
indexRowStride = 6 * map.MapSize.X;
|
||||||
|
lock (IndexBuffers)
|
||||||
|
{
|
||||||
|
indexBufferWrapper = IndexBuffers.GetValue(world, world => new IndexBufferRc(world));
|
||||||
|
indexBufferWrapper.AddRef();
|
||||||
|
}
|
||||||
|
|
||||||
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
|
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
|
||||||
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
|
|
||||||
|
|
||||||
wr.PaletteInvalidated += UpdatePaletteIndices;
|
wr.PaletteInvalidated += UpdatePaletteIndices;
|
||||||
|
|
||||||
if (wr.TerrainLighting != null)
|
if (wr.TerrainLighting != null)
|
||||||
{
|
{
|
||||||
ignoreTint = new bool[rowStride * map.MapSize.Y];
|
ignoreTint = new bool[vertexRowStride * map.MapSize.Y];
|
||||||
wr.TerrainLighting.CellChanged += UpdateTint;
|
wr.TerrainLighting.CellChanged += UpdateTint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,8 +75,9 @@ namespace OpenRA.Graphics
|
|||||||
for (var i = 0; i < vertices.Length; i++)
|
for (var i = 0; i < vertices.Length; i++)
|
||||||
{
|
{
|
||||||
var v = vertices[i];
|
var v = vertices[i];
|
||||||
var p = palettes[i / 6]?.TextureIndex ?? 0;
|
var p = palettes[i / 4]?.TextureIndex ?? 0;
|
||||||
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, p, v.C, v.R, v.G, v.B, v.A);
|
var c = (uint)((p & 0xFFFF) << 16) | (v.C & 0xFFFF);
|
||||||
|
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, c, v.R, v.G, v.B, v.A);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var row = 0; row < map.MapSize.Y; row++)
|
for (var row = 0; row < map.MapSize.Y; row++)
|
||||||
@@ -97,13 +108,13 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
void UpdateTint(MPos uv)
|
void UpdateTint(MPos uv)
|
||||||
{
|
{
|
||||||
var offset = rowStride * uv.V + 6 * uv.U;
|
var offset = vertexRowStride * uv.V + 4 * uv.U;
|
||||||
if (ignoreTint[offset])
|
if (ignoreTint[offset])
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 6; i++)
|
for (var i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
var v = vertices[offset + 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, v.P, v.C, v.A * float3.Ones, v.A);
|
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.C, v.A * float3.Ones, v.A);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -125,10 +136,10 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
// Apply tint directly to the underlying vertices
|
// Apply tint directly to the underlying vertices
|
||||||
// This saves us from having to re-query the sprite information, which has not changed
|
// This saves us from having to re-query the sprite information, which has not changed
|
||||||
for (var i = 0; i < 6; i++)
|
for (var i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
var v = vertices[offset + 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, v.P, v.C, v.A * weights[CornerVertexMap[i]], v.A);
|
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.C, v.A * weights[i], v.A);
|
||||||
}
|
}
|
||||||
|
|
||||||
dirtyRows.Add(uv.V);
|
dirtyRows.Add(uv.V);
|
||||||
@@ -180,7 +191,7 @@ namespace OpenRA.Graphics
|
|||||||
if (!map.Tiles.Contains(uv))
|
if (!map.Tiles.Contains(uv))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var offset = rowStride * uv.V + 6 * uv.U;
|
var offset = vertexRowStride * uv.V + 4 * uv.U;
|
||||||
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha);
|
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha);
|
||||||
palettes[uv.V * map.MapSize.X + uv.U] = palette;
|
palettes[uv.V * map.MapSize.X + uv.U] = palette;
|
||||||
|
|
||||||
@@ -209,13 +220,13 @@ namespace OpenRA.Graphics
|
|||||||
if (!dirtyRows.Remove(row))
|
if (!dirtyRows.Remove(row))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var rowOffset = rowStride * row;
|
var rowOffset = vertexRowStride * row;
|
||||||
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
|
vertexBuffer.SetData(vertices, rowOffset, rowOffset, vertexRowStride);
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||||
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
|
vertexBuffer, indexBufferWrapper.Buffer, indexRowStride * firstRow,
|
||||||
PrimitiveType.TriangleList, sheets, BlendMode);
|
indexRowStride * (lastRow - firstRow), sheets, BlendMode);
|
||||||
|
|
||||||
Game.Renderer.Flush();
|
Game.Renderer.Flush();
|
||||||
}
|
}
|
||||||
@@ -227,6 +238,29 @@ namespace OpenRA.Graphics
|
|||||||
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
|
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
|
||||||
|
|
||||||
vertexBuffer.Dispose();
|
vertexBuffer.Dispose();
|
||||||
|
|
||||||
|
lock (IndexBuffers)
|
||||||
|
indexBufferWrapper.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class IndexBufferRc : IDisposable
|
||||||
|
{
|
||||||
|
public IIndexBuffer Buffer;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
public IndexBufferRc(World world)
|
||||||
|
{
|
||||||
|
Buffer = Game.Renderer.Context.CreateIndexBuffer(Util.CreateQuadIndices(world.Map.MapSize.X * world.Map.MapSize.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRef() { count++; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
count--;
|
||||||
|
if (count == 0)
|
||||||
|
Buffer.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ namespace OpenRA.Graphics
|
|||||||
readonly float alpha;
|
readonly float alpha;
|
||||||
readonly float rotation = 0f;
|
readonly float rotation = 0f;
|
||||||
|
|
||||||
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale = 1f, float alpha = 1f, float rotation = 0f)
|
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette,
|
||||||
|
float scale = 1f, float alpha = 1f, float rotation = 0f)
|
||||||
{
|
{
|
||||||
this.sprite = sprite;
|
this.sprite = sprite;
|
||||||
Pos = effectiveWorldPos;
|
Pos = effectiveWorldPos;
|
||||||
@@ -47,7 +48,8 @@ namespace OpenRA.Graphics
|
|||||||
public PaletteReference Palette { get; }
|
public PaletteReference Palette { get; }
|
||||||
public int ZOffset { get; }
|
public int ZOffset { get; }
|
||||||
|
|
||||||
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, Pos, screenPos, ZOffset, newPalette, scale, alpha, rotation); }
|
public IPalettedRenderable WithPalette(PaletteReference newPalette) =>
|
||||||
|
new UISpriteRenderable(sprite, Pos, screenPos, ZOffset, newPalette, scale, alpha, rotation);
|
||||||
public IRenderable WithZOffset(int newOffset) { return this; }
|
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||||
public IRenderable OffsetBy(in WVec vec) { return this; }
|
public IRenderable OffsetBy(in WVec vec) { return this; }
|
||||||
public IRenderable AsDecoration() { return this; }
|
public IRenderable AsDecoration() { return this; }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
@@ -20,7 +21,17 @@ namespace OpenRA.Graphics
|
|||||||
// yes, our channel order is nuts.
|
// yes, our channel order is nuts.
|
||||||
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
|
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
|
||||||
|
|
||||||
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv,
|
public static uint[] CreateQuadIndices(int quads)
|
||||||
|
{
|
||||||
|
var indices = new uint[quads * 6];
|
||||||
|
ReadOnlySpan<uint> cornerVertexMap = stackalloc uint[] { 0, 1, 2, 2, 3, 0 };
|
||||||
|
for (var i = 0; i < indices.Length; i++)
|
||||||
|
indices[i] = cornerVertexMap[i % 6] + (uint)(4 * (i / 6));
|
||||||
|
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, int paletteTextureIndex, int nv,
|
||||||
in float3 size, in float3 tint, float alpha, float rotation = 0f)
|
in float3 size, in float3 tint, float alpha, float rotation = 0f)
|
||||||
{
|
{
|
||||||
float3 a, b, c, d;
|
float3 a, b, c, d;
|
||||||
@@ -62,7 +73,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public static void FastCreateQuad(Vertex[] vertices,
|
public static void FastCreateQuad(Vertex[] vertices,
|
||||||
in float3 a, in float3 b, in float3 c, in float3 d,
|
in float3 a, in float3 b, in float3 c, in float3 d,
|
||||||
Sprite r, int2 samplers, float paletteTextureIndex,
|
Sprite r, int2 samplers, int paletteTextureIndex,
|
||||||
in float3 tint, float alpha, int nv)
|
in float3 tint, float alpha, int nv)
|
||||||
{
|
{
|
||||||
float sl = 0;
|
float sl = 0;
|
||||||
@@ -84,76 +95,33 @@ namespace OpenRA.Graphics
|
|||||||
attribC |= samplers.Y << 9;
|
attribC |= samplers.Y << 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fAttribC = (float)attribC;
|
attribC |= (paletteTextureIndex & 0xFFFF) << 16;
|
||||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
|
|
||||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint, alpha);
|
var uAttribC = (uint)attribC;
|
||||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, uAttribC, tint, alpha);
|
||||||
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, uAttribC, tint, alpha);
|
||||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, uAttribC, tint, alpha);
|
||||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
|
vertices[nv + 3] = new Vertex(d, r.Left, r.Bottom, sl, sb, uAttribC, tint, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType)
|
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType, bool premultiplied = false)
|
||||||
{
|
{
|
||||||
var destData = dest.Sheet.GetData();
|
var destData = dest.Sheet.GetData();
|
||||||
|
var stride = dest.Sheet.Size.Width;
|
||||||
|
var x = dest.Bounds.Left;
|
||||||
|
var y = dest.Bounds.Top;
|
||||||
var width = dest.Bounds.Width;
|
var width = dest.Bounds.Width;
|
||||||
var height = dest.Bounds.Height;
|
var height = dest.Bounds.Height;
|
||||||
|
|
||||||
if (dest.Channel == TextureChannel.RGBA)
|
if (dest.Channel == TextureChannel.RGBA)
|
||||||
{
|
{
|
||||||
var destStride = dest.Sheet.Size.Width;
|
CopyIntoRgba(src, srcType, premultiplied, destData, x, y, width, height, stride);
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
// Cast the data to an int array so we can copy the src data directly
|
|
||||||
fixed (byte* bd = &destData[0])
|
|
||||||
{
|
|
||||||
var data = (int*)bd;
|
|
||||||
var x = dest.Bounds.Left;
|
|
||||||
var y = dest.Bounds.Top;
|
|
||||||
|
|
||||||
var k = 0;
|
|
||||||
for (var j = 0; j < height; j++)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < width; i++)
|
|
||||||
{
|
|
||||||
byte r, g, b, a;
|
|
||||||
switch (srcType)
|
|
||||||
{
|
|
||||||
case SpriteFrameType.Bgra32:
|
|
||||||
case SpriteFrameType.Bgr24:
|
|
||||||
{
|
|
||||||
b = src[k++];
|
|
||||||
g = src[k++];
|
|
||||||
r = src[k++];
|
|
||||||
a = srcType == SpriteFrameType.Bgra32 ? src[k++] : (byte)255;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SpriteFrameType.Rgba32:
|
|
||||||
case SpriteFrameType.Rgb24:
|
|
||||||
{
|
|
||||||
r = src[k++];
|
|
||||||
g = src[k++];
|
|
||||||
b = src[k++];
|
|
||||||
a = srcType == SpriteFrameType.Rgba32 ? src[k++] : (byte)255;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var cc = Color.FromArgb(a, r, g, b);
|
|
||||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var destStride = dest.Sheet.Size.Width * 4;
|
// Copy into single channel of destination.
|
||||||
var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel];
|
var destStride = stride * 4;
|
||||||
|
var destOffset = destStride * y + x * 4 + ChannelMasks[(int)dest.Channel];
|
||||||
var destSkip = destStride - 4 * width;
|
var destSkip = destStride - 4 * width;
|
||||||
|
|
||||||
var srcOffset = 0;
|
var srcOffset = 0;
|
||||||
@@ -170,56 +138,119 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void CopyIntoRgba(
|
||||||
|
byte[] src, SpriteFrameType srcType, bool premultiplied, byte[] dest, int x, int y, int width, int height, int stride)
|
||||||
|
{
|
||||||
|
var si = 0;
|
||||||
|
var di = y * stride + x;
|
||||||
|
var d = MemoryMarshal.Cast<byte, uint>(dest);
|
||||||
|
|
||||||
|
// SpriteFrameType.Brga32 is a common source format, and it matches the destination format.
|
||||||
|
// Provide a fast past that just performs memory copies.
|
||||||
|
if (srcType == SpriteFrameType.Bgra32)
|
||||||
|
{
|
||||||
|
var s = MemoryMarshal.Cast<byte, uint>(src);
|
||||||
|
for (var h = 0; h < height; h++)
|
||||||
|
{
|
||||||
|
s[si..(si + width)].CopyTo(d[di..(di + width)]);
|
||||||
|
|
||||||
|
if (!premultiplied)
|
||||||
|
{
|
||||||
|
for (var w = 0; w < width; w++)
|
||||||
|
{
|
||||||
|
d[di] = PremultiplyAlpha(Color.FromArgb(d[di])).ToArgb();
|
||||||
|
di++;
|
||||||
|
}
|
||||||
|
|
||||||
|
di -= width;
|
||||||
|
}
|
||||||
|
|
||||||
|
si += width;
|
||||||
|
di += stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var h = 0; h < height; h++)
|
||||||
|
{
|
||||||
|
for (var w = 0; w < width; w++)
|
||||||
|
{
|
||||||
|
byte r, g, b, a;
|
||||||
|
switch (srcType)
|
||||||
|
{
|
||||||
|
case SpriteFrameType.Bgra32:
|
||||||
|
case SpriteFrameType.Bgr24:
|
||||||
|
b = src[si++];
|
||||||
|
g = src[si++];
|
||||||
|
r = src[si++];
|
||||||
|
a = srcType == SpriteFrameType.Bgra32 ? src[si++] : byte.MaxValue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SpriteFrameType.Rgba32:
|
||||||
|
case SpriteFrameType.Rgb24:
|
||||||
|
r = src[si++];
|
||||||
|
g = src[si++];
|
||||||
|
b = src[si++];
|
||||||
|
a = srcType == SpriteFrameType.Rgba32 ? src[si++] : byte.MaxValue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = Color.FromArgb(a, r, g, b);
|
||||||
|
if (!premultiplied)
|
||||||
|
c = PremultiplyAlpha(c);
|
||||||
|
d[di++] = c.ToArgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
di += stride - width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void FastCopyIntoSprite(Sprite dest, Png src)
|
public static void FastCopyIntoSprite(Sprite dest, Png src)
|
||||||
{
|
{
|
||||||
var destData = dest.Sheet.GetData();
|
var destData = dest.Sheet.GetData();
|
||||||
var destStride = dest.Sheet.Size.Width;
|
var stride = dest.Sheet.Size.Width;
|
||||||
|
var x = dest.Bounds.Left;
|
||||||
|
var y = dest.Bounds.Top;
|
||||||
var width = dest.Bounds.Width;
|
var width = dest.Bounds.Width;
|
||||||
var height = dest.Bounds.Height;
|
var height = dest.Bounds.Height;
|
||||||
|
|
||||||
unsafe
|
var si = 0;
|
||||||
|
var di = y * stride + x;
|
||||||
|
var d = MemoryMarshal.Cast<byte, uint>(destData);
|
||||||
|
|
||||||
|
for (var h = 0; h < height; h++)
|
||||||
{
|
{
|
||||||
// Cast the data to an int array so we can copy the src data directly
|
for (var w = 0; w < width; w++)
|
||||||
fixed (byte* bd = &destData[0])
|
|
||||||
{
|
{
|
||||||
var data = (int*)bd;
|
Color c;
|
||||||
var x = dest.Bounds.Left;
|
switch (src.Type)
|
||||||
var y = dest.Bounds.Top;
|
|
||||||
|
|
||||||
var k = 0;
|
|
||||||
for (var j = 0; j < height; j++)
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < width; i++)
|
case SpriteFrameType.Indexed8:
|
||||||
{
|
c = src.Palette[src.Data[si++]];
|
||||||
Color cc;
|
break;
|
||||||
switch (src.Type)
|
|
||||||
{
|
|
||||||
case SpriteFrameType.Indexed8:
|
|
||||||
{
|
|
||||||
cc = src.Palette[src.Data[k++]];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SpriteFrameType.Rgba32:
|
case SpriteFrameType.Rgba32:
|
||||||
case SpriteFrameType.Rgb24:
|
case SpriteFrameType.Rgb24:
|
||||||
{
|
var r = src.Data[si++];
|
||||||
var r = src.Data[k++];
|
var g = src.Data[si++];
|
||||||
var g = src.Data[k++];
|
var b = src.Data[si++];
|
||||||
var b = src.Data[k++];
|
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[si++] : byte.MaxValue;
|
||||||
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255;
|
c = Color.FromArgb(a, r, g, b);
|
||||||
cc = Color.FromArgb(a, r, g, b);
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pngs don't support BGR[A], so no need to include them here
|
// PNGs don't support BGR[A], so no need to include them here
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
|
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
|
||||||
}
|
|
||||||
|
|
||||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d[di++] = PremultiplyAlpha(c).ToArgb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
di += stride - width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,239 +336,5 @@ namespace OpenRA.Graphics
|
|||||||
(int)((byte)(t * a2 * c2.G + 0.5f) + (1 - t) * (byte)(a1 * c1.G + 0.5f)),
|
(int)((byte)(t * a2 * c2.G + 0.5f) + (1 - t) * (byte)(a1 * c1.G + 0.5f)),
|
||||||
(int)((byte)(t * a2 * c2.B + 0.5f) + (1 - t) * (byte)(a1 * c1.B + 0.5f))));
|
(int)((byte)(t * a2 * c2.B + 0.5f) + (1 - t) * (byte)(a1 * c1.B + 0.5f))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float[] IdentityMatrix()
|
|
||||||
{
|
|
||||||
return Exts.MakeArray(16, j => (j % 5 == 0) ? 1.0f : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] ScaleMatrix(float sx, float sy, float sz)
|
|
||||||
{
|
|
||||||
var mtx = IdentityMatrix();
|
|
||||||
mtx[0] = sx;
|
|
||||||
mtx[5] = sy;
|
|
||||||
mtx[10] = sz;
|
|
||||||
return mtx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] TranslationMatrix(float x, float y, float z)
|
|
||||||
{
|
|
||||||
var mtx = IdentityMatrix();
|
|
||||||
mtx[12] = x;
|
|
||||||
mtx[13] = y;
|
|
||||||
mtx[14] = z;
|
|
||||||
return mtx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] MatrixMultiply(float[] lhs, float[] rhs)
|
|
||||||
{
|
|
||||||
var mtx = new float[16];
|
|
||||||
for (var i = 0; i < 4; i++)
|
|
||||||
for (var j = 0; j < 4; j++)
|
|
||||||
{
|
|
||||||
mtx[4 * i + j] = 0;
|
|
||||||
for (var k = 0; k < 4; k++)
|
|
||||||
mtx[4 * i + j] += lhs[4 * k + j] * rhs[4 * i + k];
|
|
||||||
}
|
|
||||||
|
|
||||||
return mtx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] MatrixVectorMultiply(float[] mtx, float[] vec)
|
|
||||||
{
|
|
||||||
var ret = new float[4];
|
|
||||||
for (var j = 0; j < 4; j++)
|
|
||||||
{
|
|
||||||
ret[j] = 0;
|
|
||||||
for (var k = 0; k < 4; k++)
|
|
||||||
ret[j] += mtx[4 * k + j] * vec[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] MatrixInverse(float[] m)
|
|
||||||
{
|
|
||||||
var mtx = new float[16];
|
|
||||||
|
|
||||||
mtx[0] = m[5] * m[10] * m[15] -
|
|
||||||
m[5] * m[11] * m[14] -
|
|
||||||
m[9] * m[6] * m[15] +
|
|
||||||
m[9] * m[7] * m[14] +
|
|
||||||
m[13] * m[6] * m[11] -
|
|
||||||
m[13] * m[7] * m[10];
|
|
||||||
|
|
||||||
mtx[4] = -m[4] * m[10] * m[15] +
|
|
||||||
m[4] * m[11] * m[14] +
|
|
||||||
m[8] * m[6] * m[15] -
|
|
||||||
m[8] * m[7] * m[14] -
|
|
||||||
m[12] * m[6] * m[11] +
|
|
||||||
m[12] * m[7] * m[10];
|
|
||||||
|
|
||||||
mtx[8] = m[4] * m[9] * m[15] -
|
|
||||||
m[4] * m[11] * m[13] -
|
|
||||||
m[8] * m[5] * m[15] +
|
|
||||||
m[8] * m[7] * m[13] +
|
|
||||||
m[12] * m[5] * m[11] -
|
|
||||||
m[12] * m[7] * m[9];
|
|
||||||
|
|
||||||
mtx[12] = -m[4] * m[9] * m[14] +
|
|
||||||
m[4] * m[10] * m[13] +
|
|
||||||
m[8] * m[5] * m[14] -
|
|
||||||
m[8] * m[6] * m[13] -
|
|
||||||
m[12] * m[5] * m[10] +
|
|
||||||
m[12] * m[6] * m[9];
|
|
||||||
|
|
||||||
mtx[1] = -m[1] * m[10] * m[15] +
|
|
||||||
m[1] * m[11] * m[14] +
|
|
||||||
m[9] * m[2] * m[15] -
|
|
||||||
m[9] * m[3] * m[14] -
|
|
||||||
m[13] * m[2] * m[11] +
|
|
||||||
m[13] * m[3] * m[10];
|
|
||||||
|
|
||||||
mtx[5] = m[0] * m[10] * m[15] -
|
|
||||||
m[0] * m[11] * m[14] -
|
|
||||||
m[8] * m[2] * m[15] +
|
|
||||||
m[8] * m[3] * m[14] +
|
|
||||||
m[12] * m[2] * m[11] -
|
|
||||||
m[12] * m[3] * m[10];
|
|
||||||
|
|
||||||
mtx[9] = -m[0] * m[9] * m[15] +
|
|
||||||
m[0] * m[11] * m[13] +
|
|
||||||
m[8] * m[1] * m[15] -
|
|
||||||
m[8] * m[3] * m[13] -
|
|
||||||
m[12] * m[1] * m[11] +
|
|
||||||
m[12] * m[3] * m[9];
|
|
||||||
|
|
||||||
mtx[13] = m[0] * m[9] * m[14] -
|
|
||||||
m[0] * m[10] * m[13] -
|
|
||||||
m[8] * m[1] * m[14] +
|
|
||||||
m[8] * m[2] * m[13] +
|
|
||||||
m[12] * m[1] * m[10] -
|
|
||||||
m[12] * m[2] * m[9];
|
|
||||||
|
|
||||||
mtx[2] = m[1] * m[6] * m[15] -
|
|
||||||
m[1] * m[7] * m[14] -
|
|
||||||
m[5] * m[2] * m[15] +
|
|
||||||
m[5] * m[3] * m[14] +
|
|
||||||
m[13] * m[2] * m[7] -
|
|
||||||
m[13] * m[3] * m[6];
|
|
||||||
|
|
||||||
mtx[6] = -m[0] * m[6] * m[15] +
|
|
||||||
m[0] * m[7] * m[14] +
|
|
||||||
m[4] * m[2] * m[15] -
|
|
||||||
m[4] * m[3] * m[14] -
|
|
||||||
m[12] * m[2] * m[7] +
|
|
||||||
m[12] * m[3] * m[6];
|
|
||||||
|
|
||||||
mtx[10] = m[0] * m[5] * m[15] -
|
|
||||||
m[0] * m[7] * m[13] -
|
|
||||||
m[4] * m[1] * m[15] +
|
|
||||||
m[4] * m[3] * m[13] +
|
|
||||||
m[12] * m[1] * m[7] -
|
|
||||||
m[12] * m[3] * m[5];
|
|
||||||
|
|
||||||
mtx[14] = -m[0] * m[5] * m[14] +
|
|
||||||
m[0] * m[6] * m[13] +
|
|
||||||
m[4] * m[1] * m[14] -
|
|
||||||
m[4] * m[2] * m[13] -
|
|
||||||
m[12] * m[1] * m[6] +
|
|
||||||
m[12] * m[2] * m[5];
|
|
||||||
|
|
||||||
mtx[3] = -m[1] * m[6] * m[11] +
|
|
||||||
m[1] * m[7] * m[10] +
|
|
||||||
m[5] * m[2] * m[11] -
|
|
||||||
m[5] * m[3] * m[10] -
|
|
||||||
m[9] * m[2] * m[7] +
|
|
||||||
m[9] * m[3] * m[6];
|
|
||||||
|
|
||||||
mtx[7] = m[0] * m[6] * m[11] -
|
|
||||||
m[0] * m[7] * m[10] -
|
|
||||||
m[4] * m[2] * m[11] +
|
|
||||||
m[4] * m[3] * m[10] +
|
|
||||||
m[8] * m[2] * m[7] -
|
|
||||||
m[8] * m[3] * m[6];
|
|
||||||
|
|
||||||
mtx[11] = -m[0] * m[5] * m[11] +
|
|
||||||
m[0] * m[7] * m[9] +
|
|
||||||
m[4] * m[1] * m[11] -
|
|
||||||
m[4] * m[3] * m[9] -
|
|
||||||
m[8] * m[1] * m[7] +
|
|
||||||
m[8] * m[3] * m[5];
|
|
||||||
|
|
||||||
mtx[15] = m[0] * m[5] * m[10] -
|
|
||||||
m[0] * m[6] * m[9] -
|
|
||||||
m[4] * m[1] * m[10] +
|
|
||||||
m[4] * m[2] * m[9] +
|
|
||||||
m[8] * m[1] * m[6] -
|
|
||||||
m[8] * m[2] * m[5];
|
|
||||||
|
|
||||||
var det = m[0] * mtx[0] + m[1] * mtx[4] + m[2] * mtx[8] + m[3] * mtx[12];
|
|
||||||
if (det == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
for (var i = 0; i < 16; i++)
|
|
||||||
mtx[i] *= 1 / det;
|
|
||||||
|
|
||||||
return mtx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] MakeFloatMatrix(Int32Matrix4x4 imtx)
|
|
||||||
{
|
|
||||||
var multipler = 1f / imtx.M44;
|
|
||||||
return new[]
|
|
||||||
{
|
|
||||||
imtx.M11 * multipler,
|
|
||||||
imtx.M12 * multipler,
|
|
||||||
imtx.M13 * multipler,
|
|
||||||
imtx.M14 * multipler,
|
|
||||||
|
|
||||||
imtx.M21 * multipler,
|
|
||||||
imtx.M22 * multipler,
|
|
||||||
imtx.M23 * multipler,
|
|
||||||
imtx.M24 * multipler,
|
|
||||||
|
|
||||||
imtx.M31 * multipler,
|
|
||||||
imtx.M32 * multipler,
|
|
||||||
imtx.M33 * multipler,
|
|
||||||
imtx.M34 * multipler,
|
|
||||||
|
|
||||||
imtx.M41 * multipler,
|
|
||||||
imtx.M42 * multipler,
|
|
||||||
imtx.M43 * multipler,
|
|
||||||
imtx.M44 * multipler,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] MatrixAABBMultiply(float[] mtx, float[] bounds)
|
|
||||||
{
|
|
||||||
// Corner offsets
|
|
||||||
var ix = new uint[] { 0, 0, 0, 0, 3, 3, 3, 3 };
|
|
||||||
var iy = new uint[] { 1, 1, 4, 4, 1, 1, 4, 4 };
|
|
||||||
var iz = new uint[] { 2, 5, 2, 5, 2, 5, 2, 5 };
|
|
||||||
|
|
||||||
// Vectors to opposing corner
|
|
||||||
var ret = new[]
|
|
||||||
{
|
|
||||||
float.MaxValue, float.MaxValue, float.MaxValue,
|
|
||||||
float.MinValue, float.MinValue, float.MinValue
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transform vectors and find new bounding box
|
|
||||||
for (var i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
var vec = new[] { bounds[ix[i]], bounds[iy[i]], bounds[iz[i]], 1 };
|
|
||||||
var tvec = MatrixVectorMultiply(mtx, vec);
|
|
||||||
|
|
||||||
ret[0] = Math.Min(ret[0], tvec[0] / tvec[3]);
|
|
||||||
ret[1] = Math.Min(ret[1], tvec[1] / tvec[3]);
|
|
||||||
ret[2] = Math.Min(ret[2], tvec[2] / tvec[3]);
|
|
||||||
ret[3] = Math.Max(ret[3], tvec[0] / tvec[3]);
|
|
||||||
ret[4] = Math.Max(ret[4], tvec[1] / tvec[3]);
|
|
||||||
ret[5] = Math.Max(ret[5], tvec[2] / tvec[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,27 +23,42 @@ namespace OpenRA.Graphics
|
|||||||
public readonly float S, T, U, V;
|
public readonly float S, T, U, V;
|
||||||
|
|
||||||
// Palette and channel flags
|
// Palette and channel flags
|
||||||
public readonly float P, C;
|
public readonly uint C;
|
||||||
|
|
||||||
// Color tint
|
// Color tint
|
||||||
public readonly float R, G, B, A;
|
public readonly float R, G, B, A;
|
||||||
|
|
||||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
|
public Vertex(in float3 xyz, float s, float t, float u, float v, uint c)
|
||||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones, 1f) { }
|
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, c, float3.Ones, 1f) { }
|
||||||
|
|
||||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
|
public Vertex(in float3 xyz, float s, float t, float u, float v, uint c, in float3 tint, float a)
|
||||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
|
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, c, tint.X, tint.Y, tint.Z, a) { }
|
||||||
|
|
||||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
|
public Vertex(float x, float y, float z, float s, float t, float u, float v, uint c, in float3 tint, float a)
|
||||||
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
|
: this(x, y, z, s, t, u, v, c, tint.X, tint.Y, tint.Z, a) { }
|
||||||
|
|
||||||
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, float a)
|
public Vertex(float x, float y, float z, float s, float t, float u, float v, uint c, float r, float g, float b, float a)
|
||||||
{
|
{
|
||||||
X = x; Y = y; Z = z;
|
X = x; Y = y; Z = z;
|
||||||
S = s; T = t;
|
S = s; T = t;
|
||||||
U = u; V = v;
|
U = u; V = v;
|
||||||
P = p; C = c;
|
C = c;
|
||||||
R = r; G = g; B = b; A = a;
|
R = r; G = g; B = b; A = a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class CombinedShaderBindings : ShaderBindings
|
||||||
|
{
|
||||||
|
public CombinedShaderBindings()
|
||||||
|
: base("combined")
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public override ShaderVertexAttribute[] Attributes { get; } = new[]
|
||||||
|
{
|
||||||
|
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
|
||||||
|
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
|
||||||
|
new ShaderVertexAttribute("aVertexAttributes", ShaderVertexAttributeType.UInt, 1, 28),
|
||||||
|
new ShaderVertexAttribute("aVertexTint", ShaderVertexAttributeType.Float, 4, 32)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,9 @@ namespace OpenRA.Graphics
|
|||||||
MinZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
|
MinZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
MaxZoom = Math.Min(MinZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
|
MaxZoom = Math.Min(
|
||||||
|
MinZoom * viewportSizes.MaxZoomScale,
|
||||||
|
Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
|
||||||
|
|
||||||
if (unlockMinZoom)
|
if (unlockMinZoom)
|
||||||
{
|
{
|
||||||
@@ -231,11 +233,12 @@ namespace OpenRA.Graphics
|
|||||||
else
|
else
|
||||||
Zoom = Zoom.Clamp(MinZoom, MaxZoom);
|
Zoom = Zoom.Clamp(MinZoom, MaxZoom);
|
||||||
|
|
||||||
var maxSize = 1f / (unlockMinZoom ? unlockedMinZoom : MinZoom) * new float2(Game.Renderer.NativeResolution);
|
var minZoom = unlockMinZoom ? unlockedMinZoom : MinZoom;
|
||||||
|
var maxSize = 1f / minZoom * new float2(Game.Renderer.NativeResolution);
|
||||||
Game.Renderer.SetMaximumViewportSize(new Size((int)maxSize.X, (int)maxSize.Y));
|
Game.Renderer.SetMaximumViewportSize(new Size((int)maxSize.X, (int)maxSize.Y));
|
||||||
|
|
||||||
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
|
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
|
||||||
t.ViewportZoomExtentsChanged(MinZoom, MaxZoom);
|
t.ViewportZoomExtentsChanged(minZoom, MaxZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CPos ViewToWorld(int2 view)
|
public CPos ViewToWorld(int2 view)
|
||||||
@@ -282,11 +285,23 @@ namespace OpenRA.Graphics
|
|||||||
IEnumerable<MPos> CandidateMouseoverCells(int2 world)
|
IEnumerable<MPos> CandidateMouseoverCells(int2 world)
|
||||||
{
|
{
|
||||||
var map = worldRenderer.World.Map;
|
var map = worldRenderer.World.Map;
|
||||||
|
var tileScale = map.Grid.TileScale / 2;
|
||||||
var minPos = worldRenderer.ProjectedPosition(world);
|
var minPos = worldRenderer.ProjectedPosition(world);
|
||||||
|
|
||||||
// Find all the cells that could potentially have been clicked
|
// Find all the cells that could potentially have been clicked.
|
||||||
var a = map.CellContaining(minPos - new WVec(1024, 0, 0)).ToMPos(map.Grid.Type);
|
MPos a;
|
||||||
var b = map.CellContaining(minPos + new WVec(512, 512 * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
|
MPos b;
|
||||||
|
if (map.Grid.Type == MapGridType.RectangularIsometric)
|
||||||
|
{
|
||||||
|
// TODO: this generates too many cells.
|
||||||
|
a = map.CellContaining(minPos - new WVec(tileScale, 0, 0)).ToMPos(map.Grid.Type);
|
||||||
|
b = map.CellContaining(minPos + new WVec(tileScale, tileScale * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a = map.CellContaining(minPos).ToMPos(map.Grid.Type);
|
||||||
|
b = map.CellContaining(minPos + new WVec(0, tileScale * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
|
||||||
|
}
|
||||||
|
|
||||||
for (var v = b.V; v >= a.V; v--)
|
for (var v = b.V; v >= a.V; v--)
|
||||||
for (var u = b.U; u >= a.U; u--)
|
for (var u = b.U; u >= a.U; u--)
|
||||||
@@ -299,10 +314,13 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public void Center(IEnumerable<Actor> actors)
|
public void Center(IEnumerable<Actor> actors)
|
||||||
{
|
{
|
||||||
if (!actors.Any())
|
var actorsCollection = actors as IReadOnlyCollection<Actor>;
|
||||||
|
actorsCollection ??= actors.ToList();
|
||||||
|
|
||||||
|
if (actorsCollection.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Center(actors.Select(a => a.CenterPosition).Average());
|
Center(actorsCollection.Select(a => a.CenterPosition).Average());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Center(WPos pos)
|
public void Center(WPos pos)
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ namespace OpenRA.Graphics
|
|||||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
|
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
|
||||||
|
|
||||||
readonly List<IRenderable> renderablesBuffer = new();
|
readonly List<IRenderable> renderablesBuffer = new();
|
||||||
|
readonly IRenderer[] renderers;
|
||||||
|
readonly IRenderPostProcessPass[] postProcessPasses;
|
||||||
|
|
||||||
internal WorldRenderer(ModData modData, World world)
|
internal WorldRenderer(ModData modData, World world)
|
||||||
{
|
{
|
||||||
@@ -60,15 +62,29 @@ namespace OpenRA.Graphics
|
|||||||
foreach (var pal in world.TraitDict.ActorsWithTrait<ILoadsPalettes>())
|
foreach (var pal in world.TraitDict.ActorsWithTrait<ILoadsPalettes>())
|
||||||
pal.Trait.LoadPalettes(this);
|
pal.Trait.LoadPalettes(this);
|
||||||
|
|
||||||
foreach (var p in world.Players)
|
Player.SetupRelationshipColors(world.Players, world.LocalPlayer, this, true);
|
||||||
UpdatePalettesForPlayer(p.InternalName, p.Color, false);
|
|
||||||
|
|
||||||
palette.Initialize();
|
palette.Initialize();
|
||||||
|
|
||||||
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
||||||
|
renderers = world.WorldActor.TraitsImplementing<IRenderer>().ToArray();
|
||||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||||
|
|
||||||
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
||||||
|
|
||||||
|
postProcessPasses = world.WorldActor.TraitsImplementing<IRenderPostProcessPass>().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginFrame()
|
||||||
|
{
|
||||||
|
foreach (var r in renderers)
|
||||||
|
r.BeginFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndFrame()
|
||||||
|
{
|
||||||
|
foreach (var r in renderers)
|
||||||
|
r.EndFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdatePalettesForPlayer(string internalName, Color color, bool replaceExisting)
|
public void UpdatePalettesForPlayer(string internalName, Color color, bool replaceExisting)
|
||||||
@@ -270,6 +286,8 @@ namespace OpenRA.Graphics
|
|||||||
if (enableDepthBuffer)
|
if (enableDepthBuffer)
|
||||||
Game.Renderer.ClearDepthBuffer();
|
Game.Renderer.ClearDepthBuffer();
|
||||||
|
|
||||||
|
ApplyPostProcessing(PostProcessPassType.AfterActors);
|
||||||
|
|
||||||
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
|
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
|
||||||
{
|
{
|
||||||
if (actor.IsInWorld && !actor.Disposed)
|
if (actor.IsInWorld && !actor.Disposed)
|
||||||
@@ -279,6 +297,8 @@ namespace OpenRA.Graphics
|
|||||||
if (enableDepthBuffer)
|
if (enableDepthBuffer)
|
||||||
Game.Renderer.ClearDepthBuffer();
|
Game.Renderer.ClearDepthBuffer();
|
||||||
|
|
||||||
|
ApplyPostProcessing(PostProcessPassType.AfterWorld);
|
||||||
|
|
||||||
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
|
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
|
||||||
|
|
||||||
if (enableDepthBuffer)
|
if (enableDepthBuffer)
|
||||||
@@ -292,9 +312,23 @@ namespace OpenRA.Graphics
|
|||||||
foreach (var r in g)
|
foreach (var r in g)
|
||||||
r.Render(this);
|
r.Render(this);
|
||||||
|
|
||||||
|
ApplyPostProcessing(PostProcessPassType.AfterShroud);
|
||||||
|
|
||||||
Game.Renderer.Flush();
|
Game.Renderer.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplyPostProcessing(PostProcessPassType type)
|
||||||
|
{
|
||||||
|
foreach (var pass in postProcessPasses)
|
||||||
|
{
|
||||||
|
if (pass.Type != type || !pass.Enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Game.Renderer.Flush();
|
||||||
|
pass.Draw(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void DrawAnnotations()
|
public void DrawAnnotations()
|
||||||
{
|
{
|
||||||
Game.Renderer.EnableAntialiasingFilter();
|
Game.Renderer.EnableAntialiasingFilter();
|
||||||
|
|||||||
@@ -16,11 +16,19 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public sealed class HotkeyDefinition
|
public sealed class HotkeyDefinition
|
||||||
{
|
{
|
||||||
|
public const string ContextFluentPrefix = "hotkey-context";
|
||||||
|
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
public readonly Hotkey Default = Hotkey.Invalid;
|
public readonly Hotkey Default = Hotkey.Invalid;
|
||||||
|
|
||||||
|
[FluentReference]
|
||||||
public readonly string Description = "";
|
public readonly string Description = "";
|
||||||
|
|
||||||
public readonly HashSet<string> Types = new();
|
public readonly HashSet<string> Types = new();
|
||||||
|
|
||||||
|
[FluentReference]
|
||||||
public readonly HashSet<string> Contexts = new();
|
public readonly HashSet<string> Contexts = new();
|
||||||
|
|
||||||
public readonly bool Readonly = false;
|
public readonly bool Readonly = false;
|
||||||
public bool HasDuplicates { get; internal set; }
|
public bool HasDuplicates { get; internal set; }
|
||||||
|
|
||||||
@@ -31,29 +39,27 @@ namespace OpenRA
|
|||||||
if (!string.IsNullOrEmpty(node.Value))
|
if (!string.IsNullOrEmpty(node.Value))
|
||||||
Default = FieldLoader.GetValue<Hotkey>("value", node.Value);
|
Default = FieldLoader.GetValue<Hotkey>("value", node.Value);
|
||||||
|
|
||||||
var descriptionNode = node.Nodes.FirstOrDefault(n => n.Key == "Description");
|
var nodeDict = node.ToDictionary();
|
||||||
if (descriptionNode != null)
|
|
||||||
Description = descriptionNode.Value.Value;
|
|
||||||
|
|
||||||
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
|
if (nodeDict.TryGetValue("Description", out var descriptionYaml))
|
||||||
if (typesNode != null)
|
Description = descriptionYaml.Value;
|
||||||
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value);
|
|
||||||
|
|
||||||
var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts");
|
if (nodeDict.TryGetValue("Types", out var typesYaml))
|
||||||
if (contextsNode != null)
|
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesYaml.Value);
|
||||||
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextsNode.Value.Value);
|
|
||||||
|
|
||||||
var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform");
|
if (nodeDict.TryGetValue("Contexts", out var contextYaml))
|
||||||
if (platformNode != null)
|
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextYaml.Value)
|
||||||
|
.Select(c => ContextFluentPrefix + "." + c).ToHashSet();
|
||||||
|
|
||||||
|
if (nodeDict.TryGetValue("Platform", out var platformYaml))
|
||||||
{
|
{
|
||||||
var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString());
|
var platformOverride = platformYaml.NodeWithKeyOrDefault(Platform.CurrentPlatform.ToString());
|
||||||
if (platformOverride != null)
|
if (platformOverride != null)
|
||||||
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
|
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly");
|
if (nodeDict.TryGetValue("Readonly", out var readonlyYaml))
|
||||||
if (readonlyNode != null)
|
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyYaml.Value);
|
||||||
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyNode.Value.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,16 +91,16 @@ namespace OpenRA
|
|||||||
var ret = KeycodeExts.DisplayString(Key);
|
var ret = KeycodeExts.DisplayString(Key);
|
||||||
|
|
||||||
if (Modifiers.HasModifier(Modifiers.Shift))
|
if (Modifiers.HasModifier(Modifiers.Shift))
|
||||||
ret = "Shift + " + ret;
|
ret = $"{ModifiersExts.DisplayString(Modifiers.Shift)} + {ret}";
|
||||||
|
|
||||||
if (Modifiers.HasModifier(Modifiers.Alt))
|
if (Modifiers.HasModifier(Modifiers.Alt))
|
||||||
ret = "Alt + " + ret;
|
ret = $"{ModifiersExts.DisplayString(Modifiers.Alt)} + {ret}";
|
||||||
|
|
||||||
if (Modifiers.HasModifier(Modifiers.Ctrl))
|
if (Modifiers.HasModifier(Modifiers.Ctrl))
|
||||||
ret = "Ctrl + " + ret;
|
ret = $"{ModifiersExts.DisplayString(Modifiers.Ctrl)} + {ret}";
|
||||||
|
|
||||||
if (Modifiers.HasModifier(Modifiers.Meta))
|
if (Modifiers.HasModifier(Modifiers.Meta))
|
||||||
ret = (Platform.CurrentPlatform == PlatformType.OSX ? "Cmd + " : "Meta + ") + ret;
|
ret = $"{ModifiersExts.DisplayString(Modifiers.Meta)} + {ret}";
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
@@ -61,6 +62,33 @@ namespace OpenRA
|
|||||||
Meta = 8,
|
Meta = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ModifiersExts
|
||||||
|
{
|
||||||
|
[FluentReference]
|
||||||
|
public const string Cmd = "keycode-modifier.cmd";
|
||||||
|
|
||||||
|
[FluentReference(Traits.LintDictionaryReference.Values)]
|
||||||
|
public static readonly IReadOnlyDictionary<Modifiers, string> ModifierFluentKeys = new Dictionary<Modifiers, string>()
|
||||||
|
{
|
||||||
|
{ Modifiers.None, "keycode-modifier.none" },
|
||||||
|
{ Modifiers.Shift, "keycode-modifier.shift" },
|
||||||
|
{ Modifiers.Alt, "keycode-modifier.alt" },
|
||||||
|
{ Modifiers.Ctrl, "keycode-modifier.ctrl" },
|
||||||
|
{ Modifiers.Meta, "keycode-modifier.meta" },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string DisplayString(Modifiers m)
|
||||||
|
{
|
||||||
|
if (m == Modifiers.Meta && Platform.CurrentPlatform == PlatformType.OSX)
|
||||||
|
return FluentProvider.GetMessage(Cmd);
|
||||||
|
|
||||||
|
if (!ModifierFluentKeys.TryGetValue(m, out var fluentKey))
|
||||||
|
return m.ToString();
|
||||||
|
|
||||||
|
return FluentProvider.GetMessage(fluentKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum KeyInputEvent { Down, Up }
|
public enum KeyInputEvent { Down, Up }
|
||||||
public struct KeyInput
|
public struct KeyInput
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -259,254 +259,255 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static class KeycodeExts
|
public static class KeycodeExts
|
||||||
{
|
{
|
||||||
static readonly Dictionary<Keycode, string> KeyNames = new()
|
[FluentReference(Traits.LintDictionaryReference.Values)]
|
||||||
|
public static readonly IReadOnlyDictionary<Keycode, string> KeycodeFluentKeys = new Dictionary<Keycode, string>()
|
||||||
{
|
{
|
||||||
{ Keycode.UNKNOWN, "Undefined" },
|
{ Keycode.UNKNOWN, "keycode.unknown" },
|
||||||
{ Keycode.RETURN, "Return" },
|
{ Keycode.RETURN, "keycode.return" },
|
||||||
{ Keycode.ESCAPE, "Escape" },
|
{ Keycode.ESCAPE, "keycode.escape" },
|
||||||
{ Keycode.BACKSPACE, "Backspace" },
|
{ Keycode.BACKSPACE, "keycode.backspace" },
|
||||||
{ Keycode.TAB, "Tab" },
|
{ Keycode.TAB, "keycode.tab" },
|
||||||
{ Keycode.SPACE, "Space" },
|
{ Keycode.SPACE, "keycode.space" },
|
||||||
{ Keycode.EXCLAIM, "!" },
|
{ Keycode.EXCLAIM, "keycode.exclaim" },
|
||||||
{ Keycode.QUOTEDBL, "\"" },
|
{ Keycode.QUOTEDBL, "keycode.quotedbl" },
|
||||||
{ Keycode.HASH, "#" },
|
{ Keycode.HASH, "keycode.hash" },
|
||||||
{ Keycode.PERCENT, "%" },
|
{ Keycode.PERCENT, "keycode.percent" },
|
||||||
{ Keycode.DOLLAR, "$" },
|
{ Keycode.DOLLAR, "keycode.dollar" },
|
||||||
{ Keycode.AMPERSAND, "&" },
|
{ Keycode.AMPERSAND, "keycode.ampersand" },
|
||||||
{ Keycode.QUOTE, "'" },
|
{ Keycode.QUOTE, "keycode.quote" },
|
||||||
{ Keycode.LEFTPAREN, "(" },
|
{ Keycode.LEFTPAREN, "keycode.leftparen" },
|
||||||
{ Keycode.RIGHTPAREN, ")" },
|
{ Keycode.RIGHTPAREN, "keycode.rightparen" },
|
||||||
{ Keycode.ASTERISK, "*" },
|
{ Keycode.ASTERISK, "keycode.asterisk" },
|
||||||
{ Keycode.PLUS, "+" },
|
{ Keycode.PLUS, "keycode.plus" },
|
||||||
{ Keycode.COMMA, "," },
|
{ Keycode.COMMA, "keycode.comma" },
|
||||||
{ Keycode.MINUS, "-" },
|
{ Keycode.MINUS, "keycode.minus" },
|
||||||
{ Keycode.PERIOD, "." },
|
{ Keycode.PERIOD, "keycode.period" },
|
||||||
{ Keycode.SLASH, "/" },
|
{ Keycode.SLASH, "keycode.slash" },
|
||||||
{ Keycode.NUMBER_0, "0" },
|
{ Keycode.NUMBER_0, "keycode.number_0" },
|
||||||
{ Keycode.NUMBER_1, "1" },
|
{ Keycode.NUMBER_1, "keycode.number_1" },
|
||||||
{ Keycode.NUMBER_2, "2" },
|
{ Keycode.NUMBER_2, "keycode.number_2" },
|
||||||
{ Keycode.NUMBER_3, "3" },
|
{ Keycode.NUMBER_3, "keycode.number_3" },
|
||||||
{ Keycode.NUMBER_4, "4" },
|
{ Keycode.NUMBER_4, "keycode.number_4" },
|
||||||
{ Keycode.NUMBER_5, "5" },
|
{ Keycode.NUMBER_5, "keycode.number_5" },
|
||||||
{ Keycode.NUMBER_6, "6" },
|
{ Keycode.NUMBER_6, "keycode.number_6" },
|
||||||
{ Keycode.NUMBER_7, "7" },
|
{ Keycode.NUMBER_7, "keycode.number_7" },
|
||||||
{ Keycode.NUMBER_8, "8" },
|
{ Keycode.NUMBER_8, "keycode.number_8" },
|
||||||
{ Keycode.NUMBER_9, "9" },
|
{ Keycode.NUMBER_9, "keycode.number_9" },
|
||||||
{ Keycode.COLON, ":" },
|
{ Keycode.COLON, "keycode.colon" },
|
||||||
{ Keycode.SEMICOLON, ";" },
|
{ Keycode.SEMICOLON, "keycode.semicolon" },
|
||||||
{ Keycode.LESS, "<" },
|
{ Keycode.LESS, "keycode.less" },
|
||||||
{ Keycode.EQUALS, "=" },
|
{ Keycode.EQUALS, "keycode.equals" },
|
||||||
{ Keycode.GREATER, ">" },
|
{ Keycode.GREATER, "keycode.greater" },
|
||||||
{ Keycode.QUESTION, "?" },
|
{ Keycode.QUESTION, "keycode.question" },
|
||||||
{ Keycode.AT, "@" },
|
{ Keycode.AT, "keycode.at" },
|
||||||
{ Keycode.LEFTBRACKET, "[" },
|
{ Keycode.LEFTBRACKET, "keycode.leftbracket" },
|
||||||
{ Keycode.BACKSLASH, "\\" },
|
{ Keycode.BACKSLASH, "keycode.backslash" },
|
||||||
{ Keycode.RIGHTBRACKET, "]" },
|
{ Keycode.RIGHTBRACKET, "keycode.rightbracket" },
|
||||||
{ Keycode.CARET, "^" },
|
{ Keycode.CARET, "keycode.caret" },
|
||||||
{ Keycode.UNDERSCORE, "_" },
|
{ Keycode.UNDERSCORE, "keycode.underscore" },
|
||||||
{ Keycode.BACKQUOTE, "`" },
|
{ Keycode.BACKQUOTE, "keycode.backquote" },
|
||||||
{ Keycode.A, "A" },
|
{ Keycode.A, "keycode.a" },
|
||||||
{ Keycode.B, "B" },
|
{ Keycode.B, "keycode.b" },
|
||||||
{ Keycode.C, "C" },
|
{ Keycode.C, "keycode.c" },
|
||||||
{ Keycode.D, "D" },
|
{ Keycode.D, "keycode.d" },
|
||||||
{ Keycode.E, "E" },
|
{ Keycode.E, "keycode.e" },
|
||||||
{ Keycode.F, "F" },
|
{ Keycode.F, "keycode.f" },
|
||||||
{ Keycode.G, "G" },
|
{ Keycode.G, "keycode.g" },
|
||||||
{ Keycode.H, "H" },
|
{ Keycode.H, "keycode.h" },
|
||||||
{ Keycode.I, "I" },
|
{ Keycode.I, "keycode.i" },
|
||||||
{ Keycode.J, "J" },
|
{ Keycode.J, "keycode.j" },
|
||||||
{ Keycode.K, "K" },
|
{ Keycode.K, "keycode.k" },
|
||||||
{ Keycode.L, "L" },
|
{ Keycode.L, "keycode.l" },
|
||||||
{ Keycode.M, "M" },
|
{ Keycode.M, "keycode.m" },
|
||||||
{ Keycode.N, "N" },
|
{ Keycode.N, "keycode.n" },
|
||||||
{ Keycode.O, "O" },
|
{ Keycode.O, "keycode.o" },
|
||||||
{ Keycode.P, "P" },
|
{ Keycode.P, "keycode.p" },
|
||||||
{ Keycode.Q, "Q" },
|
{ Keycode.Q, "keycode.q" },
|
||||||
{ Keycode.R, "R" },
|
{ Keycode.R, "keycode.r" },
|
||||||
{ Keycode.S, "S" },
|
{ Keycode.S, "keycode.s" },
|
||||||
{ Keycode.T, "T" },
|
{ Keycode.T, "keycode.t" },
|
||||||
{ Keycode.U, "U" },
|
{ Keycode.U, "keycode.u" },
|
||||||
{ Keycode.V, "V" },
|
{ Keycode.V, "keycode.v" },
|
||||||
{ Keycode.W, "W" },
|
{ Keycode.W, "keycode.w" },
|
||||||
{ Keycode.X, "X" },
|
{ Keycode.X, "keycode.x" },
|
||||||
{ Keycode.Y, "Y" },
|
{ Keycode.Y, "keycode.y" },
|
||||||
{ Keycode.Z, "Z" },
|
{ Keycode.Z, "keycode.z" },
|
||||||
{ Keycode.CAPSLOCK, "CapsLock" },
|
{ Keycode.CAPSLOCK, "keycode.capslock" },
|
||||||
{ Keycode.F1, "F1" },
|
{ Keycode.F1, "keycode.f1" },
|
||||||
{ Keycode.F2, "F2" },
|
{ Keycode.F2, "keycode.f2" },
|
||||||
{ Keycode.F3, "F3" },
|
{ Keycode.F3, "keycode.f3" },
|
||||||
{ Keycode.F4, "F4" },
|
{ Keycode.F4, "keycode.f4" },
|
||||||
{ Keycode.F5, "F5" },
|
{ Keycode.F5, "keycode.f5" },
|
||||||
{ Keycode.F6, "F6" },
|
{ Keycode.F6, "keycode.f6" },
|
||||||
{ Keycode.F7, "F7" },
|
{ Keycode.F7, "keycode.f7" },
|
||||||
{ Keycode.F8, "F8" },
|
{ Keycode.F8, "keycode.f8" },
|
||||||
{ Keycode.F9, "F9" },
|
{ Keycode.F9, "keycode.f9" },
|
||||||
{ Keycode.F10, "F10" },
|
{ Keycode.F10, "keycode.f10" },
|
||||||
{ Keycode.F11, "F11" },
|
{ Keycode.F11, "keycode.f11" },
|
||||||
{ Keycode.F12, "F12" },
|
{ Keycode.F12, "keycode.f12" },
|
||||||
{ Keycode.PRINTSCREEN, "PrintScreen" },
|
{ Keycode.PRINTSCREEN, "keycode.printscreen" },
|
||||||
{ Keycode.SCROLLLOCK, "ScrollLock" },
|
{ Keycode.SCROLLLOCK, "keycode.scrolllock" },
|
||||||
{ Keycode.PAUSE, "Pause" },
|
{ Keycode.PAUSE, "keycode.pause" },
|
||||||
{ Keycode.INSERT, "Insert" },
|
{ Keycode.INSERT, "keycode.insert" },
|
||||||
{ Keycode.HOME, "Home" },
|
{ Keycode.HOME, "keycode.home" },
|
||||||
{ Keycode.PAGEUP, "PageUp" },
|
{ Keycode.PAGEUP, "keycode.pageup" },
|
||||||
{ Keycode.DELETE, "Delete" },
|
{ Keycode.DELETE, "keycode.delete" },
|
||||||
{ Keycode.END, "End" },
|
{ Keycode.END, "keycode.end" },
|
||||||
{ Keycode.PAGEDOWN, "PageDown" },
|
{ Keycode.PAGEDOWN, "keycode.pagedown" },
|
||||||
{ Keycode.RIGHT, "Right" },
|
{ Keycode.RIGHT, "keycode.right" },
|
||||||
{ Keycode.LEFT, "Left" },
|
{ Keycode.LEFT, "keycode.left" },
|
||||||
{ Keycode.DOWN, "Down" },
|
{ Keycode.DOWN, "keycode.down" },
|
||||||
{ Keycode.UP, "Up" },
|
{ Keycode.UP, "keycode.up" },
|
||||||
{ Keycode.NUMLOCKCLEAR, "Numlock" },
|
{ Keycode.NUMLOCKCLEAR, "keycode.numlockclear" },
|
||||||
{ Keycode.KP_DIVIDE, "Keypad /" },
|
{ Keycode.KP_DIVIDE, "keycode.kp_divide" },
|
||||||
{ Keycode.KP_MULTIPLY, "Keypad *" },
|
{ Keycode.KP_MULTIPLY, "keycode.kp_multiply" },
|
||||||
{ Keycode.KP_MINUS, "Keypad -" },
|
{ Keycode.KP_MINUS, "keycode.kp_minus" },
|
||||||
{ Keycode.KP_PLUS, "Keypad +" },
|
{ Keycode.KP_PLUS, "keycode.kp_plus" },
|
||||||
{ Keycode.KP_ENTER, "Keypad Enter" },
|
{ Keycode.KP_ENTER, "keycode.kp_enter" },
|
||||||
{ Keycode.KP_1, "Keypad 1" },
|
{ Keycode.KP_1, "keycode.kp_1" },
|
||||||
{ Keycode.KP_2, "Keypad 2" },
|
{ Keycode.KP_2, "keycode.kp_2" },
|
||||||
{ Keycode.KP_3, "Keypad 3" },
|
{ Keycode.KP_3, "keycode.kp_3" },
|
||||||
{ Keycode.KP_4, "Keypad 4" },
|
{ Keycode.KP_4, "keycode.kp_4" },
|
||||||
{ Keycode.KP_5, "Keypad 5" },
|
{ Keycode.KP_5, "keycode.kp_5" },
|
||||||
{ Keycode.KP_6, "Keypad 6" },
|
{ Keycode.KP_6, "keycode.kp_6" },
|
||||||
{ Keycode.KP_7, "Keypad 7" },
|
{ Keycode.KP_7, "keycode.kp_7" },
|
||||||
{ Keycode.KP_8, "Keypad 8" },
|
{ Keycode.KP_8, "keycode.kp_8" },
|
||||||
{ Keycode.KP_9, "Keypad 9" },
|
{ Keycode.KP_9, "keycode.kp_9" },
|
||||||
{ Keycode.KP_0, "Keypad 0" },
|
{ Keycode.KP_0, "keycode.kp_0" },
|
||||||
{ Keycode.KP_PERIOD, "Keypad ." },
|
{ Keycode.KP_PERIOD, "keycode.kp_period" },
|
||||||
{ Keycode.APPLICATION, "Application" },
|
{ Keycode.APPLICATION, "keycode.application" },
|
||||||
{ Keycode.POWER, "Power" },
|
{ Keycode.POWER, "keycode.power" },
|
||||||
{ Keycode.KP_EQUALS, "Keypad =" },
|
{ Keycode.KP_EQUALS, "keycode.kp_equals" },
|
||||||
{ Keycode.F13, "F13" },
|
{ Keycode.F13, "keycode.f13" },
|
||||||
{ Keycode.F14, "F14" },
|
{ Keycode.F14, "keycode.f14" },
|
||||||
{ Keycode.F15, "F15" },
|
{ Keycode.F15, "keycode.f15" },
|
||||||
{ Keycode.F16, "F16" },
|
{ Keycode.F16, "keycode.f16" },
|
||||||
{ Keycode.F17, "F17" },
|
{ Keycode.F17, "keycode.f17" },
|
||||||
{ Keycode.F18, "F18" },
|
{ Keycode.F18, "keycode.f18" },
|
||||||
{ Keycode.F19, "F19" },
|
{ Keycode.F19, "keycode.f19" },
|
||||||
{ Keycode.F20, "F20" },
|
{ Keycode.F20, "keycode.f20" },
|
||||||
{ Keycode.F21, "F21" },
|
{ Keycode.F21, "keycode.f21" },
|
||||||
{ Keycode.F22, "F22" },
|
{ Keycode.F22, "keycode.f22" },
|
||||||
{ Keycode.F23, "F23" },
|
{ Keycode.F23, "keycode.f23" },
|
||||||
{ Keycode.F24, "F24" },
|
{ Keycode.F24, "keycode.f24" },
|
||||||
{ Keycode.EXECUTE, "Execute" },
|
{ Keycode.EXECUTE, "keycode.execute" },
|
||||||
{ Keycode.HELP, "Help" },
|
{ Keycode.HELP, "keycode.help" },
|
||||||
{ Keycode.MENU, "Menu" },
|
{ Keycode.MENU, "keycode.menu" },
|
||||||
{ Keycode.SELECT, "Select" },
|
{ Keycode.SELECT, "keycode.select" },
|
||||||
{ Keycode.STOP, "Stop" },
|
{ Keycode.STOP, "keycode.stop" },
|
||||||
{ Keycode.AGAIN, "Again" },
|
{ Keycode.AGAIN, "keycode.again" },
|
||||||
{ Keycode.UNDO, "Undo" },
|
{ Keycode.UNDO, "keycode.undo" },
|
||||||
{ Keycode.CUT, "Cut" },
|
{ Keycode.CUT, "keycode.cut" },
|
||||||
{ Keycode.COPY, "Copy" },
|
{ Keycode.COPY, "keycode.copy" },
|
||||||
{ Keycode.PASTE, "Paste" },
|
{ Keycode.PASTE, "keycode.paste" },
|
||||||
{ Keycode.FIND, "Find" },
|
{ Keycode.FIND, "keycode.find" },
|
||||||
{ Keycode.MUTE, "Mute" },
|
{ Keycode.MUTE, "keycode.mute" },
|
||||||
{ Keycode.VOLUMEUP, "VolumeUp" },
|
{ Keycode.VOLUMEUP, "keycode.volumeup" },
|
||||||
{ Keycode.VOLUMEDOWN, "VolumeDown" },
|
{ Keycode.VOLUMEDOWN, "keycode.volumedown" },
|
||||||
{ Keycode.KP_COMMA, "Keypad }," },
|
{ Keycode.KP_COMMA, "keycode.kp_comma" },
|
||||||
{ Keycode.KP_EQUALSAS400, "Keypad, (AS400)" },
|
{ Keycode.KP_EQUALSAS400, "keycode.kp_equalsas400" },
|
||||||
{ Keycode.ALTERASE, "AltErase" },
|
{ Keycode.ALTERASE, "keycode.alterase" },
|
||||||
{ Keycode.SYSREQ, "SysReq" },
|
{ Keycode.SYSREQ, "keycode.sysreq" },
|
||||||
{ Keycode.CANCEL, "Cancel" },
|
{ Keycode.CANCEL, "keycode.cancel" },
|
||||||
{ Keycode.CLEAR, "Clear" },
|
{ Keycode.CLEAR, "keycode.clear" },
|
||||||
{ Keycode.PRIOR, "Prior" },
|
{ Keycode.PRIOR, "keycode.prior" },
|
||||||
{ Keycode.RETURN2, "Return" },
|
{ Keycode.RETURN2, "keycode.return2" },
|
||||||
{ Keycode.SEPARATOR, "Separator" },
|
{ Keycode.SEPARATOR, "keycode.separator" },
|
||||||
{ Keycode.OUT, "Out" },
|
{ Keycode.OUT, "keycode.out" },
|
||||||
{ Keycode.OPER, "Oper" },
|
{ Keycode.OPER, "keycode.oper" },
|
||||||
{ Keycode.CLEARAGAIN, "Clear / Again" },
|
{ Keycode.CLEARAGAIN, "keycode.clearagain" },
|
||||||
{ Keycode.CRSEL, "CrSel" },
|
{ Keycode.CRSEL, "keycode.crsel" },
|
||||||
{ Keycode.EXSEL, "ExSel" },
|
{ Keycode.EXSEL, "keycode.exsel" },
|
||||||
{ Keycode.KP_00, "Keypad 00" },
|
{ Keycode.KP_00, "keycode.kp_00" },
|
||||||
{ Keycode.KP_000, "Keypad 000" },
|
{ Keycode.KP_000, "keycode.kp_000" },
|
||||||
{ Keycode.THOUSANDSSEPARATOR, "ThousandsSeparator" },
|
{ Keycode.THOUSANDSSEPARATOR, "keycode.thousandsseparator" },
|
||||||
{ Keycode.DECIMALSEPARATOR, "DecimalSeparator" },
|
{ Keycode.DECIMALSEPARATOR, "keycode.decimalseparator" },
|
||||||
{ Keycode.CURRENCYUNIT, "CurrencyUnit" },
|
{ Keycode.CURRENCYUNIT, "keycode.currencyunit" },
|
||||||
{ Keycode.CURRENCYSUBUNIT, "CurrencySubUnit" },
|
{ Keycode.CURRENCYSUBUNIT, "keycode.currencysubunit" },
|
||||||
{ Keycode.KP_LEFTPAREN, "Keypad (" },
|
{ Keycode.KP_LEFTPAREN, "keycode.kp_leftparen" },
|
||||||
{ Keycode.KP_RIGHTPAREN, "Keypad )" },
|
{ Keycode.KP_RIGHTPAREN, "keycode.kp_rightparen" },
|
||||||
{ Keycode.KP_LEFTBRACE, "Keypad {" },
|
{ Keycode.KP_LEFTBRACE, "keycode.kp_leftbrace" },
|
||||||
{ Keycode.KP_RIGHTBRACE, "Keypad }" },
|
{ Keycode.KP_RIGHTBRACE, "keycode.kp_rightbrace" },
|
||||||
{ Keycode.KP_TAB, "Keypad Tab" },
|
{ Keycode.KP_TAB, "keycode.kp_tab" },
|
||||||
{ Keycode.KP_BACKSPACE, "Keypad Backspace" },
|
{ Keycode.KP_BACKSPACE, "keycode.kp_backspace" },
|
||||||
{ Keycode.KP_A, "Keypad A" },
|
{ Keycode.KP_A, "keycode.kp_a" },
|
||||||
{ Keycode.KP_B, "Keypad B" },
|
{ Keycode.KP_B, "keycode.kp_b" },
|
||||||
{ Keycode.KP_C, "Keypad C" },
|
{ Keycode.KP_C, "keycode.kp_c" },
|
||||||
{ Keycode.KP_D, "Keypad D" },
|
{ Keycode.KP_D, "keycode.kp_d" },
|
||||||
{ Keycode.KP_E, "Keypad E" },
|
{ Keycode.KP_E, "keycode.kp_e" },
|
||||||
{ Keycode.KP_F, "Keypad F" },
|
{ Keycode.KP_F, "keycode.kp_f" },
|
||||||
{ Keycode.KP_XOR, "Keypad XOR" },
|
{ Keycode.KP_XOR, "keycode.kp_xor" },
|
||||||
{ Keycode.KP_POWER, "Keypad ^" },
|
{ Keycode.KP_POWER, "keycode.kp_power" },
|
||||||
{ Keycode.KP_PERCENT, "Keypad %" },
|
{ Keycode.KP_PERCENT, "keycode.kp_percent" },
|
||||||
{ Keycode.KP_LESS, "Keypad <" },
|
{ Keycode.KP_LESS, "keycode.kp_less" },
|
||||||
{ Keycode.KP_GREATER, "Keypad >" },
|
{ Keycode.KP_GREATER, "keycode.kp_greater" },
|
||||||
{ Keycode.KP_AMPERSAND, "Keypad &" },
|
{ Keycode.KP_AMPERSAND, "keycode.kp_ampersand" },
|
||||||
{ Keycode.KP_DBLAMPERSAND, "Keypad &&" },
|
{ Keycode.KP_DBLAMPERSAND, "keycode.kp_dblampersand" },
|
||||||
{ Keycode.KP_VERTICALBAR, "Keypad |" },
|
{ Keycode.KP_VERTICALBAR, "keycode.kp_verticalbar" },
|
||||||
{ Keycode.KP_DBLVERTICALBAR, "Keypad ||" },
|
{ Keycode.KP_DBLVERTICALBAR, "keycode.kp_dblverticalbar" },
|
||||||
{ Keycode.KP_COLON, "Keypad :" },
|
{ Keycode.KP_COLON, "keycode.kp_colon" },
|
||||||
{ Keycode.KP_HASH, "Keypad #" },
|
{ Keycode.KP_HASH, "keycode.kp_hash" },
|
||||||
{ Keycode.KP_SPACE, "Keypad Space" },
|
{ Keycode.KP_SPACE, "keycode.kp_space" },
|
||||||
{ Keycode.KP_AT, "Keypad @" },
|
{ Keycode.KP_AT, "keycode.kp_at" },
|
||||||
{ Keycode.KP_EXCLAM, "Keypad !" },
|
{ Keycode.KP_EXCLAM, "keycode.kp_exclam" },
|
||||||
{ Keycode.KP_MEMSTORE, "Keypad MemStore" },
|
{ Keycode.KP_MEMSTORE, "keycode.kp_memstore" },
|
||||||
{ Keycode.KP_MEMRECALL, "Keypad MemRecall" },
|
{ Keycode.KP_MEMRECALL, "keycode.kp_memrecall" },
|
||||||
{ Keycode.KP_MEMCLEAR, "Keypad MemClear" },
|
{ Keycode.KP_MEMCLEAR, "keycode.kp_memclear" },
|
||||||
{ Keycode.KP_MEMADD, "Keypad MemAdd" },
|
{ Keycode.KP_MEMADD, "keycode.kp_memadd" },
|
||||||
{ Keycode.KP_MEMSUBTRACT, "Keypad MemSubtract" },
|
{ Keycode.KP_MEMSUBTRACT, "keycode.kp_memsubtract" },
|
||||||
{ Keycode.KP_MEMMULTIPLY, "Keypad MemMultiply" },
|
{ Keycode.KP_MEMMULTIPLY, "keycode.kp_memmultiply" },
|
||||||
{ Keycode.KP_MEMDIVIDE, "Keypad MemDivide" },
|
{ Keycode.KP_MEMDIVIDE, "keycode.kp_memdivide" },
|
||||||
{ Keycode.KP_PLUSMINUS, "Keypad +/-" },
|
{ Keycode.KP_PLUSMINUS, "keycode.kp_plusminus" },
|
||||||
{ Keycode.KP_CLEAR, "Keypad Clear" },
|
{ Keycode.KP_CLEAR, "keycode.kp_clear" },
|
||||||
{ Keycode.KP_CLEARENTRY, "Keypad ClearEntry" },
|
{ Keycode.KP_CLEARENTRY, "keycode.kp_clearentry" },
|
||||||
{ Keycode.KP_BINARY, "Keypad Binary" },
|
{ Keycode.KP_BINARY, "keycode.kp_binary" },
|
||||||
{ Keycode.KP_OCTAL, "Keypad Octal" },
|
{ Keycode.KP_OCTAL, "keycode.kp_octal" },
|
||||||
{ Keycode.KP_DECIMAL, "Keypad Decimal" },
|
{ Keycode.KP_DECIMAL, "keycode.kp_decimal" },
|
||||||
{ Keycode.KP_HEXADECIMAL, "Keypad Hexadecimal" },
|
{ Keycode.KP_HEXADECIMAL, "keycode.kp_hexadecimal" },
|
||||||
{ Keycode.LCTRL, "Left Ctrl" },
|
{ Keycode.LCTRL, "keycode.lctrl" },
|
||||||
{ Keycode.LSHIFT, "Left Shift" },
|
{ Keycode.LSHIFT, "keycode.lshift" },
|
||||||
{ Keycode.LALT, "Left Alt" },
|
{ Keycode.LALT, "keycode.lalt" },
|
||||||
{ Keycode.LGUI, "Left GUI" },
|
{ Keycode.LGUI, "keycode.lgui" },
|
||||||
{ Keycode.RCTRL, "Right Ctrl" },
|
{ Keycode.RCTRL, "keycode.rctrl" },
|
||||||
{ Keycode.RSHIFT, "Right Shift" },
|
{ Keycode.RSHIFT, "keycode.rshift" },
|
||||||
{ Keycode.RALT, "Right Alt" },
|
{ Keycode.RALT, "keycode.ralt" },
|
||||||
{ Keycode.RGUI, "Right GUI" },
|
{ Keycode.RGUI, "keycode.rgui" },
|
||||||
{ Keycode.MODE, "ModeSwitch" },
|
{ Keycode.MODE, "keycode.mode" },
|
||||||
{ Keycode.AUDIONEXT, "AudioNext" },
|
{ Keycode.AUDIONEXT, "keycode.audionext" },
|
||||||
{ Keycode.AUDIOPREV, "AudioPrev" },
|
{ Keycode.AUDIOPREV, "keycode.audioprev" },
|
||||||
{ Keycode.AUDIOSTOP, "AudioStop" },
|
{ Keycode.AUDIOSTOP, "keycode.audiostop" },
|
||||||
{ Keycode.AUDIOPLAY, "AudioPlay" },
|
{ Keycode.AUDIOPLAY, "keycode.audioplay" },
|
||||||
{ Keycode.AUDIOMUTE, "AudioMute" },
|
{ Keycode.AUDIOMUTE, "keycode.audiomute" },
|
||||||
{ Keycode.MEDIASELECT, "MediaSelect" },
|
{ Keycode.MEDIASELECT, "keycode.mediaselect" },
|
||||||
{ Keycode.WWW, "WWW" },
|
{ Keycode.WWW, "keycode.www" },
|
||||||
{ Keycode.MAIL, "Mail" },
|
{ Keycode.MAIL, "keycode.mail" },
|
||||||
{ Keycode.CALCULATOR, "Calculator" },
|
{ Keycode.CALCULATOR, "keycode.calculator" },
|
||||||
{ Keycode.COMPUTER, "Computer" },
|
{ Keycode.COMPUTER, "keycode.computer" },
|
||||||
{ Keycode.AC_SEARCH, "AC Search" },
|
{ Keycode.AC_SEARCH, "keycode.ac_search" },
|
||||||
{ Keycode.AC_HOME, "AC Home" },
|
{ Keycode.AC_HOME, "keycode.ac_home" },
|
||||||
{ Keycode.AC_BACK, "AC Back" },
|
{ Keycode.AC_BACK, "keycode.ac_back" },
|
||||||
{ Keycode.AC_FORWARD, "AC Forward" },
|
{ Keycode.AC_FORWARD, "keycode.ac_forward" },
|
||||||
{ Keycode.AC_STOP, "AC Stop" },
|
{ Keycode.AC_STOP, "keycode.ac_stop" },
|
||||||
{ Keycode.AC_REFRESH, "AC Refresh" },
|
{ Keycode.AC_REFRESH, "keycode.ac_refresh" },
|
||||||
{ Keycode.AC_BOOKMARKS, "AC Bookmarks" },
|
{ Keycode.AC_BOOKMARKS, "keycode.ac_bookmarks" },
|
||||||
{ Keycode.BRIGHTNESSDOWN, "BrightnessDown" },
|
{ Keycode.BRIGHTNESSDOWN, "keycode.brightnessdown" },
|
||||||
{ Keycode.BRIGHTNESSUP, "BrightnessUp" },
|
{ Keycode.BRIGHTNESSUP, "keycode.brightnessup" },
|
||||||
{ Keycode.DISPLAYSWITCH, "DisplaySwitch" },
|
{ Keycode.DISPLAYSWITCH, "keycode.displayswitch" },
|
||||||
{ Keycode.KBDILLUMTOGGLE, "KBDIllumToggle" },
|
{ Keycode.KBDILLUMTOGGLE, "keycode.kbdillumtoggle" },
|
||||||
{ Keycode.KBDILLUMDOWN, "KBDIllumDown" },
|
{ Keycode.KBDILLUMDOWN, "keycode.kbdillumdown" },
|
||||||
{ Keycode.KBDILLUMUP, "KBDIllumUp" },
|
{ Keycode.KBDILLUMUP, "keycode.kbdillumup" },
|
||||||
{ Keycode.EJECT, "Eject" },
|
{ Keycode.EJECT, "keycode.eject" },
|
||||||
{ Keycode.SLEEP, "Sleep" },
|
{ Keycode.SLEEP, "keycode.sleep" },
|
||||||
{ Keycode.MOUSE4, "Mouse 4" },
|
{ Keycode.MOUSE4, "keycode.mouse4" },
|
||||||
{ Keycode.MOUSE5, "Mouse 5" },
|
{ Keycode.MOUSE5, "keycode.mouse5" },
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string DisplayString(Keycode k)
|
public static string DisplayString(Keycode k)
|
||||||
{
|
{
|
||||||
if (!KeyNames.TryGetValue(k, out var ret))
|
if (!KeycodeFluentKeys.TryGetValue(k, out var fluentKey))
|
||||||
return k.ToString();
|
return k.ToString();
|
||||||
|
|
||||||
return ret;
|
return FluentProvider.GetMessage(fluentKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,10 +84,11 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var client = HttpClientFactory.Create();
|
var client = HttpClientFactory.Create();
|
||||||
|
|
||||||
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
|
var url = playerDatabase.Profile + Fingerprint;
|
||||||
|
var httpResponseMessage = await client.GetAsync(url);
|
||||||
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
var yaml = MiniYaml.FromStream(result).First();
|
var yaml = MiniYaml.FromStream(result, url).First();
|
||||||
if (yaml.Key == "Player")
|
if (yaml.Key == "Player")
|
||||||
{
|
{
|
||||||
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
||||||
|
|||||||
@@ -43,25 +43,22 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModelSequenceFormat : IGlobalModData
|
|
||||||
{
|
|
||||||
public readonly string Type;
|
|
||||||
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
|
|
||||||
public ModelSequenceFormat(MiniYaml yaml)
|
|
||||||
{
|
|
||||||
Type = yaml.Value;
|
|
||||||
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ModMetadata
|
public class ModMetadata
|
||||||
{
|
{
|
||||||
public string Title;
|
// FieldLoader used here, must matching naming in YAML.
|
||||||
public string Version;
|
#pragma warning disable IDE1006 // Naming Styles
|
||||||
public string Website;
|
[FluentReference]
|
||||||
public string WebIcon32;
|
readonly string Title;
|
||||||
public string WindowTitle;
|
public readonly string Version;
|
||||||
public bool Hidden;
|
public readonly string Website;
|
||||||
|
public readonly string WebIcon32;
|
||||||
|
[FluentReference]
|
||||||
|
readonly string WindowTitle;
|
||||||
|
public readonly bool Hidden;
|
||||||
|
#pragma warning restore IDE1006 // Naming Styles
|
||||||
|
|
||||||
|
public string TitleTranslated => FluentProvider.GetMessage(Title);
|
||||||
|
public string WindowTitleTranslated => WindowTitle != null ? FluentProvider.GetMessage(WindowTitle) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
|
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
|
||||||
@@ -72,27 +69,34 @@ namespace OpenRA
|
|||||||
public readonly ModMetadata Metadata;
|
public readonly ModMetadata Metadata;
|
||||||
public readonly string[]
|
public readonly string[]
|
||||||
Rules, ServerTraits,
|
Rules, ServerTraits,
|
||||||
Sequences, ModelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
Sequences, ModelSequences, Cursors, Chrome, ChromeLayout,
|
||||||
Weapons, Voices, Notifications, Music, Translations, TileSets,
|
Weapons, Voices, Notifications, Music, FluentMessages, TileSets,
|
||||||
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
||||||
|
|
||||||
public readonly IReadOnlyDictionary<string, string> Packages;
|
|
||||||
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
||||||
|
public readonly MiniYaml FileSystem;
|
||||||
public readonly MiniYaml LoadScreen;
|
public readonly MiniYaml LoadScreen;
|
||||||
public readonly string DefaultOrderGenerator;
|
public readonly string DefaultOrderGenerator;
|
||||||
|
|
||||||
|
public readonly string[] Assemblies = Array.Empty<string>();
|
||||||
public readonly string[] SoundFormats = Array.Empty<string>();
|
public readonly string[] SoundFormats = Array.Empty<string>();
|
||||||
public readonly string[] SpriteFormats = Array.Empty<string>();
|
public readonly string[] SpriteFormats = Array.Empty<string>();
|
||||||
public readonly string[] PackageFormats = Array.Empty<string>();
|
public readonly string[] PackageFormats = Array.Empty<string>();
|
||||||
public readonly string[] VideoFormats = Array.Empty<string>();
|
public readonly string[] VideoFormats = Array.Empty<string>();
|
||||||
|
public readonly int FontSheetSize = 512;
|
||||||
|
public readonly int CursorSheetSize = 512;
|
||||||
|
|
||||||
|
// TODO: This should be controlled by a user-selected translation bundle!
|
||||||
|
public readonly string FluentCulture = "en";
|
||||||
|
public readonly bool AllowUnusedFluentMessagesInExternalPackages = true;
|
||||||
|
|
||||||
readonly string[] reservedModuleNames =
|
readonly string[] reservedModuleNames =
|
||||||
{
|
{
|
||||||
"Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
"Include", "Metadata", "FileSystem", "MapFolders", "Rules",
|
||||||
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||||
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
"Voices", "Notifications", "Music", "FluentMessages", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
||||||
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
|
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
|
||||||
"RequiresMods", "PackageFormats"
|
"RequiresMods", "PackageFormats", "AllowUnusedFluentMessagesInExternalPackages", "FontSheetSize", "CursorSheetSize"
|
||||||
};
|
};
|
||||||
|
|
||||||
readonly TypeDictionary modules = new();
|
readonly TypeDictionary modules = new();
|
||||||
@@ -105,7 +109,8 @@ namespace OpenRA
|
|||||||
Id = modId;
|
Id = modId;
|
||||||
Package = package;
|
Package = package;
|
||||||
|
|
||||||
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml");
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
|
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), $"{package.Name}:mod.yaml", stringPool: stringPool);
|
||||||
for (var i = nodes.Count - 1; i >= 0; i--)
|
for (var i = nodes.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (nodes[i].Key != "Include")
|
if (nodes[i].Key != "Include")
|
||||||
@@ -118,7 +123,7 @@ namespace OpenRA
|
|||||||
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
|
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
|
||||||
|
|
||||||
nodes.RemoveAt(i);
|
nodes.RemoveAt(i);
|
||||||
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
|
nodes.InsertRange(i, MiniYaml.FromStream(contents, $"{package.Name}:{filename}", stringPool: stringPool));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge inherited overrides
|
// Merge inherited overrides
|
||||||
@@ -129,21 +134,20 @@ namespace OpenRA
|
|||||||
// TODO: Use fieldloader
|
// TODO: Use fieldloader
|
||||||
MapFolders = YamlDictionary(yaml, "MapFolders");
|
MapFolders = YamlDictionary(yaml, "MapFolders");
|
||||||
|
|
||||||
if (yaml.TryGetValue("Packages", out var packages))
|
if (!yaml.TryGetValue("FileSystem", out FileSystem))
|
||||||
Packages = packages.ToDictionary(x => x.Value);
|
throw new InvalidDataException("`FileSystem` section is not defined.");
|
||||||
|
|
||||||
Rules = YamlList(yaml, "Rules");
|
Rules = YamlList(yaml, "Rules");
|
||||||
Sequences = YamlList(yaml, "Sequences");
|
Sequences = YamlList(yaml, "Sequences");
|
||||||
ModelSequences = YamlList(yaml, "ModelSequences");
|
ModelSequences = YamlList(yaml, "ModelSequences");
|
||||||
Cursors = YamlList(yaml, "Cursors");
|
Cursors = YamlList(yaml, "Cursors");
|
||||||
Chrome = YamlList(yaml, "Chrome");
|
Chrome = YamlList(yaml, "Chrome");
|
||||||
Assemblies = YamlList(yaml, "Assemblies");
|
|
||||||
ChromeLayout = YamlList(yaml, "ChromeLayout");
|
ChromeLayout = YamlList(yaml, "ChromeLayout");
|
||||||
Weapons = YamlList(yaml, "Weapons");
|
Weapons = YamlList(yaml, "Weapons");
|
||||||
Voices = YamlList(yaml, "Voices");
|
Voices = YamlList(yaml, "Voices");
|
||||||
Notifications = YamlList(yaml, "Notifications");
|
Notifications = YamlList(yaml, "Notifications");
|
||||||
Music = YamlList(yaml, "Music");
|
Music = YamlList(yaml, "Music");
|
||||||
Translations = YamlList(yaml, "Translations");
|
FluentMessages = YamlList(yaml, "FluentMessages");
|
||||||
TileSets = YamlList(yaml, "TileSets");
|
TileSets = YamlList(yaml, "TileSets");
|
||||||
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
||||||
Missions = YamlList(yaml, "Missions");
|
Missions = YamlList(yaml, "Missions");
|
||||||
@@ -165,6 +169,9 @@ namespace OpenRA
|
|||||||
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
|
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
|
||||||
DefaultOrderGenerator = entry.Value;
|
DefaultOrderGenerator = entry.Value;
|
||||||
|
|
||||||
|
if (yaml.TryGetValue("Assemblies", out entry))
|
||||||
|
Assemblies = FieldLoader.GetValue<string[]>("Assemblies", entry.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("PackageFormats", out entry))
|
if (yaml.TryGetValue("PackageFormats", out entry))
|
||||||
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", entry.Value);
|
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", entry.Value);
|
||||||
|
|
||||||
@@ -176,6 +183,16 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (yaml.TryGetValue("VideoFormats", out entry))
|
if (yaml.TryGetValue("VideoFormats", out entry))
|
||||||
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
|
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
|
||||||
|
|
||||||
|
if (yaml.TryGetValue("AllowUnusedFluentMessagesInExternalPackages", out entry))
|
||||||
|
AllowUnusedFluentMessagesInExternalPackages =
|
||||||
|
FieldLoader.GetValue<bool>("AllowUnusedFluentMessagesInExternalPackages", entry.Value);
|
||||||
|
|
||||||
|
if (yaml.TryGetValue("FontSheetSize", out entry))
|
||||||
|
FontSheetSize = FieldLoader.GetValue<int>("FontSheetSize", entry.Value);
|
||||||
|
|
||||||
|
if (yaml.TryGetValue("CursorSheetSize", out entry))
|
||||||
|
CursorSheetSize = FieldLoader.GetValue<int>("CursorSheetSize", entry.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadCustomData(ObjectCreator oc)
|
public void LoadCustomData(ObjectCreator oc)
|
||||||
@@ -211,18 +228,18 @@ namespace OpenRA
|
|||||||
|
|
||||||
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)
|
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)
|
||||||
{
|
{
|
||||||
if (!yaml.ContainsKey(key))
|
if (!yaml.TryGetValue(key, out var value))
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
|
|
||||||
return yaml[key].ToDictionary().Keys.ToArray();
|
return value.Nodes.Select(n => n.Key).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
|
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
|
||||||
{
|
{
|
||||||
if (!yaml.ContainsKey(key))
|
if (!yaml.TryGetValue(key, out var value))
|
||||||
return new Dictionary<string, string>();
|
return new Dictionary<string, string>();
|
||||||
|
|
||||||
return yaml[key].ToDictionary(my => my.Value);
|
return value.ToDictionary(my => my.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains<T>() where T : IGlobalModData
|
public bool Contains<T>() where T : IGlobalModData
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public virtual void Initialize(MiniYaml yaml)
|
public virtual void Initialize(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
Initialize((T)FieldLoader.GetValue(nameof(value), typeof(T), yaml.Value));
|
Initialize(FieldLoader.GetValue<T>(nameof(value), yaml.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Initialize(T value)
|
public virtual void Initialize(T value)
|
||||||
@@ -175,8 +175,7 @@ namespace OpenRA
|
|||||||
protected CompositeActorInit(TraitInfo info)
|
protected CompositeActorInit(TraitInfo info)
|
||||||
: base(info.InstanceName) { }
|
: base(info.InstanceName) { }
|
||||||
|
|
||||||
protected CompositeActorInit()
|
protected CompositeActorInit() { }
|
||||||
: base() { }
|
|
||||||
|
|
||||||
public virtual void Initialize(MiniYaml yaml)
|
public virtual void Initialize(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
|
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
|
||||||
{
|
{
|
||||||
var ret = new MiniYaml(Type);
|
var nodes = new List<MiniYamlNode>();
|
||||||
foreach (var o in initDict.Value)
|
foreach (var o in initDict.Value)
|
||||||
{
|
{
|
||||||
if (o is not ActorInit init || o is ISuppressInitExport)
|
if (o is not ActorInit init || o is ISuppressInitExport)
|
||||||
@@ -98,10 +98,10 @@ namespace OpenRA
|
|||||||
if (!string.IsNullOrEmpty(init.InstanceName))
|
if (!string.IsNullOrEmpty(init.InstanceName))
|
||||||
initName += ActorInfo.TraitInstanceSeparator + init.InstanceName;
|
initName += ActorInfo.TraitInstanceSeparator + init.InstanceName;
|
||||||
|
|
||||||
ret.Nodes.Add(new MiniYamlNode(initName, init.Save()));
|
nodes.Add(new MiniYamlNode(initName, init.Save()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return new MiniYaml(Type, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<object> GetEnumerator() { return initDict.Value.GetEnumerator(); }
|
public IEnumerator<object> GetEnumerator() { return initDict.Value.GetEnumerator(); }
|
||||||
@@ -139,7 +139,7 @@ namespace OpenRA
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<T> GetAll<T>() where T : ActorInit
|
public IReadOnlyCollection<T> GetAll<T>() where T : ActorInit
|
||||||
{
|
{
|
||||||
return initDict.Value.WithInterface<T>();
|
return initDict.Value.WithInterface<T>();
|
||||||
}
|
}
|
||||||
@@ -152,8 +152,9 @@ namespace OpenRA
|
|||||||
// If a more specific init is not available, fall back to an unnamed init.
|
// 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 duplicate inits are defined, take the last to match standard yaml override expectations
|
||||||
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
|
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
|
||||||
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
return
|
||||||
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
||||||
|
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||||
|
|
||||||
// Untagged traits will only use untagged inits
|
// Untagged traits will only use untagged inits
|
||||||
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||||
|
|||||||
95
OpenRA.Game/Map/CellCoordsRegion.cs
Normal file
95
OpenRA.Game/Map/CellCoordsRegion.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright (c) The OpenRA Developers and Contributors
|
||||||
|
* 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.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace OpenRA
|
||||||
|
{
|
||||||
|
public readonly struct CellCoordsRegion : IEnumerable<CPos>
|
||||||
|
{
|
||||||
|
public struct CellCoordsEnumerator : IEnumerator<CPos>
|
||||||
|
{
|
||||||
|
readonly CellCoordsRegion r;
|
||||||
|
|
||||||
|
public CellCoordsEnumerator(CellCoordsRegion region)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
r = region;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
var x = Current.X + 1;
|
||||||
|
var y = Current.Y;
|
||||||
|
|
||||||
|
// Check for column overflow.
|
||||||
|
if (x > r.BottomRight.X)
|
||||||
|
{
|
||||||
|
y++;
|
||||||
|
x = r.TopLeft.X;
|
||||||
|
|
||||||
|
// Check for row overflow.
|
||||||
|
if (y > r.BottomRight.Y)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = new CPos(x, y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Current = new CPos(r.TopLeft.X - 1, r.TopLeft.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CPos Current { get; private set; }
|
||||||
|
|
||||||
|
readonly object IEnumerator.Current => Current;
|
||||||
|
public readonly void Dispose() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellCoordsRegion(CPos topLeft, CPos bottomRight)
|
||||||
|
{
|
||||||
|
TopLeft = topLeft;
|
||||||
|
BottomRight = bottomRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(CPos cell)
|
||||||
|
{
|
||||||
|
return cell.X >= TopLeft.X && cell.X <= BottomRight.X && cell.Y >= TopLeft.Y && cell.Y <= BottomRight.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{TopLeft}->{BottomRight}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellCoordsEnumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return new CellCoordsEnumerator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator<CPos> IEnumerable<CPos>.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CPos TopLeft { get; }
|
||||||
|
public CPos BottomRight { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
@@ -64,9 +63,9 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
|
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
|
||||||
public static CellRegion BoundingRegion(MapGridType shape, IEnumerable<CPos> cells)
|
public static CellRegion BoundingRegion(MapGridType shape, IReadOnlyCollection<CPos> cells)
|
||||||
{
|
{
|
||||||
if (cells == null || !cells.Any())
|
if (cells == null || cells.Count == 0)
|
||||||
throw new ArgumentException("cells must not be null or empty.", nameof(cells));
|
throw new ArgumentException("cells must not be null or empty.", nameof(cells));
|
||||||
|
|
||||||
var minU = int.MaxValue;
|
var minU = int.MaxValue;
|
||||||
@@ -103,6 +102,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MapCoordsRegion MapCoords => new(mapTopLeft, mapBottomRight);
|
public MapCoordsRegion MapCoords => new(mapTopLeft, mapBottomRight);
|
||||||
|
public CellCoordsRegion CellCoords => new(TopLeft, BottomRight);
|
||||||
|
|
||||||
public CellRegionEnumerator GetEnumerator()
|
public CellRegionEnumerator GetEnumerator()
|
||||||
{
|
{
|
||||||
@@ -136,12 +136,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
u += 1;
|
u++;
|
||||||
|
|
||||||
// Check for column overflow
|
// Check for column overflow
|
||||||
if (u > r.mapBottomRight.U)
|
if (u > r.mapBottomRight.U)
|
||||||
{
|
{
|
||||||
v += 1;
|
v++;
|
||||||
u = r.mapTopLeft.U;
|
u = r.mapTopLeft.U;
|
||||||
|
|
||||||
// Check for row overflow
|
// Check for row overflow
|
||||||
@@ -162,8 +162,8 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CPos Current { get; private set; }
|
public CPos Current { get; private set; }
|
||||||
object IEnumerator.Current => Current;
|
readonly object IEnumerator.Current => Current;
|
||||||
public void Dispose() { }
|
public readonly void Dispose() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -90,13 +91,13 @@ namespace OpenRA
|
|||||||
throw new InvalidOperationException("Map does not have a field/property " + fieldName);
|
throw new InvalidOperationException("Map does not have a field/property " + fieldName);
|
||||||
|
|
||||||
var t = field != null ? field.FieldType : property.PropertyType;
|
var t = field != null ? field.FieldType : property.PropertyType;
|
||||||
type = t == typeof(List<MiniYamlNode>) ? Type.NodeList :
|
type = t == typeof(IReadOnlyCollection<MiniYamlNode>) ? Type.NodeList :
|
||||||
t == typeof(MiniYaml) ? Type.MiniYaml : Type.Normal;
|
t == typeof(MiniYaml) ? Type.MiniYaml : Type.Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deserialize(Map map, List<MiniYamlNode> nodes)
|
public void Deserialize(Map map, MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var node = nodes.FirstOrDefault(n => n.Key == key);
|
var node = yaml.NodeWithKeyOrDefault(key);
|
||||||
if (node == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
if (required)
|
if (required)
|
||||||
@@ -130,14 +131,14 @@ namespace OpenRA
|
|||||||
var value = field != null ? field.GetValue(map) : property.GetValue(map, null);
|
var value = field != null ? field.GetValue(map) : property.GetValue(map, null);
|
||||||
if (type == Type.NodeList)
|
if (type == Type.NodeList)
|
||||||
{
|
{
|
||||||
var listValue = (List<MiniYamlNode>)value;
|
var listValue = (IReadOnlyCollection<MiniYamlNode>)value;
|
||||||
if (required || listValue.Count > 0)
|
if (required || listValue.Count > 0)
|
||||||
nodes.Add(new MiniYamlNode(key, null, listValue));
|
nodes.Add(new MiniYamlNode(key, null, listValue));
|
||||||
}
|
}
|
||||||
else if (type == Type.MiniYaml)
|
else if (type == Type.MiniYaml)
|
||||||
{
|
{
|
||||||
var yamlValue = (MiniYaml)value;
|
var yamlValue = (MiniYaml)value;
|
||||||
if (required || (yamlValue != null && (yamlValue.Value != null || yamlValue.Nodes.Count > 0)))
|
if (required || (yamlValue != null && (yamlValue.Value != null || yamlValue.Nodes.Length > 0)))
|
||||||
nodes.Add(new MiniYamlNode(key, yamlValue));
|
nodes.Add(new MiniYamlNode(key, yamlValue));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -158,26 +159,26 @@ namespace OpenRA
|
|||||||
/// <summary>Defines the order of the fields in map.yaml.</summary>
|
/// <summary>Defines the order of the fields in map.yaml.</summary>
|
||||||
static readonly MapField[] YamlFields =
|
static readonly MapField[] YamlFields =
|
||||||
{
|
{
|
||||||
new MapField("MapFormat"),
|
new("MapFormat"),
|
||||||
new MapField("RequiresMod"),
|
new("RequiresMod"),
|
||||||
new MapField("Title"),
|
new("Title"),
|
||||||
new MapField("Author"),
|
new("Author"),
|
||||||
new MapField("Tileset"),
|
new("Tileset"),
|
||||||
new MapField("MapSize"),
|
new("MapSize"),
|
||||||
new MapField("Bounds"),
|
new("Bounds"),
|
||||||
new MapField("Visibility"),
|
new("Visibility"),
|
||||||
new MapField("Categories"),
|
new("Categories"),
|
||||||
new MapField("LockPreview", required: false, ignoreIfValue: "False"),
|
new("LockPreview", required: false, ignoreIfValue: "False"),
|
||||||
new MapField("Players", "PlayerDefinitions"),
|
new("Players", nameof(PlayerDefinitions)),
|
||||||
new MapField("Actors", "ActorDefinitions"),
|
new("Actors", nameof(ActorDefinitions)),
|
||||||
new MapField("Rules", "RuleDefinitions", required: false),
|
new("Rules", nameof(RuleDefinitions), required: false),
|
||||||
new MapField("Translations", "TranslationDefinitions", required: false),
|
new("FluentMessages", nameof(FluentMessageDefinitions), required: false),
|
||||||
new MapField("Sequences", "SequenceDefinitions", required: false),
|
new("Sequences", nameof(SequenceDefinitions), required: false),
|
||||||
new MapField("ModelSequences", "ModelSequenceDefinitions", required: false),
|
new("ModelSequences", nameof(ModelSequenceDefinitions), required: false),
|
||||||
new MapField("Weapons", "WeaponDefinitions", required: false),
|
new("Weapons", nameof(WeaponDefinitions), required: false),
|
||||||
new MapField("Voices", "VoiceDefinitions", required: false),
|
new("Voices", nameof(VoiceDefinitions), required: false),
|
||||||
new MapField("Music", "MusicDefinitions", required: false),
|
new("Music", nameof(MusicDefinitions), required: false),
|
||||||
new MapField("Notifications", "NotificationDefinitions", required: false),
|
new("Notifications", nameof(NotificationDefinitions), required: false),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format versions
|
// Format versions
|
||||||
@@ -197,18 +198,18 @@ namespace OpenRA
|
|||||||
public int2 MapSize { get; private set; }
|
public int2 MapSize { get; private set; }
|
||||||
|
|
||||||
// Player and actor yaml. Public for access by the map importers and lint checks.
|
// Player and actor yaml. Public for access by the map importers and lint checks.
|
||||||
public List<MiniYamlNode> PlayerDefinitions = new();
|
public IReadOnlyCollection<MiniYamlNode> PlayerDefinitions = ImmutableArray<MiniYamlNode>.Empty;
|
||||||
public List<MiniYamlNode> ActorDefinitions = new();
|
public IReadOnlyCollection<MiniYamlNode> ActorDefinitions = ImmutableArray<MiniYamlNode>.Empty;
|
||||||
|
|
||||||
// Custom map yaml. Public for access by the map importers and lint checks
|
// Custom map yaml. Public for access by the map importers and lint checks
|
||||||
public readonly MiniYaml RuleDefinitions;
|
public MiniYaml RuleDefinitions;
|
||||||
public readonly MiniYaml TranslationDefinitions;
|
public MiniYaml FluentMessageDefinitions;
|
||||||
public readonly MiniYaml SequenceDefinitions;
|
public MiniYaml SequenceDefinitions;
|
||||||
public readonly MiniYaml ModelSequenceDefinitions;
|
public MiniYaml ModelSequenceDefinitions;
|
||||||
public readonly MiniYaml WeaponDefinitions;
|
public MiniYaml WeaponDefinitions;
|
||||||
public readonly MiniYaml VoiceDefinitions;
|
public MiniYaml VoiceDefinitions;
|
||||||
public readonly MiniYaml MusicDefinitions;
|
public MiniYaml MusicDefinitions;
|
||||||
public readonly MiniYaml NotificationDefinitions;
|
public MiniYaml NotificationDefinitions;
|
||||||
|
|
||||||
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new();
|
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new();
|
||||||
|
|
||||||
@@ -274,7 +275,10 @@ namespace OpenRA
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var filename in contents)
|
foreach (var filename in contents)
|
||||||
if (filename.EndsWith(".yaml") || filename.EndsWith(".bin") || filename.EndsWith(".lua") || (format >= 12 && filename == "map.png"))
|
if (filename.EndsWith(".yaml", StringComparison.Ordinal) ||
|
||||||
|
filename.EndsWith(".bin", StringComparison.Ordinal) ||
|
||||||
|
filename.EndsWith(".lua", StringComparison.Ordinal) ||
|
||||||
|
(format >= 12 && filename == "map.png"))
|
||||||
streams.Add(package.GetStream(filename));
|
streams.Add(package.GetStream(filename));
|
||||||
|
|
||||||
// Take the SHA1
|
// Take the SHA1
|
||||||
@@ -357,15 +361,15 @@ namespace OpenRA
|
|||||||
if (!Package.Contains("map.yaml") || !Package.Contains("map.bin"))
|
if (!Package.Contains("map.yaml") || !Package.Contains("map.bin"))
|
||||||
throw new InvalidDataException($"Not a valid map\n File: {package.Name}");
|
throw new InvalidDataException($"Not a valid map\n File: {package.Name}");
|
||||||
|
|
||||||
var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), package.Name));
|
var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), $"{package.Name}:map.yaml"));
|
||||||
foreach (var field in YamlFields)
|
foreach (var field in YamlFields)
|
||||||
field.Deserialize(this, yaml.Nodes);
|
field.Deserialize(this, yaml);
|
||||||
|
|
||||||
if (MapFormat < SupportedMapFormat)
|
if (MapFormat < SupportedMapFormat)
|
||||||
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
|
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
|
||||||
|
|
||||||
PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players");
|
PlayerDefinitions = yaml.NodeWithKeyOrDefault("Players")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
|
||||||
ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors");
|
ActorDefinitions = yaml.NodeWithKeyOrDefault("Actors")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
|
||||||
|
|
||||||
Grid = modData.Manifest.Get<MapGrid>();
|
Grid = modData.Manifest.Get<MapGrid>();
|
||||||
|
|
||||||
@@ -607,7 +611,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Odd-height ramps get bumped up a level to the next even height layer
|
// Odd-height ramps get bumped up a level to the next even height layer
|
||||||
if ((height & 1) == 1 && Ramp[uv] != 0)
|
if ((height & 1) == 1 && Ramp[uv] != 0)
|
||||||
height += 1;
|
height++;
|
||||||
|
|
||||||
var candidates = new List<PPos>();
|
var candidates = new List<PPos>();
|
||||||
|
|
||||||
@@ -646,21 +650,24 @@ namespace OpenRA
|
|||||||
foreach (var file in Package.Contents)
|
foreach (var file in Package.Contents)
|
||||||
toPackage.Update(file, Package.GetStream(file).ReadAllBytes());
|
toPackage.Update(file, Package.GetStream(file).ReadAllBytes());
|
||||||
|
|
||||||
if (!LockPreview)
|
void UpdatePackage(string filename, byte[] data)
|
||||||
{
|
{
|
||||||
var previewData = SavePreview();
|
if (Package != toPackage)
|
||||||
if (Package != toPackage || !Enumerable.SequenceEqual(previewData, Package.GetStream("map.png").ReadAllBytes()))
|
toPackage.Update(filename, data);
|
||||||
toPackage.Update("map.png", previewData);
|
else
|
||||||
|
{
|
||||||
|
var stream = Package.GetStream(filename);
|
||||||
|
if (stream == null || !Enumerable.SequenceEqual(data, stream.ReadAllBytes()))
|
||||||
|
toPackage.Update(filename, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the package with the new map data
|
if (!LockPreview)
|
||||||
var textData = Encoding.UTF8.GetBytes(root.WriteToString());
|
UpdatePackage("map.png", SavePreview());
|
||||||
if (Package != toPackage || !Enumerable.SequenceEqual(textData, Package.GetStream("map.yaml").ReadAllBytes()))
|
|
||||||
toPackage.Update("map.yaml", textData);
|
|
||||||
|
|
||||||
var binaryData = SaveBinaryData();
|
// Update the package with the new map data
|
||||||
if (Package != toPackage || !Enumerable.SequenceEqual(binaryData, Package.GetStream("map.bin").ReadAllBytes()))
|
UpdatePackage("map.yaml", Encoding.UTF8.GetBytes(root.WriteToString()));
|
||||||
toPackage.Update("map.bin", binaryData);
|
UpdatePackage("map.bin", SaveBinaryData());
|
||||||
|
|
||||||
Package = toPackage;
|
Package = toPackage;
|
||||||
|
|
||||||
@@ -681,16 +688,16 @@ namespace OpenRA
|
|||||||
writer.Write((ushort)MapSize.Y);
|
writer.Write((ushort)MapSize.Y);
|
||||||
|
|
||||||
// Data offsets
|
// Data offsets
|
||||||
var tilesOffset = 17;
|
const int TilesOffset = 17;
|
||||||
var heightsOffset = Grid.MaximumTerrainHeight > 0 ? 3 * MapSize.X * MapSize.Y + 17 : 0;
|
var heightsOffset = Grid.MaximumTerrainHeight > 0 ? 3 * MapSize.X * MapSize.Y + 17 : 0;
|
||||||
var resourcesOffset = (Grid.MaximumTerrainHeight > 0 ? 4 : 3) * MapSize.X * MapSize.Y + 17;
|
var resourcesOffset = (Grid.MaximumTerrainHeight > 0 ? 4 : 3) * MapSize.X * MapSize.Y + 17;
|
||||||
|
|
||||||
writer.Write((uint)tilesOffset);
|
writer.Write((uint)TilesOffset);
|
||||||
writer.Write((uint)heightsOffset);
|
writer.Write((uint)heightsOffset);
|
||||||
writer.Write((uint)resourcesOffset);
|
writer.Write((uint)resourcesOffset);
|
||||||
|
|
||||||
// Tile data
|
// Tile data
|
||||||
if (tilesOffset != 0)
|
if (TilesOffset != 0)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < MapSize.X; i++)
|
for (var i = 0; i < MapSize.X; i++)
|
||||||
{
|
{
|
||||||
@@ -772,19 +779,10 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (Grid.MaximumTerrainHeight > 0)
|
if (Grid.MaximumTerrainHeight > 0)
|
||||||
{
|
{
|
||||||
// The minimap is drawn in cell space, so we need to
|
(top, bottom) = GetCellSpaceBounds();
|
||||||
// unproject the PPos bounds to find the MPos boundaries.
|
|
||||||
// This matches the calculation in RadarWidget that is used ingame
|
|
||||||
for (var x = Bounds.Left; x < Bounds.Right; x++)
|
|
||||||
{
|
|
||||||
var allTop = Unproject(new PPos(x, Bounds.Top));
|
|
||||||
var allBottom = Unproject(new PPos(x, Bounds.Bottom));
|
|
||||||
if (allTop.Count > 0)
|
|
||||||
top = Math.Min(top, allTop.MinBy(uv => uv.V).V);
|
|
||||||
|
|
||||||
if (allBottom.Count > 0)
|
if (top == int.MaxValue || bottom == int.MinValue)
|
||||||
bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V);
|
throw new InvalidDataException("The map has invalid boundaries");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -801,7 +799,7 @@ namespace OpenRA
|
|||||||
bitmapWidth = 2 * bitmapWidth - 1;
|
bitmapWidth = 2 * bitmapWidth - 1;
|
||||||
|
|
||||||
var stride = bitmapWidth * 4;
|
var stride = bitmapWidth * 4;
|
||||||
var pxStride = 4;
|
const int PxStride = 4;
|
||||||
var minimapData = new byte[stride * height];
|
var minimapData = new byte[stride * height];
|
||||||
(Color Left, Color Right) terrainColor = default;
|
(Color Left, Color Right) terrainColor = default;
|
||||||
|
|
||||||
@@ -823,10 +821,10 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
// Odd rows are shifted right by 1px
|
// Odd rows are shifted right by 1px
|
||||||
var dx = uv.V & 1;
|
var dx = uv.V & 1;
|
||||||
var xOffset = pxStride * (2 * x + dx);
|
var xOffset = PxStride * (2 * x + dx);
|
||||||
if (x + dx > 0)
|
if (x + dx > 0)
|
||||||
{
|
{
|
||||||
var z = y * stride + xOffset - pxStride;
|
var z = y * stride + xOffset - PxStride;
|
||||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||||
minimapData[z++] = c.R;
|
minimapData[z++] = c.R;
|
||||||
minimapData[z++] = c.G;
|
minimapData[z++] = c.G;
|
||||||
@@ -846,7 +844,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var z = y * stride + pxStride * x;
|
var z = y * stride + PxStride * x;
|
||||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||||
minimapData[z++] = c.R;
|
minimapData[z++] = c.R;
|
||||||
minimapData[z++] = c.G;
|
minimapData[z++] = c.G;
|
||||||
@@ -860,6 +858,28 @@ namespace OpenRA
|
|||||||
return png.Save();
|
return png.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (int Top, int Bottom) GetCellSpaceBounds()
|
||||||
|
{
|
||||||
|
var top = int.MaxValue;
|
||||||
|
var bottom = int.MinValue;
|
||||||
|
|
||||||
|
// The minimap is drawn in cell space, so we need to
|
||||||
|
// unproject the PPos bounds to find the MPos boundaries.
|
||||||
|
// This matches the calculation in RadarWidget that is used ingame
|
||||||
|
for (var x = Bounds.Left; x < Bounds.Right; x++)
|
||||||
|
{
|
||||||
|
var allTop = Unproject(new PPos(x, Bounds.Top));
|
||||||
|
var allBottom = Unproject(new PPos(x, Bounds.Bottom));
|
||||||
|
if (allTop.Count > 0)
|
||||||
|
top = Math.Min(top, allTop.MinBy(uv => uv.V).V);
|
||||||
|
|
||||||
|
if (allBottom.Count > 0)
|
||||||
|
bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (top, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
public bool Contains(CPos cell)
|
public bool Contains(CPos cell)
|
||||||
{
|
{
|
||||||
if (Grid.Type == MapGridType.RectangularIsometric)
|
if (Grid.Type == MapGridType.RectangularIsometric)
|
||||||
@@ -1180,7 +1200,7 @@ namespace OpenRA
|
|||||||
// Project this guessed cell and take the first available cell
|
// Project this guessed cell and take the first available cell
|
||||||
// If it is projected outside the layer, then make another guess.
|
// If it is projected outside the layer, then make another guess.
|
||||||
var allProjected = ProjectedCellsCovering(uv);
|
var allProjected = ProjectedCellsCovering(uv);
|
||||||
var projected = allProjected.Length > 0 ? allProjected.First()
|
var projected = allProjected.Length > 0 ? allProjected[0]
|
||||||
: new PPos(uv.U, uv.V.Clamp(Bounds.Top, Bounds.Bottom));
|
: new PPos(uv.U, uv.V.Clamp(Bounds.Top, Bounds.Bottom));
|
||||||
|
|
||||||
// Clamp the projected cell to the map area
|
// Clamp the projected cell to the map area
|
||||||
@@ -1249,7 +1269,7 @@ namespace OpenRA
|
|||||||
PPos edge;
|
PPos edge;
|
||||||
if (allProjected.Length > 0)
|
if (allProjected.Length > 0)
|
||||||
{
|
{
|
||||||
var puv = allProjected.First();
|
var puv = allProjected[0];
|
||||||
var horizontalBound = (puv.U - Bounds.Left < Bounds.Width / 2) ? Bounds.Left : Bounds.Right;
|
var horizontalBound = (puv.U - Bounds.Left < Bounds.Width / 2) ? Bounds.Left : Bounds.Right;
|
||||||
var verticalBound = (puv.V - Bounds.Top < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom;
|
var verticalBound = (puv.V - Bounds.Top < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom;
|
||||||
|
|
||||||
@@ -1349,13 +1369,18 @@ namespace OpenRA
|
|||||||
throw new ArgumentOutOfRangeException(nameof(maxRange),
|
throw new ArgumentOutOfRangeException(nameof(maxRange),
|
||||||
$"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})");
|
$"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})");
|
||||||
|
|
||||||
for (var i = minRange; i <= maxRange; i++)
|
return FindTilesInAnnulus();
|
||||||
|
|
||||||
|
IEnumerable<CPos> FindTilesInAnnulus()
|
||||||
{
|
{
|
||||||
foreach (var offset in Grid.TilesByDistance[i])
|
for (var i = minRange; i <= maxRange; i++)
|
||||||
{
|
{
|
||||||
var t = offset + center;
|
foreach (var offset in Grid.TilesByDistance[i])
|
||||||
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
|
{
|
||||||
yield return t;
|
var t = offset + center;
|
||||||
|
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
|
||||||
|
yield return t;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1402,11 +1427,11 @@ namespace OpenRA
|
|||||||
return modData.DefaultFileSystem.Exists(filename);
|
return modData.DefaultFileSystem.Exists(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsExternalModFile(string filename)
|
public bool IsExternalFile(string filename)
|
||||||
{
|
{
|
||||||
// Explicit package paths never refer to a map
|
// Explicit package paths never refer to a map
|
||||||
if (filename.Contains('|'))
|
if (filename.Contains('|'))
|
||||||
return modData.DefaultFileSystem.IsExternalModFile(filename);
|
return modData.DefaultFileSystem.IsExternalFile(filename);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
@@ -38,7 +39,7 @@ namespace OpenRA
|
|||||||
readonly object syncRoot = new();
|
readonly object syncRoot = new();
|
||||||
readonly Queue<MapPreview> generateMinimap = new();
|
readonly Queue<MapPreview> generateMinimap = new();
|
||||||
|
|
||||||
public Dictionary<string, string> StringPool { get; } = new Dictionary<string, string>();
|
public HashSet<string> StringPool { get; } = new();
|
||||||
|
|
||||||
readonly List<MapDirectoryTracker> mapDirectoryTrackers = new();
|
readonly List<MapDirectoryTracker> mapDirectoryTrackers = new();
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ namespace OpenRA
|
|||||||
? MapClassification.Unknown : Enum<MapClassification>.Parse(kv.Value);
|
? MapClassification.Unknown : Enum<MapClassification>.Parse(kv.Value);
|
||||||
|
|
||||||
IReadOnlyPackage package;
|
IReadOnlyPackage package;
|
||||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
var optional = name.StartsWith('~');
|
||||||
if (optional)
|
if (optional)
|
||||||
name = name[1..];
|
name = name[1..];
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ namespace OpenRA
|
|||||||
// HACK: If the path is inside the support directory then we may need to create it
|
// HACK: If the path is inside the support directory then we may need to create it
|
||||||
// Assume that the path is a directory if there is not an existing file with the same name
|
// Assume that the path is a directory if there is not an existing file with the same name
|
||||||
var resolved = Platform.ResolvePath(name);
|
var resolved = Platform.ResolvePath(name);
|
||||||
if (resolved.StartsWith(Platform.SupportDir) && !File.Exists(resolved))
|
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && !File.Exists(resolved))
|
||||||
Directory.CreateDirectory(resolved);
|
Directory.CreateDirectory(resolved);
|
||||||
|
|
||||||
package = modData.ModFiles.OpenPackage(name);
|
package = modData.ModFiles.OpenPackage(name);
|
||||||
@@ -140,7 +141,8 @@ namespace OpenRA
|
|||||||
LoadMapInternal(map, package, classification, mapGrid, oldMap, null);
|
LoadMapInternal(map, package, classification, mapGrid, oldMap, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap, IEnumerable<List<MiniYamlNode>> modDataRules)
|
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap,
|
||||||
|
IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||||
{
|
{
|
||||||
IReadOnlyPackage mapPackage = null;
|
IReadOnlyPackage mapPackage = null;
|
||||||
try
|
try
|
||||||
@@ -153,6 +155,9 @@ namespace OpenRA
|
|||||||
var uid = Map.ComputeUID(mapPackage);
|
var uid = Map.ComputeUID(mapPackage);
|
||||||
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type, modDataRules);
|
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type, modDataRules);
|
||||||
|
|
||||||
|
// Freeing the package to save memory if there is a lot of Maps
|
||||||
|
previews[uid].DisposePackage();
|
||||||
|
|
||||||
if (oldMap != uid)
|
if (oldMap != uid)
|
||||||
{
|
{
|
||||||
LastModifiedMap = uid;
|
LastModifiedMap = uid;
|
||||||
@@ -190,13 +195,13 @@ namespace OpenRA
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var name = kv.Key;
|
var name = kv.Key;
|
||||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
var optional = name.StartsWith('~');
|
||||||
if (optional)
|
if (optional)
|
||||||
name = name[1..];
|
name = name[1..];
|
||||||
|
|
||||||
// Don't try to open the map directory in the support directory if it doesn't exist
|
// Don't try to open the map directory in the support directory if it doesn't exist
|
||||||
var resolved = Platform.ResolvePath(name);
|
var resolved = Platform.ResolvePath(name);
|
||||||
if (resolved.StartsWith(Platform.SupportDir) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
|
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
|
||||||
@@ -223,7 +228,8 @@ namespace OpenRA
|
|||||||
yield return mapPackage;
|
yield return mapPackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action<MapPreview> mapQueryFailed = null)
|
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids,
|
||||||
|
Action<MapPreview> mapDetailsReceived = null, Action<MapPreview> mapQueryFailed = null)
|
||||||
{
|
{
|
||||||
var queryUids = uids.Distinct()
|
var queryUids = uids.Distinct()
|
||||||
.Where(uid => uid != null)
|
.Where(uid => uid != null)
|
||||||
@@ -233,44 +239,67 @@ namespace OpenRA
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var uid in queryUids)
|
foreach (var uid in queryUids)
|
||||||
previews[uid].UpdateRemoteSearch(MapStatus.Searching, null);
|
previews[uid].UpdateRemoteSearch(MapStatus.Searching, null, null);
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var client = HttpClientFactory.Create();
|
var client = HttpClientFactory.Create();
|
||||||
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
// Limit each query to 50 maps at a time to avoid request size limits
|
// Limit each query to 50 maps at a time to avoid request size limits
|
||||||
for (var i = 0; i < queryUids.Count; i += 50)
|
for (var i = 0; i < queryUids.Count; i += 50)
|
||||||
{
|
{
|
||||||
var batchUids = queryUids.Skip(i).Take(50).ToList();
|
var batchUids = queryUids.Skip(i).Take(50).ToList();
|
||||||
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
|
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
|
||||||
try
|
|
||||||
|
using (new PerfTimer("RemoteMapDetails"))
|
||||||
{
|
{
|
||||||
var httpResponseMessage = await client.GetAsync(url);
|
try
|
||||||
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
|
||||||
|
|
||||||
var yaml = MiniYaml.FromStream(result);
|
|
||||||
foreach (var kv in yaml)
|
|
||||||
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
|
|
||||||
|
|
||||||
foreach (var uid in batchUids)
|
|
||||||
{
|
{
|
||||||
var p = previews[uid];
|
await using (var resultStream = await client.GetStreamAsync(url))
|
||||||
if (p.Status != MapStatus.DownloadAvailable)
|
{
|
||||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
using (var resultReader = new StreamReader(resultStream))
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var line = await resultReader.ReadLineAsync();
|
||||||
|
if (line == null || !line.StartsWith('\t'))
|
||||||
|
{
|
||||||
|
var yaml = MiniYaml.FromString(buffer.ToString(), url, stringPool: stringPool);
|
||||||
|
buffer.Clear();
|
||||||
|
foreach (var kv in yaml)
|
||||||
|
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, modData.Manifest.MapCompatibility, mapDetailsReceived);
|
||||||
|
|
||||||
|
if (line == null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.Append(line);
|
||||||
|
buffer.Append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var uid in batchUids)
|
||||||
|
{
|
||||||
|
var p = previews[uid];
|
||||||
|
if (p.Status != MapStatus.DownloadAvailable)
|
||||||
|
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
catch (Exception e)
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Write("debug", "Remote map query failed with error:");
|
|
||||||
Log.Write("debug", e);
|
|
||||||
Log.Write("debug", $"URL was: {url}");
|
|
||||||
|
|
||||||
foreach (var uid in batchUids)
|
|
||||||
{
|
{
|
||||||
var p = previews[uid];
|
Log.Write("debug", "Remote map query failed with error:");
|
||||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
Log.Write("debug", e);
|
||||||
mapQueryFailed?.Invoke(p);
|
Log.Write("debug", $"URL was: {url}");
|
||||||
|
|
||||||
|
foreach (var uid in batchUids)
|
||||||
|
{
|
||||||
|
var p = previews[uid];
|
||||||
|
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
|
||||||
|
mapQueryFailed?.Invoke(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,11 +311,11 @@ namespace OpenRA
|
|||||||
Log.Write("debug", "MapCache.LoadAsyncInternal started");
|
Log.Write("debug", "MapCache.LoadAsyncInternal started");
|
||||||
|
|
||||||
// Milliseconds to wait on one loop when nothing to do
|
// Milliseconds to wait on one loop when nothing to do
|
||||||
var emptyDelay = 50;
|
const int EmptyDelay = 50;
|
||||||
|
|
||||||
// Keep the thread alive for at least 5 seconds after the last minimap generation
|
// Keep the thread alive for at least 5 seconds after the last minimap generation
|
||||||
var maxKeepAlive = 5000 / emptyDelay;
|
const int MaxKeepAlive = 5000 / EmptyDelay;
|
||||||
var keepAlive = maxKeepAlive;
|
var keepAlive = MaxKeepAlive;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -306,11 +335,11 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (todo.Count == 0)
|
if (todo.Count == 0)
|
||||||
{
|
{
|
||||||
Thread.Sleep(emptyDelay);
|
Thread.Sleep(EmptyDelay);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
keepAlive = maxKeepAlive;
|
keepAlive = MaxKeepAlive;
|
||||||
|
|
||||||
// Render the minimap into the shared sheet
|
// Render the minimap into the shared sheet
|
||||||
foreach (var p in todo)
|
foreach (var p in todo)
|
||||||
@@ -348,8 +377,8 @@ namespace OpenRA
|
|||||||
|
|
||||||
while (this[uid].Status != MapStatus.Available)
|
while (this[uid].Status != MapStatus.Available)
|
||||||
{
|
{
|
||||||
if (mapUpdates.ContainsKey(uid))
|
if (mapUpdates.TryGetValue(uid, out var newUid))
|
||||||
uid = mapUpdates[uid];
|
uid = newUid;
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -406,10 +435,16 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
UpdateMaps();
|
UpdateMaps();
|
||||||
var map = string.IsNullOrEmpty(initialUid) ? null : previews[initialUid];
|
var map = string.IsNullOrEmpty(initialUid) ? null : previews[initialUid];
|
||||||
if (map == null || map.Status != MapStatus.Available || !map.Visibility.HasFlag(MapVisibility.Lobby) || (map.Class != MapClassification.System && map.Class != MapClassification.User))
|
if (map == null ||
|
||||||
|
map.Status != MapStatus.Available ||
|
||||||
|
!map.Visibility.HasFlag(MapVisibility.Lobby) ||
|
||||||
|
(map.Class != MapClassification.System && map.Class != MapClassification.User))
|
||||||
{
|
{
|
||||||
var selected = previews.Values.Where(IsSuitableInitialMap).RandomOrDefault(random) ??
|
var selected = previews.Values.Where(IsSuitableInitialMap).RandomOrDefault(random) ??
|
||||||
previews.Values.FirstOrDefault(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Lobby) && (m.Class == MapClassification.System || m.Class == MapClassification.User));
|
previews.Values.FirstOrDefault(m =>
|
||||||
|
m.Status == MapStatus.Available &&
|
||||||
|
m.Visibility.HasFlag(MapVisibility.Lobby) &&
|
||||||
|
(m.Class == MapClassification.System || m.Class == MapClassification.User));
|
||||||
return selected == null ? string.Empty : selected.Uid;
|
return selected == null ? string.Empty : selected.Uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace OpenRA
|
|||||||
// Check for column overflow
|
// Check for column overflow
|
||||||
if (u > r.BottomRight.U)
|
if (u > r.BottomRight.U)
|
||||||
{
|
{
|
||||||
v += 1;
|
v++;
|
||||||
u = r.TopLeft.U;
|
u = r.TopLeft.U;
|
||||||
|
|
||||||
// Check for row overflow
|
// Check for row overflow
|
||||||
@@ -53,8 +53,8 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MPos Current { get; private set; }
|
public MPos Current { get; private set; }
|
||||||
object IEnumerator.Current => Current;
|
readonly object IEnumerator.Current => Current;
|
||||||
public void Dispose() { }
|
public readonly void Dispose() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapCoordsRegion(MPos mapTopLeft, MPos mapBottomRight)
|
public MapCoordsRegion(MPos mapTopLeft, MPos mapBottomRight)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ namespace OpenRA
|
|||||||
dirty = false;
|
dirty = false;
|
||||||
foreach (var mapAction in mapActionQueue)
|
foreach (var mapAction in mapActionQueue)
|
||||||
{
|
{
|
||||||
var map = mapcache.FirstOrDefault(x => x.Package?.Name == mapAction.Key && x.Status == MapStatus.Available);
|
var map = mapcache.FirstOrDefault(x => x.PackageName == mapAction.Key && x.Status == MapStatus.Available);
|
||||||
if (map != null)
|
if (map != null)
|
||||||
{
|
{
|
||||||
if (mapAction.Value == MapAction.Delete)
|
if (mapAction.Value == MapAction.Delete)
|
||||||
|
|||||||
@@ -116,12 +116,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
public readonly WVec[] SubCellOffsets =
|
public readonly WVec[] SubCellOffsets =
|
||||||
{
|
{
|
||||||
new WVec(0, 0, 0), // full cell - index 0
|
new(0, 0, 0), // full cell - index 0
|
||||||
new WVec(-299, -256, 0), // top left - index 1
|
new(-299, -256, 0), // top left - index 1
|
||||||
new WVec(256, -256, 0), // top right - index 2
|
new(256, -256, 0), // top right - index 2
|
||||||
new WVec(0, 0, 0), // center - index 3
|
new(0, 0, 0), // center - index 3
|
||||||
new WVec(-299, 256, 0), // bottom left - index 4
|
new(-299, 256, 0), // bottom left - index 4
|
||||||
new WVec(256, 256, 0), // bottom right - index 5
|
new(256, 256, 0), // bottom right - index 5
|
||||||
};
|
};
|
||||||
|
|
||||||
public CellRamp[] Ramps { get; }
|
public CellRamp[] Ramps { get; }
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ namespace OpenRA
|
|||||||
public readonly string rules;
|
public readonly string rules;
|
||||||
public readonly string players_block;
|
public readonly string players_block;
|
||||||
public readonly int mapformat;
|
public readonly int mapformat;
|
||||||
|
public readonly string game_mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MapPreview : IDisposable, IReadOnlyFileSystem
|
public sealed class MapPreview : IDisposable, IReadOnlyFileSystem
|
||||||
@@ -89,8 +90,9 @@ namespace OpenRA
|
|||||||
public MiniYaml NotificationDefinitions;
|
public MiniYaml NotificationDefinitions;
|
||||||
public MiniYaml SequenceDefinitions;
|
public MiniYaml SequenceDefinitions;
|
||||||
public MiniYaml ModelSequenceDefinitions;
|
public MiniYaml ModelSequenceDefinitions;
|
||||||
|
public MiniYaml FluentMessageDefinitions;
|
||||||
|
|
||||||
public Translation Translation { get; private set; }
|
public FluentBundle FluentBundle { get; private set; }
|
||||||
public ActorInfo WorldActorInfo { get; private set; }
|
public ActorInfo WorldActorInfo { get; private set; }
|
||||||
public ActorInfo PlayerActorInfo { get; private set; }
|
public ActorInfo PlayerActorInfo { get; private set; }
|
||||||
|
|
||||||
@@ -120,13 +122,32 @@ namespace OpenRA
|
|||||||
NotificationDefinitions = LoadRuleSection(yaml, "Notifications");
|
NotificationDefinitions = LoadRuleSection(yaml, "Notifications");
|
||||||
SequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
SequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
||||||
ModelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences");
|
ModelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences");
|
||||||
|
FluentMessageDefinitions = LoadRuleSection(yaml, "FluentMessages");
|
||||||
Translation = yaml.TryGetValue("Translations", out var node) && node != null
|
|
||||||
? new Translation(Game.Settings.Player.Language, FieldLoader.GetValue<string[]>("value", node.Value), fileSystem)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (FluentMessageDefinitions != null)
|
||||||
|
{
|
||||||
|
var files = Array.Empty<string>();
|
||||||
|
if (FluentMessageDefinitions.Value != null)
|
||||||
|
files = FieldLoader.GetValue<string[]>("value", FluentMessageDefinitions.Value);
|
||||||
|
|
||||||
|
string text = null;
|
||||||
|
if (FluentMessageDefinitions.Nodes.Length > 0)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var node in FluentMessageDefinitions.Nodes)
|
||||||
|
if (node.Key == "base64")
|
||||||
|
builder.Append(Encoding.UTF8.GetString(Convert.FromBase64String(node.Value.Value)));
|
||||||
|
|
||||||
|
text = builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
FluentBundle = new FluentBundle(modData.Manifest.FluentCulture, files, fileSystem, text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
FluentBundle = null;
|
||||||
|
|
||||||
// PERF: Implement a minimal custom loader for custom world and player actors to minimize loading time
|
// PERF: Implement a minimal custom loader for custom world and player actors to minimize loading time
|
||||||
// This assumes/enforces that these actor types can only inherit abstract definitions (starting with ^)
|
// This assumes/enforces that these actor types can only inherit abstract definitions (starting with ^)
|
||||||
if (RuleDefinitions != null)
|
if (RuleDefinitions != null)
|
||||||
@@ -139,15 +160,22 @@ namespace OpenRA
|
|||||||
files = files.Append(mapFiles);
|
files = files.Append(mapFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
var sources =
|
var sources =
|
||||||
modDataRules.Select(x => x.Where(IsLoadableRuleDefinition).ToList())
|
modDataRules.Select(x => x.Where(IsLoadableRuleDefinition).ToList())
|
||||||
.Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s).Where(IsLoadableRuleDefinition).ToList()));
|
.Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool).Where(IsLoadableRuleDefinition).ToList()));
|
||||||
if (RuleDefinitions.Nodes.Count > 0)
|
if (RuleDefinitions.Nodes.Length > 0)
|
||||||
sources = sources.Append(RuleDefinitions.Nodes.Where(IsLoadableRuleDefinition).ToList());
|
sources = sources.Append(RuleDefinitions.Nodes.Where(IsLoadableRuleDefinition).ToList());
|
||||||
|
|
||||||
var yamlNodes = MiniYaml.Merge(sources);
|
var yamlNodes = MiniYaml.Merge(sources);
|
||||||
WorldActorInfo = new ActorInfo(modData.ObjectCreator, "world", yamlNodes.First(n => string.Equals(n.Key, "world", StringComparison.InvariantCultureIgnoreCase)).Value);
|
WorldActorInfo = new ActorInfo(
|
||||||
PlayerActorInfo = new ActorInfo(modData.ObjectCreator, "player", yamlNodes.First(n => string.Equals(n.Key, "player", StringComparison.InvariantCultureIgnoreCase)).Value);
|
modData.ObjectCreator,
|
||||||
|
"world",
|
||||||
|
yamlNodes.First(n => string.Equals(n.Key, "world", StringComparison.InvariantCultureIgnoreCase)).Value);
|
||||||
|
PlayerActorInfo = new ActorInfo(
|
||||||
|
modData.ObjectCreator,
|
||||||
|
"player",
|
||||||
|
yamlNodes.First(n => string.Equals(n.Key, "player", StringComparison.InvariantCultureIgnoreCase)).Value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,7 +200,19 @@ namespace OpenRA
|
|||||||
readonly ModData modData;
|
readonly ModData modData;
|
||||||
|
|
||||||
public readonly string Uid;
|
public readonly string Uid;
|
||||||
public IReadOnlyPackage Package { get; private set; }
|
public string PackageName { get; private set; }
|
||||||
|
IReadOnlyPackage package;
|
||||||
|
public IReadOnlyPackage Package
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
package ??= parentPackage.OpenPackage(PackageName, modData.ModFiles);
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
|
||||||
|
private set => package = value;
|
||||||
|
}
|
||||||
|
|
||||||
IReadOnlyPackage parentPackage;
|
IReadOnlyPackage parentPackage;
|
||||||
|
|
||||||
volatile InnerData innerData;
|
volatile InnerData innerData;
|
||||||
@@ -204,16 +244,31 @@ namespace OpenRA
|
|||||||
public int DownloadPercentage { get; private set; }
|
public int DownloadPercentage { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Functionality mirrors <see cref="TranslationProvider.GetString"/>, except instead of using
|
/// Functionality mirrors <see cref="FluentProvider.GetMessage"/>, except instead of using
|
||||||
/// loaded <see cref="Map"/>'s translations as backup, we use this <see cref="MapPreview"/>'s.
|
/// loaded <see cref="Map"/>'s fluent bundle as backup, we use this <see cref="MapPreview"/>'s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string GetLocalisedString(string key, IDictionary<string, object> args = null)
|
public string GetMessage(string key, object[] args = null)
|
||||||
{
|
{
|
||||||
// PERF: instead of loading mod level Translation per each MapPreview, reuse the already loaded one in TranslationProvider.
|
if (TryGetMessage(key, out var message, args))
|
||||||
if (TranslationProvider.TryGetModString(key, out var message, args))
|
|
||||||
return message;
|
return message;
|
||||||
|
|
||||||
return innerData.Translation?.GetString(key, args) ?? key;
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Functionality mirrors <see cref="FluentProvider.TryGetMessage"/>, except instead of using
|
||||||
|
/// loaded <see cref="Map"/>'s fluent bundle as backup, we use this <see cref="MapPreview"/>'s.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetMessage(string key, out string message, object[] args = null)
|
||||||
|
{
|
||||||
|
// PERF: instead of loading mod level strings per each MapPreview, reuse the already loaded one in FluentProvider.
|
||||||
|
if (FluentProvider.TryGetModMessage(key, out message, args))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (innerData.FluentBundle == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return innerData.FluentBundle.TryGetMessage(key, out message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sprite minimap;
|
Sprite minimap;
|
||||||
@@ -284,7 +339,7 @@ namespace OpenRA
|
|||||||
cache = modData.MapCache;
|
cache = modData.MapCache;
|
||||||
|
|
||||||
Uid = map.Uid;
|
Uid = map.Uid;
|
||||||
Package = map.Package;
|
PackageName = map.Package.Name;
|
||||||
|
|
||||||
var mapPlayers = new MapPlayers(map.PlayerDefinitions);
|
var mapPlayers = new MapPlayers(map.PlayerDefinitions);
|
||||||
var spawns = new List<CPos>();
|
var spawns = new List<CPos>();
|
||||||
@@ -315,7 +370,7 @@ namespace OpenRA
|
|||||||
innerData.SetCustomRules(modData, this, new Dictionary<string, MiniYaml>()
|
innerData.SetCustomRules(modData, this, new Dictionary<string, MiniYaml>()
|
||||||
{
|
{
|
||||||
{ "Rules", map.RuleDefinitions },
|
{ "Rules", map.RuleDefinitions },
|
||||||
{ "Translations", map.TranslationDefinitions },
|
{ "FluentMessages", map.FluentMessageDefinitions },
|
||||||
{ "Weapons", map.WeaponDefinitions },
|
{ "Weapons", map.WeaponDefinitions },
|
||||||
{ "Voices", map.VoiceDefinitions },
|
{ "Voices", map.VoiceDefinitions },
|
||||||
{ "Music", map.MusicDefinitions },
|
{ "Music", map.MusicDefinitions },
|
||||||
@@ -325,7 +380,8 @@ namespace OpenRA
|
|||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType, IEnumerable<List<MiniYamlNode>> modDataRules)
|
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification,
|
||||||
|
string[] mapCompatibility, MapGridType gridType, IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||||
{
|
{
|
||||||
Dictionary<string, MiniYaml> yaml;
|
Dictionary<string, MiniYaml> yaml;
|
||||||
using (var yamlStream = p.GetStream("map.yaml"))
|
using (var yamlStream = p.GetStream("map.yaml"))
|
||||||
@@ -333,10 +389,10 @@ namespace OpenRA
|
|||||||
if (yamlStream == null)
|
if (yamlStream == null)
|
||||||
throw new FileNotFoundException("Required file map.yaml not present in this map");
|
throw new FileNotFoundException("Required file map.yaml not present in this map");
|
||||||
|
|
||||||
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, $"{p.Name}:map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
Package = p;
|
PackageName = p.Name;
|
||||||
parentPackage = parent;
|
parentPackage = parent;
|
||||||
|
|
||||||
var newData = innerData.Clone();
|
var newData = innerData.Clone();
|
||||||
@@ -427,7 +483,7 @@ namespace OpenRA
|
|||||||
innerData = newData;
|
innerData = newData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action<MapPreview> parseMetadata = null)
|
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, string[] mapCompatibility, Action<MapPreview> parseMetadata = null)
|
||||||
{
|
{
|
||||||
var newData = innerData.Clone();
|
var newData = innerData.Clone();
|
||||||
newData.Status = status;
|
newData.Status = status;
|
||||||
@@ -474,11 +530,19 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
|
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
|
||||||
newData.Players = new MapPlayers(MiniYaml.FromString(playersString));
|
newData.Players = new MapPlayers(MiniYaml.FromString(playersString,
|
||||||
|
$"{yaml.NodeWithKey(nameof(r.players_block)).Location.Name}:{nameof(r.players_block)}"));
|
||||||
|
|
||||||
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
|
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
|
||||||
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary();
|
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString,
|
||||||
|
$"{yaml.NodeWithKey(nameof(r.rules)).Location.Name}:{nameof(r.rules)}")).ToDictionary();
|
||||||
newData.SetCustomRules(modData, this, rulesYaml, null);
|
newData.SetCustomRules(modData, this, rulesYaml, null);
|
||||||
|
|
||||||
|
// Map is for a different mod: update its information so it can be displayed
|
||||||
|
// in the cross-mod server browser UI, but mark it as unavailable so it can't
|
||||||
|
// be selected in a server for the current mod.
|
||||||
|
if (!mapCompatibility.Contains(r.game_mod))
|
||||||
|
newData.Status = MapStatus.Unavailable;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -577,10 +641,15 @@ namespace OpenRA
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (Package != null)
|
DisposePackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisposePackage()
|
||||||
|
{
|
||||||
|
if (package != null)
|
||||||
{
|
{
|
||||||
Package.Dispose();
|
package.Dispose();
|
||||||
Package = null;
|
package = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,11 +696,11 @@ namespace OpenRA
|
|||||||
return modData.DefaultFileSystem.Exists(filename);
|
return modData.DefaultFileSystem.Exists(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IReadOnlyFileSystem.IsExternalModFile(string filename)
|
bool IReadOnlyFileSystem.IsExternalFile(string filename)
|
||||||
{
|
{
|
||||||
// Explicit package paths never refer to a map
|
// Explicit package paths never refer to a map
|
||||||
if (filename.Contains('|'))
|
if (filename.Contains('|'))
|
||||||
return modData.DefaultFileSystem.IsExternalModFile(filename);
|
return modData.DefaultFileSystem.IsExternalFile(filename);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
@@ -53,5 +54,15 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
return Bounds.Contains(uv.U, uv.V);
|
return Bounds.Contains(uv.U, uv.V);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int IndexOf(T value, int startIndex)
|
||||||
|
{
|
||||||
|
return Array.IndexOf(Entries, value, startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAll(T value)
|
||||||
|
{
|
||||||
|
Array.Fill(Entries, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,12 +93,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
u += 1;
|
u++;
|
||||||
|
|
||||||
// Check for column overflow
|
// Check for column overflow
|
||||||
if (u > r.BottomRight.U)
|
if (u > r.BottomRight.U)
|
||||||
{
|
{
|
||||||
v += 1;
|
v++;
|
||||||
u = r.TopLeft.U;
|
u = r.TopLeft.U;
|
||||||
|
|
||||||
// Check for row overflow
|
// Check for row overflow
|
||||||
@@ -118,8 +118,8 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PPos Current { get; private set; }
|
public PPos Current { get; private set; }
|
||||||
object IEnumerator.Current => Current;
|
readonly object IEnumerator.Current => Current;
|
||||||
public void Dispose() { }
|
public readonly void Dispose() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,18 +20,36 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static class MiniYamlExts
|
public static class MiniYamlExts
|
||||||
{
|
{
|
||||||
public static void WriteToFile(this List<MiniYamlNode> y, string filename)
|
public static void WriteToFile(this IEnumerable<MiniYamlNode> y, string filename)
|
||||||
{
|
{
|
||||||
File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray());
|
File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string WriteToString(this List<MiniYamlNode> y)
|
public static string WriteToString(this IEnumerable<MiniYamlNode> y)
|
||||||
{
|
{
|
||||||
// Remove all trailing newlines and restore the final EOF newline
|
// Remove all trailing newlines and restore the final EOF newline
|
||||||
return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n";
|
return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<string> ToLines(this List<MiniYamlNode> y)
|
public static IEnumerable<string> ToLines(this IEnumerable<MiniYamlNode> y)
|
||||||
|
{
|
||||||
|
foreach (var kv in y)
|
||||||
|
foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment))
|
||||||
|
yield return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteToFile(this IEnumerable<MiniYamlNodeBuilder> y, string filename)
|
||||||
|
{
|
||||||
|
File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string WriteToString(this IEnumerable<MiniYamlNodeBuilder> y)
|
||||||
|
{
|
||||||
|
// Remove all trailing newlines and restore the final EOF newline
|
||||||
|
return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> ToLines(this IEnumerable<MiniYamlNodeBuilder> y)
|
||||||
{
|
{
|
||||||
foreach (var kv in y)
|
foreach (var kv in y)
|
||||||
foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment))
|
foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment))
|
||||||
@@ -43,22 +61,29 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public readonly struct SourceLocation
|
public readonly struct SourceLocation
|
||||||
{
|
{
|
||||||
public readonly string Filename;
|
public readonly string Name;
|
||||||
public readonly int Line;
|
public readonly int Line;
|
||||||
|
|
||||||
public SourceLocation(string filename, int line)
|
public SourceLocation(string name, int line)
|
||||||
{
|
{
|
||||||
Filename = filename;
|
Name = name;
|
||||||
Line = line;
|
Line = line;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() { return $"{Filename}:{Line}"; }
|
public override string ToString() { return $"{Name}:{Line}"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceLocation Location;
|
public readonly SourceLocation Location;
|
||||||
public string Key;
|
public readonly string Key;
|
||||||
public MiniYaml Value;
|
public readonly MiniYaml Value;
|
||||||
public string Comment;
|
public readonly string Comment;
|
||||||
|
|
||||||
|
public MiniYamlNode WithValue(MiniYaml value)
|
||||||
|
{
|
||||||
|
if (Value == value)
|
||||||
|
return this;
|
||||||
|
return new MiniYamlNode(Key, value, Comment, Location);
|
||||||
|
}
|
||||||
|
|
||||||
public MiniYamlNode(string k, MiniYaml v, string c = null)
|
public MiniYamlNode(string k, MiniYaml v, string c = null)
|
||||||
{
|
{
|
||||||
@@ -74,26 +99,15 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MiniYamlNode(string k, string v, string c = null)
|
public MiniYamlNode(string k, string v, string c = null)
|
||||||
: this(k, v, c, null) { }
|
: this(k, new MiniYaml(v, Enumerable.Empty<MiniYamlNode>()), c) { }
|
||||||
|
|
||||||
public MiniYamlNode(string k, string v, List<MiniYamlNode> n)
|
public MiniYamlNode(string k, string v, IEnumerable<MiniYamlNode> n)
|
||||||
: this(k, new MiniYaml(v, n), null) { }
|
: this(k, new MiniYaml(v, n), null) { }
|
||||||
|
|
||||||
public MiniYamlNode(string k, string v, string c, List<MiniYamlNode> n)
|
|
||||||
: this(k, new MiniYaml(v, n), c) { }
|
|
||||||
|
|
||||||
public MiniYamlNode(string k, string v, string c, List<MiniYamlNode> n, SourceLocation loc)
|
|
||||||
: this(k, new MiniYaml(v, n), c, loc) { }
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{{YamlNode: {Key} @ {Location}}}";
|
return $"{{YamlNode: {Key} @ {Location}}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public MiniYamlNode Clone()
|
|
||||||
{
|
|
||||||
return new MiniYamlNode(Key, Value.Clone(), Comment, Location);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MiniYaml
|
public sealed class MiniYaml
|
||||||
@@ -101,15 +115,59 @@ namespace OpenRA
|
|||||||
const int SpacesPerLevel = 4;
|
const int SpacesPerLevel = 4;
|
||||||
static readonly Func<string, string> StringIdentity = s => s;
|
static readonly Func<string, string> StringIdentity = s => s;
|
||||||
static readonly Func<MiniYaml, MiniYaml> MiniYamlIdentity = my => my;
|
static readonly Func<MiniYaml, MiniYaml> MiniYamlIdentity = my => my;
|
||||||
public string Value;
|
static readonly Dictionary<string, MiniYamlNode> ConflictScratch = new();
|
||||||
public List<MiniYamlNode> Nodes;
|
|
||||||
|
|
||||||
public MiniYaml Clone()
|
public readonly string Value;
|
||||||
|
public readonly ImmutableArray<MiniYamlNode> Nodes;
|
||||||
|
|
||||||
|
public MiniYaml WithValue(string value)
|
||||||
{
|
{
|
||||||
var clonedNodes = new List<MiniYamlNode>(Nodes.Count);
|
if (Value == value)
|
||||||
|
return this;
|
||||||
|
return new MiniYaml(value, Nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYaml WithNodes(IEnumerable<MiniYamlNode> nodes)
|
||||||
|
{
|
||||||
|
if (nodes is ImmutableArray<MiniYamlNode> n && Nodes == n)
|
||||||
|
return this;
|
||||||
|
return new MiniYaml(Value, nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYaml WithNodesAppended(IEnumerable<MiniYamlNode> nodes)
|
||||||
|
{
|
||||||
|
var newNodes = Nodes.AddRange(nodes);
|
||||||
|
if (Nodes == newNodes)
|
||||||
|
return this;
|
||||||
|
return new MiniYaml(Value, newNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYamlNode NodeWithKey(string key)
|
||||||
|
{
|
||||||
|
var result = NodeWithKeyOrDefault(key);
|
||||||
|
if (result == null)
|
||||||
|
throw new InvalidDataException($"No node with key '{key}'");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYamlNode NodeWithKeyOrDefault(string key)
|
||||||
|
{
|
||||||
|
// PERF: Avoid LINQ.
|
||||||
|
var first = true;
|
||||||
|
MiniYamlNode result = null;
|
||||||
foreach (var node in Nodes)
|
foreach (var node in Nodes)
|
||||||
clonedNodes.Add(node.Clone());
|
{
|
||||||
return new MiniYaml(Value, clonedNodes);
|
if (node.Key != key)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!first)
|
||||||
|
throw new InvalidDataException($"Duplicate key '{node.Key}' in {node.Location}");
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
result = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, MiniYaml> ToDictionary()
|
public Dictionary<string, MiniYaml> ToDictionary()
|
||||||
@@ -125,7 +183,7 @@ namespace OpenRA
|
|||||||
public Dictionary<TKey, TElement> ToDictionary<TKey, TElement>(
|
public Dictionary<TKey, TElement> ToDictionary<TKey, TElement>(
|
||||||
Func<string, TKey> keySelector, Func<MiniYaml, TElement> elementSelector)
|
Func<string, TKey> keySelector, Func<MiniYaml, TElement> elementSelector)
|
||||||
{
|
{
|
||||||
var ret = new Dictionary<TKey, TElement>(Nodes.Count);
|
var ret = new Dictionary<TKey, TElement>(Nodes.Length);
|
||||||
foreach (var y in Nodes)
|
foreach (var y in Nodes)
|
||||||
{
|
{
|
||||||
var key = keySelector(y.Key);
|
var key = keySelector(y.Key);
|
||||||
@@ -138,28 +196,27 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MiniYaml(string value)
|
public MiniYaml(string value)
|
||||||
: this(value, null) { }
|
: this(value, Enumerable.Empty<MiniYamlNode>()) { }
|
||||||
|
|
||||||
public MiniYaml(string value, List<MiniYamlNode> nodes)
|
public MiniYaml(string value, IEnumerable<MiniYamlNode> nodes)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Nodes = nodes ?? new List<MiniYamlNode>();
|
Nodes = ImmutableArray.CreateRange(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> NodesOrEmpty(MiniYaml y, string s)
|
static List<MiniYamlNode> FromLines(IEnumerable<ReadOnlyMemory<char>> lines, string name, bool discardCommentsAndWhitespace, HashSet<string> stringPool)
|
||||||
{
|
{
|
||||||
var nd = y.ToDictionary();
|
// YAML config often contains repeated strings for key, values, comments.
|
||||||
return nd.TryGetValue(s, out var v) ? v.Nodes : new List<MiniYamlNode>();
|
// Pool these strings so we only need one copy of each unique string.
|
||||||
}
|
// This saves on long-term memory usage as parsed values can often live a long time.
|
||||||
|
// A caller can also provide a pool as input, allowing de-duplication across multiple parses.
|
||||||
|
stringPool ??= new HashSet<string>();
|
||||||
|
|
||||||
static List<MiniYamlNode> FromLines(IEnumerable<ReadOnlyMemory<char>> lines, string filename, bool discardCommentsAndWhitespace, Dictionary<string, string> stringPool)
|
var result = new List<List<MiniYamlNode>>
|
||||||
{
|
|
||||||
stringPool ??= new Dictionary<string, string>();
|
|
||||||
|
|
||||||
var levels = new List<List<MiniYamlNode>>
|
|
||||||
{
|
{
|
||||||
new List<MiniYamlNode>()
|
new()
|
||||||
};
|
};
|
||||||
|
var parsedLines = new List<(int Level, string Key, string Value, string Comment, MiniYamlNode.SourceLocation Location)>();
|
||||||
|
|
||||||
var lineNo = 0;
|
var lineNo = 0;
|
||||||
foreach (var ll in lines)
|
foreach (var ll in lines)
|
||||||
@@ -175,7 +232,7 @@ namespace OpenRA
|
|||||||
ReadOnlySpan<char> key = default;
|
ReadOnlySpan<char> key = default;
|
||||||
ReadOnlySpan<char> value = default;
|
ReadOnlySpan<char> value = default;
|
||||||
ReadOnlySpan<char> comment = default;
|
ReadOnlySpan<char> comment = default;
|
||||||
var location = new MiniYamlNode.SourceLocation(filename, lineNo);
|
var location = new MiniYamlNode.SourceLocation(name, lineNo);
|
||||||
|
|
||||||
if (line.Length > 0)
|
if (line.Length > 0)
|
||||||
{
|
{
|
||||||
@@ -206,15 +263,6 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (levels.Count <= level)
|
|
||||||
throw new YamlException($"Bad indent in miniyaml at {location}");
|
|
||||||
|
|
||||||
while (levels.Count > level + 1)
|
|
||||||
{
|
|
||||||
levels[^1].TrimExcess();
|
|
||||||
levels.RemoveAt(levels.Count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract key, value, comment from line as `<key>: <value>#<comment>`
|
// Extract key, value, comment from line as `<key>: <value>#<comment>`
|
||||||
// The # character is allowed in the value if escaped (\#).
|
// The # character is allowed in the value if escaped (\#).
|
||||||
// Leading and trailing whitespace is always trimmed from keys.
|
// Leading and trailing whitespace is always trimmed from keys.
|
||||||
@@ -236,7 +284,7 @@ namespace OpenRA
|
|||||||
if (commentStart < 0 && line[i] == '#' && (i == 0 || line[i - 1] != '\\'))
|
if (commentStart < 0 && line[i] == '#' && (i == 0 || line[i - 1] != '\\'))
|
||||||
{
|
{
|
||||||
commentStart = i + 1;
|
commentStart = i + 1;
|
||||||
if (commentStart <= keyLength)
|
if (i <= keyStart + keyLength)
|
||||||
keyLength = i - keyStart;
|
keyLength = i - keyStart;
|
||||||
else
|
else
|
||||||
valueLength = i - valueStart;
|
valueLength = i - valueStart;
|
||||||
@@ -274,46 +322,81 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (!key.IsEmpty || !discardCommentsAndWhitespace)
|
if (!key.IsEmpty || !discardCommentsAndWhitespace)
|
||||||
{
|
{
|
||||||
|
if (parsedLines.Count > 0 && parsedLines[^1].Level < level - 1)
|
||||||
|
throw new YamlException($"Bad indent in miniyaml at {location}");
|
||||||
|
|
||||||
|
while (parsedLines.Count > 0 && parsedLines[^1].Level > level)
|
||||||
|
BuildCompletedSubNode(level);
|
||||||
|
|
||||||
var keyString = key.IsEmpty ? null : key.ToString();
|
var keyString = key.IsEmpty ? null : key.ToString();
|
||||||
var valueString = value.IsEmpty ? null : value.ToString();
|
var valueString = value.IsEmpty ? null : value.ToString();
|
||||||
|
|
||||||
// Note: We need to support empty comments here to ensure that empty comments
|
// Note: We need to support empty comments here to ensure that empty comments
|
||||||
// (i.e. a lone # at the end of a line) can be correctly re-serialized
|
// (i.e. a lone # at the end of a line) can be correctly re-serialized
|
||||||
var commentString = comment == default ? null : comment.ToString();
|
var commentString = comment == ReadOnlySpan<char>.Empty ? null : comment.ToString();
|
||||||
|
|
||||||
keyString = keyString == null ? null : stringPool.GetOrAdd(keyString, keyString);
|
keyString = keyString == null ? null : stringPool.GetOrAdd(keyString);
|
||||||
valueString = valueString == null ? null : stringPool.GetOrAdd(valueString, valueString);
|
valueString = valueString == null ? null : stringPool.GetOrAdd(valueString);
|
||||||
commentString = commentString == null ? null : stringPool.GetOrAdd(commentString, commentString);
|
commentString = commentString == null ? null : stringPool.GetOrAdd(commentString);
|
||||||
|
|
||||||
var nodes = new List<MiniYamlNode>();
|
parsedLines.Add((level, keyString, valueString, commentString, location));
|
||||||
levels[level].Add(new MiniYamlNode(keyString, valueString, commentString, nodes, location));
|
|
||||||
|
|
||||||
levels.Add(nodes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var nodes in levels)
|
if (parsedLines.Count > 0)
|
||||||
nodes.TrimExcess();
|
BuildCompletedSubNode(0);
|
||||||
|
|
||||||
return levels[0];
|
return result[0];
|
||||||
|
|
||||||
|
void BuildCompletedSubNode(int level)
|
||||||
|
{
|
||||||
|
var lastLevel = parsedLines[^1].Level;
|
||||||
|
while (lastLevel >= result.Count)
|
||||||
|
result.Add(new List<MiniYamlNode>());
|
||||||
|
|
||||||
|
while (parsedLines.Count > 0 && parsedLines[^1].Level >= level)
|
||||||
|
{
|
||||||
|
var parent = parsedLines[^1];
|
||||||
|
var startOfRange = parsedLines.Count - 1;
|
||||||
|
while (startOfRange > 0 && parsedLines[startOfRange - 1].Level == parent.Level)
|
||||||
|
startOfRange--;
|
||||||
|
|
||||||
|
for (var i = startOfRange; i < parsedLines.Count - 1; i++)
|
||||||
|
{
|
||||||
|
var sibling = parsedLines[i];
|
||||||
|
result[parent.Level].Add(
|
||||||
|
new MiniYamlNode(sibling.Key, new MiniYaml(sibling.Value), sibling.Comment, sibling.Location));
|
||||||
|
}
|
||||||
|
|
||||||
|
var childNodes = parent.Level + 1 < result.Count ? result[parent.Level + 1] : null;
|
||||||
|
result[parent.Level].Add(new MiniYamlNode(
|
||||||
|
parent.Key,
|
||||||
|
new MiniYaml(parent.Value, childNodes ?? Enumerable.Empty<MiniYamlNode>()),
|
||||||
|
parent.Comment,
|
||||||
|
parent.Location));
|
||||||
|
childNodes?.Clear();
|
||||||
|
|
||||||
|
parsedLines.RemoveRange(startOfRange, parsedLines.Count - startOfRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromStream(File.OpenRead(path), path, discardCommentsAndWhitespace, stringPool);
|
return FromStream(File.OpenRead(path), path, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
public static List<MiniYamlNode> FromStream(Stream s, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromLines(s.ReadAllLinesAsMemory(), fileName, discardCommentsAndWhitespace, stringPool);
|
return FromLines(s.ReadAllLinesAsMemory(), name, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
public static List<MiniYamlNode> FromString(string text, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), fileName, discardCommentsAndWhitespace, stringPool);
|
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), name, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> Merge(IEnumerable<List<MiniYamlNode>> sources)
|
public static List<MiniYamlNode> Merge(IEnumerable<IReadOnlyCollection<MiniYamlNode>> sources)
|
||||||
{
|
{
|
||||||
var sourcesList = sources.ToList();
|
var sourcesList = sources.ToList();
|
||||||
if (sourcesList.Count == 0)
|
if (sourcesList.Count == 0)
|
||||||
@@ -336,28 +419,41 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve any top-level removals (e.g. removing whole actor blocks)
|
// Resolve any top-level removals (e.g. removing whole actor blocks)
|
||||||
var nodes = new MiniYaml("", resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value)).ToList());
|
var nodes = new MiniYaml("", resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value)));
|
||||||
return ResolveInherits(nodes, tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation>.Empty);
|
var result = ResolveInherits(nodes, tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation>.Empty);
|
||||||
|
return result as List<MiniYamlNode> ?? result.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void MergeIntoResolved(MiniYamlNode overrideNode, List<MiniYamlNode> existingNodes, HashSet<string> existingNodeKeys,
|
static void MergeIntoResolved(MiniYamlNode overrideNode, List<MiniYamlNode> existingNodes, HashSet<string> existingNodeKeys,
|
||||||
Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||||
{
|
{
|
||||||
if (existingNodeKeys.Add(overrideNode.Key))
|
var existingNodeIndex = -1;
|
||||||
|
MiniYamlNode existingNode = null;
|
||||||
|
if (!existingNodeKeys.Add(overrideNode.Key))
|
||||||
{
|
{
|
||||||
existingNodes.Add(overrideNode.Clone());
|
existingNodeIndex = IndexOfKey(existingNodes, overrideNode.Key);
|
||||||
return;
|
existingNode = existingNodes[existingNodeIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
var existingNode = existingNodes.Find(n => n.Key == overrideNode.Key);
|
var value = MergePartial(existingNode?.Value, overrideNode.Value);
|
||||||
existingNode.Value = MergePartial(existingNode.Value, overrideNode.Value);
|
var nodes = ResolveInherits(value, tree, inherited);
|
||||||
existingNode.Value.Nodes = ResolveInherits(existingNode.Value, tree, inherited);
|
if (!value.Nodes.SequenceEqual(nodes))
|
||||||
|
value = value.WithNodes(nodes);
|
||||||
|
|
||||||
|
if (existingNode != null)
|
||||||
|
existingNodes[existingNodeIndex] = existingNode.WithValue(value);
|
||||||
|
else
|
||||||
|
existingNodes.Add(overrideNode.WithValue(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<MiniYamlNode> ResolveInherits(MiniYaml node, Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
static IReadOnlyCollection<MiniYamlNode> ResolveInherits(
|
||||||
|
MiniYaml node, Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||||
{
|
{
|
||||||
var resolved = new List<MiniYamlNode>(node.Nodes.Count);
|
if (node.Nodes.Length == 0)
|
||||||
var resolvedKeys = new HashSet<string>(node.Nodes.Count);
|
return node.Nodes;
|
||||||
|
|
||||||
|
var resolved = new List<MiniYamlNode>(node.Nodes.Length);
|
||||||
|
var resolvedKeys = new HashSet<string>(node.Nodes.Length);
|
||||||
|
|
||||||
foreach (var n in node.Nodes)
|
foreach (var n in node.Nodes)
|
||||||
{
|
{
|
||||||
@@ -373,13 +469,14 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
throw new YamlException($"{n.Location}: Parent type `{n.Value.Value}` was already inherited by this yaml tree at {inherited[n.Value.Value]} (note: may be from a derived tree)");
|
throw new YamlException(
|
||||||
|
$"{n.Location}: Parent type `{n.Value.Value}` was already inherited by this yaml tree at {inherited[n.Value.Value]} (note: may be from a derived tree)");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var r in ResolveInherits(parent, tree, inherited))
|
foreach (var r in ResolveInherits(parent, tree, inherited))
|
||||||
MergeIntoResolved(r, resolved, resolvedKeys, tree, inherited);
|
MergeIntoResolved(r, resolved, resolvedKeys, tree, inherited);
|
||||||
}
|
}
|
||||||
else if (n.Key.StartsWith("-", StringComparison.Ordinal))
|
else if (n.Key.StartsWith('-'))
|
||||||
{
|
{
|
||||||
var removed = n.Key[1..];
|
var removed = n.Key[1..];
|
||||||
if (resolved.RemoveAll(r => r.Key == removed) == 0)
|
if (resolved.RemoveAll(r => r.Key == removed) == 0)
|
||||||
@@ -390,7 +487,6 @@ namespace OpenRA
|
|||||||
MergeIntoResolved(n, resolved, resolvedKeys, tree, inherited);
|
MergeIntoResolved(n, resolved, resolvedKeys, tree, inherited);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolved.TrimExcess();
|
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,8 +494,11 @@ namespace OpenRA
|
|||||||
/// Merges any duplicate keys that are defined within the same set of nodes.
|
/// Merges any duplicate keys that are defined within the same set of nodes.
|
||||||
/// Does not resolve inheritance or node removals.
|
/// Does not resolve inheritance or node removals.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static List<MiniYamlNode> MergeSelfPartial(List<MiniYamlNode> existingNodes)
|
static IReadOnlyCollection<MiniYamlNode> MergeSelfPartial(IReadOnlyCollection<MiniYamlNode> existingNodes)
|
||||||
{
|
{
|
||||||
|
if (existingNodes.Count == 0)
|
||||||
|
return existingNodes;
|
||||||
|
|
||||||
var keys = new HashSet<string>(existingNodes.Count);
|
var keys = new HashSet<string>(existingNodes.Count);
|
||||||
var ret = new List<MiniYamlNode>(existingNodes.Count);
|
var ret = new List<MiniYamlNode>(existingNodes.Count);
|
||||||
foreach (var n in existingNodes)
|
foreach (var n in existingNodes)
|
||||||
@@ -409,19 +508,26 @@ namespace OpenRA
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Node with the same key has already been added: merge new node over the existing one
|
// Node with the same key has already been added: merge new node over the existing one
|
||||||
var original = ret.First(r => r.Key == n.Key);
|
var originalIndex = IndexOfKey(ret, n.Key);
|
||||||
original.Value = MergePartial(original.Value, n.Value);
|
var original = ret[originalIndex];
|
||||||
|
ret[originalIndex] = original.WithValue(MergePartial(original.Value, n.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.TrimExcess();
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static MiniYaml MergePartial(MiniYaml existingNodes, MiniYaml overrideNodes)
|
static MiniYaml MergePartial(MiniYaml existingNodes, MiniYaml overrideNodes)
|
||||||
{
|
{
|
||||||
existingNodes?.Nodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => $"{x.Key} (at {x.Location})");
|
lock (ConflictScratch)
|
||||||
overrideNodes?.Nodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => $"{x.Key} (at {x.Location})");
|
{
|
||||||
|
// PERF: Reuse ConflictScratch for all conflict checks to avoid allocations.
|
||||||
|
existingNodes?.Nodes.IntoDictionaryWithConflictLog(
|
||||||
|
n => n.Key, n => n, "MiniYaml.Merge", ConflictScratch, k => k, n => $"{n.Key} (at {n.Location})");
|
||||||
|
overrideNodes?.Nodes.IntoDictionaryWithConflictLog(
|
||||||
|
n => n.Key, n => n, "MiniYaml.Merge", ConflictScratch, k => k, n => $"{n.Key} (at {n.Location})");
|
||||||
|
ConflictScratch.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (existingNodes == null)
|
if (existingNodes == null)
|
||||||
return overrideNodes;
|
return overrideNodes;
|
||||||
@@ -432,7 +538,7 @@ namespace OpenRA
|
|||||||
return new MiniYaml(overrideNodes.Value ?? existingNodes.Value, MergePartial(existingNodes.Nodes, overrideNodes.Nodes));
|
return new MiniYaml(overrideNodes.Value ?? existingNodes.Value, MergePartial(existingNodes.Nodes, overrideNodes.Nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<MiniYamlNode> MergePartial(List<MiniYamlNode> existingNodes, List<MiniYamlNode> overrideNodes)
|
static IReadOnlyCollection<MiniYamlNode> MergePartial(IReadOnlyCollection<MiniYamlNode> existingNodes, IReadOnlyCollection<MiniYamlNode> overrideNodes)
|
||||||
{
|
{
|
||||||
if (existingNodes.Count == 0)
|
if (existingNodes.Count == 0)
|
||||||
return overrideNodes;
|
return overrideNodes;
|
||||||
@@ -452,7 +558,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
// Append Removal nodes to the result.
|
// Append Removal nodes to the result.
|
||||||
// Therefore: we know the remainder of the method deals with a plain node.
|
// Therefore: we know the remainder of the method deals with a plain node.
|
||||||
if (node.Key.StartsWith("-", StringComparison.Ordinal))
|
if (node.Key.StartsWith('-'))
|
||||||
{
|
{
|
||||||
ret.Add(node);
|
ret.Add(node);
|
||||||
return;
|
return;
|
||||||
@@ -468,9 +574,8 @@ namespace OpenRA
|
|||||||
// A Removal node is closer than the previous node.
|
// A Removal node is closer than the previous node.
|
||||||
// We should not merge the new node, as the data being merged will jump before the Removal.
|
// We should not merge the new node, as the data being merged will jump before the Removal.
|
||||||
// Instead, append it so the previous node is applied, then removed, then the new node is applied.
|
// Instead, append it so the previous node is applied, then removed, then the new node is applied.
|
||||||
var removalKey = $"-{node.Key}";
|
var previousNodeIndex = LastIndexOfKey(ret, node.Key);
|
||||||
var previousNodeIndex = ret.FindLastIndex(n => n.Key == node.Key);
|
var previousRemovalNodeIndex = LastIndexOfKey(ret, $"-{node.Key}");
|
||||||
var previousRemovalNodeIndex = ret.FindLastIndex(n => n.Key == removalKey);
|
|
||||||
if (previousRemovalNodeIndex != -1 && previousRemovalNodeIndex > previousNodeIndex)
|
if (previousRemovalNodeIndex != -1 && previousRemovalNodeIndex > previousNodeIndex)
|
||||||
{
|
{
|
||||||
ret.Add(node);
|
ret.Add(node);
|
||||||
@@ -479,13 +584,30 @@ namespace OpenRA
|
|||||||
|
|
||||||
// A previous node is present with no intervening Removal.
|
// A previous node is present with no intervening Removal.
|
||||||
// We should merge the new one into it, in place.
|
// We should merge the new one into it, in place.
|
||||||
ret[previousNodeIndex] = new MiniYamlNode(node.Key, MergePartial(ret[previousNodeIndex].Value, node.Value), node.Comment, node.Location);
|
ret[previousNodeIndex] = node.WithValue(MergePartial(ret[previousNodeIndex].Value, node.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.TrimExcess();
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int IndexOfKey(List<MiniYamlNode> nodes, string key)
|
||||||
|
{
|
||||||
|
// PERF: Avoid LINQ.
|
||||||
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
|
if (nodes[i].Key == key)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int LastIndexOfKey(List<MiniYamlNode> nodes, string key)
|
||||||
|
{
|
||||||
|
// PERF: Avoid LINQ.
|
||||||
|
for (var i = nodes.Count - 1; i >= 0; i--)
|
||||||
|
if (nodes[i].Key == key)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<string> ToLines(string key, string comment = null)
|
public IEnumerable<string> ToLines(string key, string comment = null)
|
||||||
{
|
{
|
||||||
var hasKey = !string.IsNullOrEmpty(key);
|
var hasKey = !string.IsNullOrEmpty(key);
|
||||||
@@ -508,14 +630,100 @@ namespace OpenRA
|
|||||||
files = files.Append(mapFiles);
|
files = files.Append(mapFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
var yaml = files.Select(s => FromStream(fileSystem.Open(s), s));
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
if (mapRules != null && mapRules.Nodes.Count > 0)
|
IEnumerable<IReadOnlyCollection<MiniYamlNode>> yaml = files.Select(s => FromStream(fileSystem.Open(s), s, stringPool: stringPool));
|
||||||
|
if (mapRules != null && mapRules.Nodes.Length > 0)
|
||||||
yaml = yaml.Append(mapRules.Nodes);
|
yaml = yaml.Append(mapRules.Nodes);
|
||||||
|
|
||||||
return Merge(yaml);
|
return Merge(yaml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class MiniYamlNodeBuilder
|
||||||
|
{
|
||||||
|
public MiniYamlNode.SourceLocation Location;
|
||||||
|
public string Key;
|
||||||
|
public MiniYamlBuilder Value;
|
||||||
|
public string Comment;
|
||||||
|
|
||||||
|
public MiniYamlNodeBuilder(MiniYamlNode node)
|
||||||
|
{
|
||||||
|
Location = node.Location;
|
||||||
|
Key = node.Key;
|
||||||
|
Value = new MiniYamlBuilder(node.Value);
|
||||||
|
Comment = node.Comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYamlNodeBuilder(string k, MiniYamlBuilder v, string c = null)
|
||||||
|
{
|
||||||
|
Key = k;
|
||||||
|
Value = v;
|
||||||
|
Comment = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYamlNodeBuilder(string k, MiniYamlBuilder v, string c, MiniYamlNode.SourceLocation loc)
|
||||||
|
: this(k, v, c)
|
||||||
|
{
|
||||||
|
Location = loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYamlNodeBuilder(string k, string v, string c = null)
|
||||||
|
: this(k, new MiniYamlBuilder(v, null), c) { }
|
||||||
|
|
||||||
|
public MiniYamlNodeBuilder(string k, string v, List<MiniYamlNode> n)
|
||||||
|
: this(k, new MiniYamlBuilder(v, n), null) { }
|
||||||
|
|
||||||
|
public MiniYamlNode Build()
|
||||||
|
{
|
||||||
|
return new MiniYamlNode(Key, Value.Build(), Comment, Location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MiniYamlBuilder
|
||||||
|
{
|
||||||
|
public string Value;
|
||||||
|
public List<MiniYamlNodeBuilder> Nodes;
|
||||||
|
|
||||||
|
public MiniYamlBuilder(MiniYaml yaml)
|
||||||
|
{
|
||||||
|
Value = yaml.Value;
|
||||||
|
Nodes = yaml.Nodes.Select(n => new MiniYamlNodeBuilder(n)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYamlBuilder(string value)
|
||||||
|
: this(value, null) { }
|
||||||
|
|
||||||
|
public MiniYamlBuilder(string value, List<MiniYamlNode> nodes)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Nodes = nodes == null ? new List<MiniYamlNodeBuilder>() : nodes.ConvertAll(x => new MiniYamlNodeBuilder(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYaml Build()
|
||||||
|
{
|
||||||
|
return new MiniYaml(Value, Nodes.Select(n => n.Build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> ToLines(string key, string comment = null)
|
||||||
|
{
|
||||||
|
var hasKey = !string.IsNullOrEmpty(key);
|
||||||
|
var hasValue = !string.IsNullOrEmpty(Value);
|
||||||
|
var hasComment = comment != null;
|
||||||
|
yield return (hasKey ? key + ":" : "")
|
||||||
|
+ (hasValue ? " " + Value.Replace("#", "\\#") : "")
|
||||||
|
+ (hasComment ? (hasKey || hasValue ? " " : "") + "#" + comment : "");
|
||||||
|
|
||||||
|
if (Nodes != null)
|
||||||
|
foreach (var line in Nodes.ToLines())
|
||||||
|
yield return "\t" + line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiniYamlNodeBuilder NodeWithKeyOrDefault(string key)
|
||||||
|
{
|
||||||
|
return Nodes.SingleOrDefault(n => n.Key == key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class YamlException : Exception
|
public class YamlException : Exception
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ namespace OpenRA
|
|||||||
public readonly ISpriteLoader[] SpriteLoaders;
|
public readonly ISpriteLoader[] SpriteLoaders;
|
||||||
public readonly ITerrainLoader TerrainLoader;
|
public readonly ITerrainLoader TerrainLoader;
|
||||||
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
||||||
public readonly IModelSequenceLoader ModelSequenceLoader;
|
|
||||||
public readonly IVideoLoader[] VideoLoaders;
|
public readonly IVideoLoader[] VideoLoaders;
|
||||||
public readonly HotkeyManager Hotkeys;
|
public readonly HotkeyManager Hotkeys;
|
||||||
|
public readonly IFileSystemLoader FileSystemLoader;
|
||||||
|
|
||||||
public ILoadScreen LoadScreen { get; }
|
public ILoadScreen LoadScreen { get; }
|
||||||
public CursorProvider CursorProvider { get; private set; }
|
public CursorProvider CursorProvider { get; private set; }
|
||||||
public FS ModFiles;
|
public FS ModFiles;
|
||||||
@@ -55,11 +56,17 @@ namespace OpenRA
|
|||||||
Manifest = new Manifest(mod.Id, mod.Package);
|
Manifest = new Manifest(mod.Id, mod.Package);
|
||||||
ObjectCreator = new ObjectCreator(Manifest, mods);
|
ObjectCreator = new ObjectCreator(Manifest, mods);
|
||||||
PackageLoaders = ObjectCreator.GetLoaders<IPackageLoader>(Manifest.PackageFormats, "package");
|
PackageLoaders = ObjectCreator.GetLoaders<IPackageLoader>(Manifest.PackageFormats, "package");
|
||||||
|
|
||||||
ModFiles = new FS(mod.Id, mods, PackageLoaders);
|
ModFiles = new FS(mod.Id, mods, PackageLoaders);
|
||||||
ModFiles.LoadFromManifest(Manifest);
|
|
||||||
|
FileSystemLoader = ObjectCreator.GetLoader<IFileSystemLoader>(Manifest.FileSystem.Value, "filesystem");
|
||||||
|
FieldLoader.Load(FileSystemLoader, Manifest.FileSystem);
|
||||||
|
FileSystemLoader.Mount(ModFiles, ObjectCreator);
|
||||||
|
ModFiles.TrimExcess();
|
||||||
|
|
||||||
Manifest.LoadCustomData(ObjectCreator);
|
Manifest.LoadCustomData(ObjectCreator);
|
||||||
|
|
||||||
|
FluentProvider.Initialize(this, DefaultFileSystem);
|
||||||
|
|
||||||
if (useLoadScreen)
|
if (useLoadScreen)
|
||||||
{
|
{
|
||||||
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);
|
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);
|
||||||
@@ -90,15 +97,6 @@ namespace OpenRA
|
|||||||
|
|
||||||
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
|
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
|
||||||
|
|
||||||
var modelFormat = Manifest.Get<ModelSequenceFormat>();
|
|
||||||
var modelLoader = ObjectCreator.FindType(modelFormat.Type + "Loader");
|
|
||||||
var modelCtor = modelLoader?.GetConstructor(new[] { typeof(ModData) });
|
|
||||||
if (modelLoader == null || !modelLoader.GetInterfaces().Contains(typeof(IModelSequenceLoader)) || modelCtor == null)
|
|
||||||
throw new InvalidOperationException($"Unable to find a model loader for type '{modelFormat.Type}'.");
|
|
||||||
|
|
||||||
ModelSequenceLoader = (IModelSequenceLoader)modelCtor.Invoke(new[] { this });
|
|
||||||
ModelSequenceLoader.OnMissingModelError = s => Log.Write("debug", s);
|
|
||||||
|
|
||||||
Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest);
|
Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest);
|
||||||
|
|
||||||
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
|
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
|
||||||
@@ -134,7 +132,7 @@ namespace OpenRA
|
|||||||
// horribly when you use ModData in unexpected ways.
|
// horribly when you use ModData in unexpected ways.
|
||||||
ChromeMetrics.Initialize(this);
|
ChromeMetrics.Initialize(this);
|
||||||
ChromeProvider.Initialize(this);
|
ChromeProvider.Initialize(this);
|
||||||
TranslationProvider.Initialize(this, fileSystem);
|
FluentProvider.Initialize(this, fileSystem);
|
||||||
|
|
||||||
Game.Sound.Initialize(SoundLoaders, fileSystem);
|
Game.Sound.Initialize(SoundLoaders, fileSystem);
|
||||||
|
|
||||||
@@ -168,7 +166,8 @@ namespace OpenRA
|
|||||||
|
|
||||||
public List<MiniYamlNode>[] GetRulesYaml()
|
public List<MiniYamlNode>[] GetRulesYaml()
|
||||||
{
|
{
|
||||||
return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s)).ToArray();
|
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
||||||
|
return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s, stringPool: stringPool)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -199,4 +198,9 @@ namespace OpenRA
|
|||||||
/// <summary>Called when the engine expects to connect to a server/replay or load the shellmap.</summary>
|
/// <summary>Called when the engine expects to connect to a server/replay or load the shellmap.</summary>
|
||||||
void StartGame(Arguments args);
|
void StartGame(Arguments args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IFileSystemLoader
|
||||||
|
{
|
||||||
|
void Mount(FS fileSystem, ObjectCreator objectCreator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,15 +260,15 @@ namespace OpenRA.Network
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
ms.WriteArray(BitConverter.GetBytes(packet.Length));
|
ms.Write(packet.Length);
|
||||||
ms.WriteArray(packet);
|
ms.Write(packet);
|
||||||
|
|
||||||
foreach (var s in queuedSyncPackets)
|
foreach (var s in queuedSyncPackets)
|
||||||
{
|
{
|
||||||
var q = OrderIO.SerializeSync(s);
|
var q = OrderIO.SerializeSync(s);
|
||||||
|
|
||||||
ms.WriteArray(BitConverter.GetBytes(q.Length));
|
ms.Write(q.Length);
|
||||||
ms.WriteArray(q);
|
ms.Write(q);
|
||||||
|
|
||||||
sentSync.Enqueue(s);
|
sentSync.Enqueue(s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Linguini.Shared.Types.Bundle;
|
using Linguini.Shared.Types.Bundle;
|
||||||
|
|
||||||
namespace OpenRA.Network
|
namespace OpenRA.Network
|
||||||
@@ -50,74 +48,75 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LocalizedMessage
|
public class FluentMessage
|
||||||
{
|
{
|
||||||
public const int ProtocolVersion = 1;
|
public const int ProtocolVersion = 1;
|
||||||
|
|
||||||
public readonly string Key = string.Empty;
|
public readonly string Key = string.Empty;
|
||||||
|
|
||||||
[FieldLoader.LoadUsing(nameof(LoadArguments))]
|
[FieldLoader.LoadUsing(nameof(LoadArguments))]
|
||||||
public readonly FluentArgument[] Arguments = Array.Empty<FluentArgument>();
|
public readonly object[] Arguments;
|
||||||
|
|
||||||
public string TranslatedText { get; }
|
|
||||||
|
|
||||||
static object LoadArguments(MiniYaml yaml)
|
static object LoadArguments(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var arguments = new List<FluentArgument>();
|
var arguments = new List<object>();
|
||||||
var argumentsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Arguments");
|
var argumentsNode = yaml.NodeWithKeyOrDefault("Arguments");
|
||||||
if (argumentsNode != null)
|
if (argumentsNode != null)
|
||||||
{
|
{
|
||||||
var regex = new Regex(@"Argument@\d+");
|
foreach (var argumentNode in argumentsNode.Value.Nodes)
|
||||||
foreach (var argument in argumentsNode.Value.Nodes)
|
{
|
||||||
if (regex.IsMatch(argument.Key))
|
var argument = FieldLoader.Load<FluentArgument>(argumentNode.Value);
|
||||||
arguments.Add(FieldLoader.Load<FluentArgument>(argument.Value));
|
arguments.Add(argument.Key);
|
||||||
|
if (argument.Type == FluentArgument.FluentArgumentType.Number)
|
||||||
|
{
|
||||||
|
if (!double.TryParse(argument.Value, out var number))
|
||||||
|
Log.Write("debug", $"Failed to parse {argument.Value}");
|
||||||
|
|
||||||
|
arguments.Add(number);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
arguments.Add(argument.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return arguments.ToArray();
|
return arguments.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalizedMessage(MiniYaml yaml)
|
public FluentMessage(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
// Let the FieldLoader do the dirty work of loading the public fields.
|
// Let the FieldLoader do the dirty work of loading the public fields.
|
||||||
FieldLoader.Load(this, yaml);
|
FieldLoader.Load(this, yaml);
|
||||||
|
|
||||||
var argumentDictionary = new Dictionary<string, object>();
|
|
||||||
foreach (var argument in Arguments)
|
|
||||||
{
|
|
||||||
if (argument.Type == FluentArgument.FluentArgumentType.Number)
|
|
||||||
{
|
|
||||||
if (!double.TryParse(argument.Value, out var number))
|
|
||||||
Log.Write("debug", $"Failed to parse {argument.Value}");
|
|
||||||
|
|
||||||
argumentDictionary.Add(argument.Key, number);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
argumentDictionary.Add(argument.Key, argument.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
TranslatedText = TranslationProvider.GetString(Key, argumentDictionary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Serialize(string key, Dictionary<string, object> arguments = null)
|
public static string Serialize(string key, object[] args)
|
||||||
{
|
{
|
||||||
var root = new List<MiniYamlNode>
|
var root = new List<MiniYamlNode>
|
||||||
{
|
{
|
||||||
new MiniYamlNode("Protocol", ProtocolVersion.ToString()),
|
new("Protocol", ProtocolVersion.ToStringInvariant()),
|
||||||
new MiniYamlNode("Key", key)
|
new("Key", key),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (arguments != null)
|
if (args != null)
|
||||||
{
|
{
|
||||||
var argumentsNode = new MiniYaml("");
|
var nodes = new List<MiniYamlNode>();
|
||||||
var i = 0;
|
for (var i = 0; i < args.Length; i += 2)
|
||||||
foreach (var argument in arguments.Select(a => new FluentArgument(a.Key, a.Value)))
|
{
|
||||||
argumentsNode.Nodes.Add(new MiniYamlNode("Argument@" + i++, FieldSaver.Save(argument)));
|
var argKey = args[i] as string;
|
||||||
|
if (string.IsNullOrEmpty(argKey))
|
||||||
|
throw new ArgumentException($"Expected the argument at index {i} to be a non-empty string", nameof(args));
|
||||||
|
|
||||||
root.Add(new MiniYamlNode("Arguments", argumentsNode));
|
var argValue = args[i + 1];
|
||||||
|
if (argValue == null)
|
||||||
|
throw new ArgumentNullException(nameof(args), $"Expected the argument at index {i + 1} to be a non-null value");
|
||||||
|
|
||||||
|
nodes.Add(new MiniYamlNode($"Argument@{i / 2}", FieldSaver.Save(new FluentArgument(argKey, argValue))));
|
||||||
|
}
|
||||||
|
|
||||||
|
root.Add(new MiniYamlNode("Arguments", new MiniYaml("", nodes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MiniYaml("", root)
|
return new MiniYaml("", root)
|
||||||
.ToLines("LocalizedMessage")
|
.ToLines("FluentMessage")
|
||||||
.JoinWith("\n");
|
.JoinWith("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,10 +122,10 @@ namespace OpenRA.Network
|
|||||||
LastSyncFrame = rs.ReadInt32();
|
LastSyncFrame = rs.ReadInt32();
|
||||||
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
|
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
|
||||||
|
|
||||||
var globalSettings = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
var globalSettings = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:globalSettings");
|
||||||
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
|
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
|
||||||
|
|
||||||
var slots = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
var slots = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slots");
|
||||||
Slots = new Dictionary<string, Session.Slot>();
|
Slots = new Dictionary<string, Session.Slot>();
|
||||||
foreach (var s in slots)
|
foreach (var s in slots)
|
||||||
{
|
{
|
||||||
@@ -133,7 +133,7 @@ namespace OpenRA.Network
|
|||||||
Slots.Add(slot.PlayerReference, slot);
|
Slots.Add(slot.PlayerReference, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
var slotClients = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
var slotClients = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slotClients");
|
||||||
SlotClients = new Dictionary<string, SlotClient>();
|
SlotClients = new Dictionary<string, SlotClient>();
|
||||||
foreach (var s in slotClients)
|
foreach (var s in slotClients)
|
||||||
{
|
{
|
||||||
@@ -144,9 +144,9 @@ namespace OpenRA.Network
|
|||||||
if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker)
|
if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker)
|
||||||
throw new InvalidDataException("Invalid orasav file");
|
throw new InvalidDataException("Invalid orasav file");
|
||||||
|
|
||||||
var traitData = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
var traitData = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:traitData");
|
||||||
foreach (var td in traitData)
|
foreach (var td in traitData)
|
||||||
TraitData.Add(int.Parse(td.Key), td.Value);
|
TraitData.Add(Exts.ParseInt32Invariant(td.Key), td.Value);
|
||||||
|
|
||||||
rs.Seek(0, SeekOrigin.Begin);
|
rs.Seek(0, SeekOrigin.Begin);
|
||||||
ordersStream.Write(rs.ReadBytes(metadataOffset), 0, metadataOffset);
|
ordersStream.Write(rs.ReadBytes(metadataOffset), 0, metadataOffset);
|
||||||
@@ -226,10 +226,10 @@ namespace OpenRA.Network
|
|||||||
clientSlot = firstBotSlotIndex;
|
clientSlot = firstBotSlotIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
ordersStream.WriteArray(BitConverter.GetBytes(data.Length + 8));
|
ordersStream.Write(data.Length + 8);
|
||||||
ordersStream.WriteArray(BitConverter.GetBytes(frame));
|
ordersStream.Write(frame);
|
||||||
ordersStream.WriteArray(BitConverter.GetBytes(clientSlot));
|
ordersStream.Write(clientSlot);
|
||||||
ordersStream.WriteArray(data);
|
ordersStream.Write(data);
|
||||||
LastOrdersFrame = frame;
|
LastOrdersFrame = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ namespace OpenRA.Network
|
|||||||
// Send the trait data first to guarantee that it is available when needed
|
// Send the trait data first to guarantee that it is available when needed
|
||||||
foreach (var kv in TraitData)
|
foreach (var kv in TraitData)
|
||||||
{
|
{
|
||||||
var data = new List<MiniYamlNode>() { new MiniYamlNode(kv.Key.ToString(), kv.Value) }.WriteToString();
|
var data = new List<MiniYamlNode>() { new(kv.Key.ToStringInvariant(), kv.Value) }.WriteToString();
|
||||||
packetFn(0, 0, Order.FromTargetString("SaveTraitData", data, true).Serialize());
|
packetFn(0, 0, Order.FromTargetString("SaveTraitData", data, true).Serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,35 +288,35 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
ordersStream.Seek(0, SeekOrigin.Begin);
|
ordersStream.Seek(0, SeekOrigin.Begin);
|
||||||
ordersStream.CopyTo(file);
|
ordersStream.CopyTo(file);
|
||||||
file.Write(BitConverter.GetBytes(MetadataMarker), 0, 4);
|
file.Write(MetadataMarker);
|
||||||
file.Write(BitConverter.GetBytes(LastOrdersFrame), 0, 4);
|
file.Write(LastOrdersFrame);
|
||||||
file.Write(BitConverter.GetBytes(LastSyncFrame), 0, 4);
|
file.Write(LastSyncFrame);
|
||||||
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
|
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
|
||||||
|
|
||||||
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
|
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
|
||||||
file.WriteString(Encoding.UTF8, globalSettingsNodes.WriteToString());
|
file.WriteLengthPrefixedString(Encoding.UTF8, globalSettingsNodes.WriteToString());
|
||||||
|
|
||||||
var slotNodes = Slots
|
var slotNodes = Slots
|
||||||
.Select(s => s.Value.Serialize())
|
.Select(s => s.Value.Serialize())
|
||||||
.ToList();
|
.ToList();
|
||||||
file.WriteString(Encoding.UTF8, slotNodes.WriteToString());
|
file.WriteLengthPrefixedString(Encoding.UTF8, slotNodes.WriteToString());
|
||||||
|
|
||||||
var slotClientNodes = SlotClients
|
var slotClientNodes = SlotClients
|
||||||
.Select(s => s.Value.Serialize(s.Key))
|
.Select(s => s.Value.Serialize(s.Key))
|
||||||
.ToList();
|
.ToList();
|
||||||
file.WriteString(Encoding.UTF8, slotClientNodes.WriteToString());
|
file.WriteLengthPrefixedString(Encoding.UTF8, slotClientNodes.WriteToString());
|
||||||
|
|
||||||
var traitDataOffset = file.Length;
|
var traitDataOffset = file.Length;
|
||||||
file.Write(BitConverter.GetBytes(TraitDataMarker), 0, 4);
|
file.Write(TraitDataMarker);
|
||||||
|
|
||||||
var traitDataNodes = TraitData
|
var traitDataNodes = TraitData
|
||||||
.Select(kv => new MiniYamlNode(kv.Key.ToString(), kv.Value))
|
.Select(kv => new MiniYamlNode(kv.Key.ToStringInvariant(), kv.Value))
|
||||||
.ToList();
|
.ToList();
|
||||||
file.WriteString(Encoding.UTF8, traitDataNodes.WriteToString());
|
file.WriteLengthPrefixedString(Encoding.UTF8, traitDataNodes.WriteToString());
|
||||||
|
|
||||||
file.Write(BitConverter.GetBytes(ordersStream.Length), 0, 4);
|
file.Write((int)ordersStream.Length);
|
||||||
file.Write(BitConverter.GetBytes(traitDataOffset), 0, 4);
|
file.Write((int)traitDataOffset);
|
||||||
file.Write(BitConverter.GetBytes(EOFMarker), 0, 4);
|
file.Write(EOFMarker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace OpenRA.Network
|
|||||||
static object LoadClients(MiniYaml yaml)
|
static object LoadClients(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var clients = new List<GameClient>();
|
var clients = new List<GameClient>();
|
||||||
var clientsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Clients");
|
var clientsNode = yaml.NodeWithKeyOrDefault("Clients");
|
||||||
if (clientsNode != null)
|
if (clientsNode != null)
|
||||||
{
|
{
|
||||||
var regex = new Regex(@"Client@\d+");
|
var regex = new Regex(@"Client@\d+");
|
||||||
@@ -159,7 +159,7 @@ namespace OpenRA.Network
|
|||||||
// Games advertised using the old API used a single Mods field
|
// Games advertised using the old API used a single Mods field
|
||||||
if (Mod == null || Version == null)
|
if (Mod == null || Version == null)
|
||||||
{
|
{
|
||||||
var modsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Mods");
|
var modsNode = yaml.NodeWithKeyOrDefault("Mods");
|
||||||
if (modsNode != null)
|
if (modsNode != null)
|
||||||
{
|
{
|
||||||
var modVersion = modsNode.Value.Value.Split('@');
|
var modVersion = modsNode.Value.Value.Split('@');
|
||||||
@@ -169,9 +169,8 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Games advertised using the old API calculated the play time locally
|
// Games advertised using the old API calculated the play time locally
|
||||||
if (State == 2 && PlayTime < 0)
|
if (State == 2 && PlayTime < 0 && DateTime.TryParse(Started, out var startTime))
|
||||||
if (DateTime.TryParse(Started, out var startTime))
|
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
|
||||||
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
|
|
||||||
|
|
||||||
var externalKey = ExternalMod.MakeKey(Mod, Version);
|
var externalKey = ExternalMod.MakeKey(Mod, Version);
|
||||||
if (Game.ExternalMods.TryGetValue(externalKey, out var external) && external.Version == Version)
|
if (Game.ExternalMods.TryGetValue(externalKey, out var external) && external.Version == Version)
|
||||||
@@ -183,13 +182,13 @@ namespace OpenRA.Network
|
|||||||
if (external != null && external.Version == Version)
|
if (external != null && external.Version == Version)
|
||||||
{
|
{
|
||||||
// Use external mod registration to populate the section header
|
// Use external mod registration to populate the section header
|
||||||
ModTitle = external.Title;
|
ModTitle = external.Id;
|
||||||
}
|
}
|
||||||
else if (Game.Mods.TryGetValue(Mod, out var mod))
|
else if (Game.Mods.TryGetValue(Mod, out var mod))
|
||||||
{
|
{
|
||||||
// Use internal mod data to populate the section header, but
|
// Use internal mod data to populate the section header, but
|
||||||
// on-connect switching must use the external mod plumbing.
|
// on-connect switching must use the external mod plumbing.
|
||||||
ModTitle = mod.Metadata.Title;
|
ModTitle = mod.Metadata.TitleTranslated;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -200,7 +199,7 @@ namespace OpenRA.Network
|
|||||||
.FirstOrDefault(m => m.Id == Mod);
|
.FirstOrDefault(m => m.Id == Mod);
|
||||||
|
|
||||||
if (guessMod != null)
|
if (guessMod != null)
|
||||||
ModTitle = guessMod.Title;
|
ModTitle = guessMod.Id;
|
||||||
else
|
else
|
||||||
ModTitle = $"Unknown mod: {Mod}";
|
ModTitle = $"Unknown mod: {Mod}";
|
||||||
}
|
}
|
||||||
@@ -217,13 +216,13 @@ namespace OpenRA.Network
|
|||||||
Name = server.Settings.Name;
|
Name = server.Settings.Name;
|
||||||
|
|
||||||
// IP address will be replaced with a real value by the master server / receiving LAN client
|
// IP address will be replaced with a real value by the master server / receiving LAN client
|
||||||
Address = "0.0.0.0:" + server.Settings.ListenPort.ToString();
|
Address = "0.0.0.0:" + server.Settings.ListenPort.ToStringInvariant();
|
||||||
State = (int)server.State;
|
State = (int)server.State;
|
||||||
MaxPlayers = server.LobbyInfo.Slots.Count(s => !s.Value.Closed) - server.LobbyInfo.Clients.Count(c1 => c1.Bot != null);
|
MaxPlayers = server.LobbyInfo.Slots.Count(s => !s.Value.Closed) - server.LobbyInfo.Clients.Count(c1 => c1.Bot != null);
|
||||||
Map = server.Map.Uid;
|
Map = server.Map.Uid;
|
||||||
Mod = manifest.Id;
|
Mod = manifest.Id;
|
||||||
Version = manifest.Metadata.Version;
|
Version = manifest.Metadata.Version;
|
||||||
ModTitle = manifest.Metadata.Title;
|
ModTitle = manifest.Metadata.TitleTranslated;
|
||||||
ModWebsite = manifest.Metadata.Website;
|
ModWebsite = manifest.Metadata.Website;
|
||||||
ModIcon32 = manifest.Metadata.WebIcon32;
|
ModIcon32 = manifest.Metadata.WebIcon32;
|
||||||
Protected = !string.IsNullOrEmpty(server.Settings.Password);
|
Protected = !string.IsNullOrEmpty(server.Settings.Password);
|
||||||
@@ -234,7 +233,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public string ToPOSTData(bool lanGame)
|
public string ToPOSTData(bool lanGame)
|
||||||
{
|
{
|
||||||
var root = new List<MiniYamlNode>() { new MiniYamlNode("Protocol", ProtocolVersion.ToString()) };
|
var root = new List<MiniYamlNode>() { new("Protocol", ProtocolVersion.ToStringInvariant()) };
|
||||||
foreach (var field in SerializeFields)
|
foreach (var field in SerializeFields)
|
||||||
root.Add(FieldSaver.SaveField(this, field));
|
root.Add(FieldSaver.SaveField(this, field));
|
||||||
|
|
||||||
@@ -243,18 +242,16 @@ namespace OpenRA.Network
|
|||||||
// Add fields that are normally generated by the master server
|
// Add fields that are normally generated by the master server
|
||||||
// LAN games overload the Id with a GUID string (rather than an ID) to allow deduplication
|
// LAN games overload the Id with a GUID string (rather than an ID) to allow deduplication
|
||||||
root.Add(new MiniYamlNode("Id", Platform.SessionGUID.ToString()));
|
root.Add(new MiniYamlNode("Id", Platform.SessionGUID.ToString()));
|
||||||
root.Add(new MiniYamlNode("Players", Clients.Count(c => !c.IsBot && !c.IsSpectator).ToString()));
|
root.Add(new MiniYamlNode("Players", Clients.Count(c => !c.IsBot && !c.IsSpectator).ToStringInvariant()));
|
||||||
root.Add(new MiniYamlNode("Spectators", Clients.Count(c => c.IsSpectator).ToString()));
|
root.Add(new MiniYamlNode("Spectators", Clients.Count(c => c.IsSpectator).ToStringInvariant()));
|
||||||
root.Add(new MiniYamlNode("Bots", Clients.Count(c => c.IsBot).ToString()));
|
root.Add(new MiniYamlNode("Bots", Clients.Count(c => c.IsBot).ToStringInvariant()));
|
||||||
|
|
||||||
// Included for backwards compatibility with older clients that don't support separated Mod/Version.
|
// Included for backwards compatibility with older clients that don't support separated Mod/Version.
|
||||||
root.Add(new MiniYamlNode("Mods", Mod + "@" + Version));
|
root.Add(new MiniYamlNode("Mods", Mod + "@" + Version));
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientsNode = new MiniYaml("");
|
var clientsNode = new MiniYaml("", Clients.Select((c, i) =>
|
||||||
var i = 0;
|
new MiniYamlNode("Client@" + i, FieldSaver.Save(c))));
|
||||||
foreach (var c in Clients)
|
|
||||||
clientsNode.Nodes.Add(new MiniYamlNode("Client@" + i++.ToString(), FieldSaver.Save(c)));
|
|
||||||
|
|
||||||
root.Add(new MiniYamlNode("Clients", clientsNode));
|
root.Add(new MiniYamlNode("Clients", clientsNode));
|
||||||
return new MiniYaml("", root)
|
return new MiniYaml("", root)
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ namespace OpenRA.Network
|
|||||||
public string Version;
|
public string Version;
|
||||||
public string AuthToken;
|
public string AuthToken;
|
||||||
|
|
||||||
public static HandshakeRequest Deserialize(string data)
|
public static HandshakeRequest Deserialize(string data, string name)
|
||||||
{
|
{
|
||||||
var handshake = new HandshakeRequest();
|
var handshake = new HandshakeRequest();
|
||||||
FieldLoader.Load(handshake, MiniYaml.FromString(data).First().Value);
|
FieldLoader.Load(handshake, MiniYaml.FromString(data, name).First().Value);
|
||||||
return handshake;
|
return handshake;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Serialize()
|
public string Serialize()
|
||||||
{
|
{
|
||||||
var data = new List<MiniYamlNode> { new MiniYamlNode("Handshake", FieldSaver.Save(this)) };
|
var data = new List<MiniYamlNode> { new("Handshake", FieldSaver.Save(this)) };
|
||||||
return data.WriteToString();
|
return data.WriteToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,14 +51,14 @@ namespace OpenRA.Network
|
|||||||
[FieldLoader.Ignore]
|
[FieldLoader.Ignore]
|
||||||
public Session.Client Client;
|
public Session.Client Client;
|
||||||
|
|
||||||
public static HandshakeResponse Deserialize(string data)
|
public static HandshakeResponse Deserialize(string data, string name)
|
||||||
{
|
{
|
||||||
var handshake = new HandshakeResponse
|
var handshake = new HandshakeResponse
|
||||||
{
|
{
|
||||||
Client = new Session.Client()
|
Client = new Session.Client()
|
||||||
};
|
};
|
||||||
|
|
||||||
var ys = MiniYaml.FromString(data);
|
var ys = MiniYaml.FromString(data, name);
|
||||||
foreach (var y in ys)
|
foreach (var y in ys)
|
||||||
{
|
{
|
||||||
switch (y.Key)
|
switch (y.Key)
|
||||||
@@ -79,9 +79,9 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
var data = new List<MiniYamlNode>
|
var data = new List<MiniYamlNode>
|
||||||
{
|
{
|
||||||
new MiniYamlNode("Handshake", null,
|
new("Handshake", null,
|
||||||
new[] { "Mod", "Version", "Password", "Fingerprint", "AuthSignature", "OrdersProtocol" }.Select(p => FieldSaver.SaveField(this, p)).ToList()),
|
new[] { "Mod", "Version", "Password", "Fingerprint", "AuthSignature", "OrdersProtocol" }.Select(p => FieldSaver.SaveField(this, p)).ToList()),
|
||||||
new MiniYamlNode("Client", FieldSaver.Save(Client))
|
new("Client", FieldSaver.Save(Client))
|
||||||
};
|
};
|
||||||
|
|
||||||
return data.WriteToString();
|
return data.WriteToString();
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Port forwarding failed: {0}", e.Message);
|
Log.Write("nat", "Port forwarding failed.");
|
||||||
Log.Write("nat", e.StackTrace);
|
Log.Write("nat", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Port removal failed: {0}", e.Message);
|
Log.Write("nat", "Port removal failed.");
|
||||||
Log.Write("nat", e.StackTrace);
|
Log.Write("nat", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ namespace OpenRA
|
|||||||
readonly Target target;
|
readonly Target target;
|
||||||
readonly Target visualFeedbackTarget;
|
readonly Target visualFeedbackTarget;
|
||||||
|
|
||||||
Order(string orderString, Actor subject, in Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
|
Order(string orderString, Actor subject, in Target target, string targetString, bool queued,
|
||||||
|
Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
|
||||||
{
|
{
|
||||||
OrderString = orderString ?? "";
|
OrderString = orderString ?? "";
|
||||||
Subject = subject;
|
Subject = subject;
|
||||||
@@ -156,7 +157,18 @@ namespace OpenRA
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var pos = new WPos(r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
|
var pos = new WPos(r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
|
||||||
target = Target.FromPos(pos);
|
|
||||||
|
var numberOfTerrainPositions = r.ReadInt16();
|
||||||
|
if (numberOfTerrainPositions == -1)
|
||||||
|
target = Target.FromPos(pos);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var terrainPositions = new WPos[numberOfTerrainPositions];
|
||||||
|
for (var i = 0; i < numberOfTerrainPositions; i++)
|
||||||
|
terrainPositions[i] = new WPos(r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
|
||||||
|
|
||||||
|
target = Target.FromSerializedTerrainPosition(pos, terrainPositions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -259,7 +271,15 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static Order FromGroupedOrder(Order grouped, Actor subject)
|
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);
|
return new Order(
|
||||||
|
grouped.OrderString,
|
||||||
|
subject,
|
||||||
|
grouped.Target,
|
||||||
|
grouped.TargetString,
|
||||||
|
grouped.Queued,
|
||||||
|
grouped.ExtraActors,
|
||||||
|
grouped.ExtraLocation,
|
||||||
|
grouped.ExtraData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Order Command(string text)
|
public static Order Command(string text)
|
||||||
@@ -388,6 +408,21 @@ namespace OpenRA
|
|||||||
w.Write(targetState.Pos.X);
|
w.Write(targetState.Pos.X);
|
||||||
w.Write(targetState.Pos.Y);
|
w.Write(targetState.Pos.Y);
|
||||||
w.Write(targetState.Pos.Z);
|
w.Write(targetState.Pos.Z);
|
||||||
|
|
||||||
|
// Don't send extra data over the network that will be restored by the Target ctor
|
||||||
|
var terrainPositions = targetState.TerrainPositions.Length;
|
||||||
|
if (terrainPositions == 1 && targetState.TerrainPositions[0] == targetState.Pos)
|
||||||
|
w.Write((short)-1);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
w.Write((short)terrainPositions);
|
||||||
|
foreach (var position in targetState.TerrainPositions)
|
||||||
|
{
|
||||||
|
w.Write(position.X);
|
||||||
|
w.Write(position.Y);
|
||||||
|
w.Write(position.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -430,7 +465,7 @@ namespace OpenRA
|
|||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"OrderString: \"{OrderString}\" \n\t Type: \"{Type}\". \n\t Subject: \"{Subject}\". \n\t Target: \"{Target}\"." +
|
return $"OrderString: \"{OrderString}\" \n\t Type: \"{Type}\". \n\t Subject: \"{Subject}\". \n\t Target: \"{Target}\"." +
|
||||||
$"\n\t TargetString: \"{TargetString}\".\n\t IsImmediate: {IsImmediate}.\n\t Player(PlayerName): {Player?.PlayerName}\n";
|
$"\n\t TargetString: \"{TargetString}\".\n\t IsImmediate: {IsImmediate}.\n\t Player(PlayerName): {Player?.ResolvedPlayerName}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace OpenRA.Network
|
|||||||
// the Order objects directly on the local client.
|
// the Order objects directly on the local client.
|
||||||
data = new MemoryStream();
|
data = new MemoryStream();
|
||||||
foreach (var o in orders)
|
foreach (var o in orders)
|
||||||
data.WriteArray(o.Serialize());
|
data.Write(o.Serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OrderPacket(MemoryStream data)
|
public OrderPacket(MemoryStream data)
|
||||||
@@ -55,7 +55,7 @@ namespace OpenRA.Network
|
|||||||
public byte[] Serialize(int frame)
|
public byte[] Serialize(int frame)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream((int)data.Length + 4);
|
var ms = new MemoryStream((int)data.Length + 4);
|
||||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
ms.Write(frame);
|
||||||
|
|
||||||
data.Position = 0;
|
data.Position = 0;
|
||||||
data.CopyTo(ms);
|
data.CopyTo(ms);
|
||||||
@@ -83,19 +83,19 @@ namespace OpenRA.Network
|
|||||||
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
|
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
|
||||||
ms.WriteArray(BitConverter.GetBytes(data.Frame));
|
ms.Write(data.Frame);
|
||||||
ms.WriteByte((byte)OrderType.SyncHash);
|
ms.WriteByte((byte)OrderType.SyncHash);
|
||||||
ms.WriteArray(BitConverter.GetBytes(data.SyncHash));
|
ms.Write(data.SyncHash);
|
||||||
ms.WriteArray(BitConverter.GetBytes(data.DefeatState));
|
ms.Write(data.DefeatState);
|
||||||
return ms.GetBuffer();
|
return ms.GetBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] SerializePingResponse(long timestamp, byte queueLength)
|
public static byte[] SerializePingResponse(long timestamp, byte queueLength)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(14);
|
var ms = new MemoryStream(14);
|
||||||
ms.WriteArray(BitConverter.GetBytes(0));
|
ms.Write(0);
|
||||||
ms.WriteByte((byte)OrderType.Ping);
|
ms.WriteByte((byte)OrderType.Ping);
|
||||||
ms.WriteArray(BitConverter.GetBytes(timestamp));
|
ms.Write(timestamp);
|
||||||
ms.WriteByte(queueLength);
|
ms.WriteByte(queueLength);
|
||||||
return ms.GetBuffer();
|
return ms.GetBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
const OrderPacket ClientDisconnected = null;
|
const OrderPacket ClientDisconnected = null;
|
||||||
|
|
||||||
|
[FluentReference("frame")]
|
||||||
|
const string DesyncCompareLogs = "notification-desync-compare-logs";
|
||||||
|
|
||||||
readonly SyncReport syncReport;
|
readonly SyncReport syncReport;
|
||||||
readonly Dictionary<int, Queue<(int Frame, OrderPacket Orders)>> pendingOrders = new();
|
readonly Dictionary<int, Queue<(int Frame, OrderPacket Orders)>> pendingOrders = new();
|
||||||
readonly Dictionary<int, (int SyncHash, ulong DefeatState)> syncForFrame = new();
|
readonly Dictionary<int, (int SyncHash, ulong DefeatState)> syncForFrame = new();
|
||||||
@@ -36,6 +39,9 @@ namespace OpenRA.Network
|
|||||||
public string ServerError = null;
|
public string ServerError = null;
|
||||||
public bool AuthenticationFailed = false;
|
public bool AuthenticationFailed = false;
|
||||||
|
|
||||||
|
// The default null means "no map restriction" while an empty set means "all maps restricted"
|
||||||
|
public HashSet<string> ServerMapPool = null;
|
||||||
|
|
||||||
public int NetFrameNumber { get; private set; }
|
public int NetFrameNumber { get; private set; }
|
||||||
public int LocalFrameNumber;
|
public int LocalFrameNumber;
|
||||||
|
|
||||||
@@ -70,7 +76,7 @@ namespace OpenRA.Network
|
|||||||
public int Client;
|
public int Client;
|
||||||
public Order Order;
|
public Order Order;
|
||||||
|
|
||||||
public override string ToString()
|
public override readonly string ToString()
|
||||||
{
|
{
|
||||||
return $"ClientId: {Client} {Order}";
|
return $"ClientId: {Client} {Order}";
|
||||||
}
|
}
|
||||||
@@ -85,7 +91,7 @@ namespace OpenRA.Network
|
|||||||
World.OutOfSync();
|
World.OutOfSync();
|
||||||
IsOutOfSync = true;
|
IsOutOfSync = true;
|
||||||
|
|
||||||
TextNotificationsManager.AddSystemLine($"Out of sync in frame {frame}.\nCompare syncreport.log with other players.");
|
TextNotificationsManager.AddSystemLine(DesyncCompareLogs, "frame", frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartGame()
|
public void StartGame()
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ namespace OpenRA.Network
|
|||||||
if (o.OrderString == "StartGame")
|
if (o.OrderString == "StartGame")
|
||||||
IsValid = true;
|
IsValid = true;
|
||||||
else if (o.OrderString == "SyncInfo" && !IsValid)
|
else if (o.OrderString == "SyncInfo" && !IsValid)
|
||||||
LobbyInfo = Session.Deserialize(o.TargetString);
|
LobbyInfo = Session.Deserialize(o.TargetString, o.OrderString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file.WriteArray(initialContent);
|
file.Write(initialContent);
|
||||||
writer = new BinaryWriter(file);
|
writer = new BinaryWriter(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,8 +92,8 @@ namespace OpenRA.Network
|
|||||||
public void ReceiveFrame(int clientID, int frame, byte[] data)
|
public void ReceiveFrame(int clientID, int frame, byte[] data)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(4 + data.Length);
|
var ms = new MemoryStream(4 + data.Length);
|
||||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
ms.Write(frame);
|
||||||
ms.WriteArray(data);
|
ms.Write(data);
|
||||||
Receive(clientID, ms.GetBuffer());
|
Receive(clientID, ms.GetBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ namespace OpenRA.Network
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Session Deserialize(string data)
|
public static Session Deserialize(string data, string name)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var session = new Session();
|
var session = new Session();
|
||||||
|
|
||||||
var nodes = MiniYaml.FromString(data);
|
var nodes = MiniYaml.FromString(data, name);
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var strings = node.Key.Split('@');
|
var strings = node.Key.Split('@');
|
||||||
@@ -227,7 +227,7 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
var gs = FieldLoader.Load<Global>(data);
|
var gs = FieldLoader.Load<Global>(data);
|
||||||
|
|
||||||
var optionsNode = data.Nodes.FirstOrDefault(n => n.Key == "Options");
|
var optionsNode = data.NodeWithKeyOrDefault("Options");
|
||||||
if (optionsNode != null)
|
if (optionsNode != null)
|
||||||
foreach (var n in optionsNode.Value.Nodes)
|
foreach (var n in optionsNode.Value.Nodes)
|
||||||
gs.LobbyOptions[n.Key] = FieldLoader.Load<LobbyOptionState>(n.Value);
|
gs.LobbyOptions[n.Key] = FieldLoader.Load<LobbyOptionState>(n.Value);
|
||||||
@@ -238,8 +238,9 @@ namespace OpenRA.Network
|
|||||||
public MiniYamlNode Serialize()
|
public MiniYamlNode Serialize()
|
||||||
{
|
{
|
||||||
var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this));
|
var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this));
|
||||||
var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value))).ToList();
|
var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value)));
|
||||||
data.Value.Nodes.Add(new MiniYamlNode("Options", new MiniYaml(null, options)));
|
data = data.WithValue(data.Value.WithNodesAppended(
|
||||||
|
new[] { new MiniYamlNode("Options", new MiniYaml(null, options)) }));
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +265,7 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
var sessionData = new List<MiniYamlNode>()
|
var sessionData = new List<MiniYamlNode>()
|
||||||
{
|
{
|
||||||
new MiniYamlNode("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
|
new("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var client in Clients)
|
foreach (var client in Clients)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ namespace OpenRA.Network
|
|||||||
Log.Write("sync", $"Player: {Game.Settings.Player.Name} ({Platform.CurrentPlatform} {Environment.OSVersion} {Platform.RuntimeVersion})");
|
Log.Write("sync", $"Player: {Game.Settings.Player.Name} ({Platform.CurrentPlatform} {Environment.OSVersion} {Platform.RuntimeVersion})");
|
||||||
if (Game.IsHost)
|
if (Game.IsHost)
|
||||||
Log.Write("sync", "Player is host.");
|
Log.Write("sync", "Player is host.");
|
||||||
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.Title} at Version {mod.Version})");
|
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.TitleTranslated} at Version {mod.Version})");
|
||||||
Log.Write("sync", $"Sync for net frame {r.Frame} -------------");
|
Log.Write("sync", $"Sync for net frame {r.Frame} -------------");
|
||||||
Log.Write("sync", $"SharedRandom: {r.SyncedRandom} (#{r.TotalCount})");
|
Log.Write("sync", $"SharedRandom: {r.SyncedRandom} (#{r.TotalCount})");
|
||||||
Log.Write("sync", "Synced Traits:");
|
Log.Write("sync", "Synced Traits:");
|
||||||
@@ -201,8 +201,12 @@ namespace OpenRA.Network
|
|||||||
public TypeInfo(Type type)
|
public TypeInfo(Type type)
|
||||||
{
|
{
|
||||||
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||||
var fields = type.GetFields(Flags).Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<SyncAttribute>());
|
var fields = type.GetFields(Flags)
|
||||||
var properties = type.GetProperties(Flags).Where(pi => pi.HasAttribute<SyncAttribute>());
|
.Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<SyncAttribute>())
|
||||||
|
.ToList();
|
||||||
|
var properties = type.GetProperties(Flags)
|
||||||
|
.Where(pi => pi.HasAttribute<SyncAttribute>())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
foreach (var prop in properties)
|
foreach (var prop in properties)
|
||||||
if (!prop.CanRead || prop.GetIndexParameters().Length > 0)
|
if (!prop.CanRead || prop.GetIndexParameters().Length > 0)
|
||||||
@@ -300,7 +304,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public object this[int index]
|
public object this[int index]
|
||||||
{
|
{
|
||||||
get
|
readonly get
|
||||||
{
|
{
|
||||||
if (item2OrSentinel == Sentinel)
|
if (item2OrSentinel == Sentinel)
|
||||||
return ((object[])item1OrArray)[index];
|
return ((object[])item1OrArray)[index];
|
||||||
|
|||||||
@@ -20,6 +20,24 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
public const int ChatMessageMaxLength = 2500;
|
public const int ChatMessageMaxLength = 2500;
|
||||||
|
|
||||||
|
[FluentReference("player")]
|
||||||
|
const string Joined = "notification-joined";
|
||||||
|
|
||||||
|
[FluentReference("player")]
|
||||||
|
const string Left = "notification-lobby-disconnected";
|
||||||
|
|
||||||
|
[FluentReference]
|
||||||
|
const string GameStarted = "notification-game-has-started";
|
||||||
|
|
||||||
|
[FluentReference]
|
||||||
|
const string GameSaved = "notification-game-saved";
|
||||||
|
|
||||||
|
[FluentReference("player")]
|
||||||
|
const string GamePaused = "notification-game-paused";
|
||||||
|
|
||||||
|
[FluentReference("player")]
|
||||||
|
const string GameUnpaused = "notification-game-unpaused";
|
||||||
|
|
||||||
public static int? KickVoteTarget { get; internal set; }
|
public static int? KickVoteTarget { get; internal set; }
|
||||||
|
|
||||||
static Player FindPlayerByClient(this World world, Session.Client c)
|
static Player FindPlayerByClient(this World world, Session.Client c)
|
||||||
@@ -38,344 +56,352 @@ namespace OpenRA.Network
|
|||||||
TextNotificationsManager.AddSystemLine(order.TargetString);
|
TextNotificationsManager.AddSystemLine(order.TargetString);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Client side translated server message
|
// Client side resolved server message
|
||||||
case "LocalizedMessage":
|
case "FluentMessage":
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(order.TargetString))
|
if (string.IsNullOrEmpty(order.TargetString))
|
||||||
break;
|
|
||||||
|
|
||||||
var yaml = MiniYaml.FromString(order.TargetString);
|
|
||||||
foreach (var node in yaml)
|
|
||||||
{
|
|
||||||
var localizedMessage = new LocalizedMessage(node.Value);
|
|
||||||
TextNotificationsManager.AddSystemLine(localizedMessage.TranslatedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
var yaml = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||||
|
foreach (var node in yaml)
|
||||||
|
{
|
||||||
|
var message = new FluentMessage(node.Value);
|
||||||
|
if (message.Key == Joined)
|
||||||
|
TextNotificationsManager.AddPlayerJoinedLine(message.Key, message.Arguments);
|
||||||
|
else if (message.Key == Left)
|
||||||
|
TextNotificationsManager.AddPlayerLeftLine(message.Key, message.Arguments);
|
||||||
|
else
|
||||||
|
TextNotificationsManager.AddSystemLine(message.Key, message.Arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "DisableChatEntry":
|
case "DisableChatEntry":
|
||||||
{
|
{
|
||||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
break;
|
|
||||||
|
|
||||||
// Server may send MaxValue to indicate that it is disabled until further notice
|
|
||||||
if (order.ExtraData == uint.MaxValue)
|
|
||||||
TextNotificationsManager.ChatDisabledUntil = uint.MaxValue;
|
|
||||||
else
|
|
||||||
TextNotificationsManager.ChatDisabledUntil = Game.RunTime + order.ExtraData;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
// Server may send MaxValue to indicate that it is disabled until further notice
|
||||||
|
if (order.ExtraData == uint.MaxValue)
|
||||||
|
TextNotificationsManager.ChatDisabledUntil = uint.MaxValue;
|
||||||
|
else
|
||||||
|
TextNotificationsManager.ChatDisabledUntil = Game.RunTime + order.ExtraData;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "StartKickVote":
|
case "StartKickVote":
|
||||||
{
|
{
|
||||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
break;
|
|
||||||
|
|
||||||
KickVoteTarget = (int)order.ExtraData;
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
KickVoteTarget = (int)order.ExtraData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "EndKickVote":
|
case "EndKickVote":
|
||||||
{
|
{
|
||||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
break;
|
|
||||||
|
|
||||||
if (KickVoteTarget == (int)order.ExtraData)
|
|
||||||
KickVoteTarget = null;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
if (KickVoteTarget == (int)order.ExtraData)
|
||||||
|
KickVoteTarget = null;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "Chat":
|
case "Chat":
|
||||||
|
{
|
||||||
|
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||||
|
if (client == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Cut chat messages to the hard limit to avoid exploits
|
||||||
|
var message = order.TargetString;
|
||||||
|
if (message.Length > ChatMessageMaxLength)
|
||||||
|
message = order.TargetString[..ChatMessageMaxLength];
|
||||||
|
|
||||||
|
// ExtraData 0 means this is a normal chat order, everything else is team chat
|
||||||
|
if (order.ExtraData == 0)
|
||||||
{
|
{
|
||||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
var p = world?.FindPlayerByClient(client);
|
||||||
if (client == null)
|
var suffix = (p != null && p.WinState == WinState.Lost) ? " (Dead)" : "";
|
||||||
break;
|
suffix = client.IsObserver ? " (Spectator)" : suffix;
|
||||||
|
|
||||||
// Cut chat messages to the hard limit to avoid exploits
|
if (orderManager.LocalClient != null && client != orderManager.LocalClient && client.Team > 0 && client.Team == orderManager.LocalClient.Team)
|
||||||
var message = order.TargetString;
|
suffix += " (Ally)";
|
||||||
if (message.Length > ChatMessageMaxLength)
|
|
||||||
message = order.TargetString[..ChatMessageMaxLength];
|
|
||||||
|
|
||||||
// ExtraData 0 means this is a normal chat order, everything else is team chat
|
TextNotificationsManager.AddChatLine(clientId, client.Name + suffix, message, client.Color);
|
||||||
if (order.ExtraData == 0)
|
break;
|
||||||
{
|
}
|
||||||
var p = world?.FindPlayerByClient(client);
|
|
||||||
var suffix = (p != null && p.WinState == WinState.Lost) ? " (Dead)" : "";
|
|
||||||
suffix = client.IsObserver ? " (Spectator)" : suffix;
|
|
||||||
|
|
||||||
if (orderManager.LocalClient != null && client != orderManager.LocalClient && client.Team > 0 && client.Team == orderManager.LocalClient.Team)
|
// We are still in the lobby
|
||||||
suffix += " (Ally)";
|
if (world == null)
|
||||||
|
{
|
||||||
TextNotificationsManager.AddChatLine(clientId, client.Name + suffix, message, client.Color);
|
var prefix = order.ExtraData == uint.MaxValue ? "[Spectators] " : "[Team] ";
|
||||||
break;
|
if (orderManager.LocalClient != null && client.Team == orderManager.LocalClient.Team)
|
||||||
}
|
TextNotificationsManager.AddChatLine(clientId, prefix + client.Name, message, client.Color);
|
||||||
|
|
||||||
// We are still in the lobby
|
|
||||||
if (world == null)
|
|
||||||
{
|
|
||||||
var prefix = order.ExtraData == uint.MaxValue ? "[Spectators] " : "[Team] ";
|
|
||||||
if (orderManager.LocalClient != null && client.Team == orderManager.LocalClient.Team)
|
|
||||||
TextNotificationsManager.AddChatLine(clientId, prefix + client.Name, message, client.Color);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var player = world.FindPlayerByClient(client);
|
|
||||||
var localClientIsObserver = world.IsReplay || (orderManager.LocalClient != null && orderManager.LocalClient.IsObserver)
|
|
||||||
|| (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
|
|
||||||
|
|
||||||
// ExtraData gives us the team number, uint.MaxValue means Spectators
|
|
||||||
if (order.ExtraData == uint.MaxValue && localClientIsObserver)
|
|
||||||
{
|
|
||||||
// Validate before adding the line
|
|
||||||
if (client.IsObserver || (player != null && player.WinState != WinState.Undefined))
|
|
||||||
TextNotificationsManager.AddChatLine(clientId, "[Spectators] " + client.Name, message, client.Color);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var valid = client.Team == order.ExtraData && player != null && player.WinState == WinState.Undefined;
|
|
||||||
var isSameTeam = orderManager.LocalClient != null && order.ExtraData == orderManager.LocalClient.Team
|
|
||||||
&& world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined;
|
|
||||||
|
|
||||||
if (valid && (isSameTeam || world.IsReplay))
|
|
||||||
TextNotificationsManager.AddChatLine(clientId, "[Team" + (world.IsReplay ? " " + order.ExtraData : "") + "] " + client.Name, message, client.Color);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var player = world.FindPlayerByClient(client);
|
||||||
|
var localClientIsObserver = world.IsReplay || (orderManager.LocalClient != null && orderManager.LocalClient.IsObserver)
|
||||||
|
|| (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
|
||||||
|
|
||||||
|
// ExtraData gives us the team number, uint.MaxValue means Spectators
|
||||||
|
if (order.ExtraData == uint.MaxValue && localClientIsObserver)
|
||||||
|
{
|
||||||
|
// Validate before adding the line
|
||||||
|
if (client.IsObserver || (player != null && player.WinState != WinState.Undefined))
|
||||||
|
TextNotificationsManager.AddChatLine(clientId, "[Spectators] " + client.Name, message, client.Color);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var valid = client.Team == order.ExtraData && player != null && player.WinState == WinState.Undefined;
|
||||||
|
var isSameTeam = orderManager.LocalClient != null && order.ExtraData == orderManager.LocalClient.Team
|
||||||
|
&& world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined;
|
||||||
|
|
||||||
|
if (valid && (isSameTeam || world.IsReplay))
|
||||||
|
TextNotificationsManager.AddChatLine(clientId, "[Team" + (world.IsReplay ? " " + order.ExtraData : "") + "] " + client.Name, message, client.Color);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "StartGame":
|
case "StartGame":
|
||||||
|
{
|
||||||
|
if (Game.ModData.MapCache[orderManager.LobbyInfo.GlobalSettings.Map].Status != MapStatus.Available)
|
||||||
{
|
{
|
||||||
if (Game.ModData.MapCache[orderManager.LobbyInfo.GlobalSettings.Map].Status != MapStatus.Available)
|
Game.Disconnect();
|
||||||
{
|
Game.LoadShellMap();
|
||||||
Game.Disconnect();
|
|
||||||
Game.LoadShellMap();
|
|
||||||
|
|
||||||
// TODO: After adding a startup error dialog, notify the replay load failure.
|
// TODO: After adding a startup error dialog, notify the replay load failure.
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(order.TargetString))
|
|
||||||
{
|
|
||||||
var data = MiniYaml.FromString(order.TargetString);
|
|
||||||
var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame");
|
|
||||||
if (saveLastOrdersFrame != null)
|
|
||||||
orderManager.GameSaveLastFrame =
|
|
||||||
FieldLoader.GetValue<int>("saveLastOrdersFrame", saveLastOrdersFrame.Value.Value);
|
|
||||||
|
|
||||||
var saveSyncFrame = data.FirstOrDefault(n => n.Key == "SaveSyncFrame");
|
|
||||||
if (saveSyncFrame != null)
|
|
||||||
orderManager.GameSaveLastSyncFrame =
|
|
||||||
FieldLoader.GetValue<int>("SaveSyncFrame", saveSyncFrame.Value.Value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
TextNotificationsManager.AddSystemLine("The game has started.");
|
|
||||||
|
|
||||||
Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map, WorldType.Regular);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(order.TargetString))
|
||||||
|
{
|
||||||
|
var data = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||||
|
var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame");
|
||||||
|
if (saveLastOrdersFrame != null)
|
||||||
|
orderManager.GameSaveLastFrame =
|
||||||
|
FieldLoader.GetValue<int>("saveLastOrdersFrame", saveLastOrdersFrame.Value.Value);
|
||||||
|
|
||||||
|
var saveSyncFrame = data.FirstOrDefault(n => n.Key == "SaveSyncFrame");
|
||||||
|
if (saveSyncFrame != null)
|
||||||
|
orderManager.GameSaveLastSyncFrame =
|
||||||
|
FieldLoader.GetValue<int>("SaveSyncFrame", saveSyncFrame.Value.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
TextNotificationsManager.AddSystemLine(GameStarted);
|
||||||
|
|
||||||
|
Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map, WorldType.Regular);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "SaveTraitData":
|
case "SaveTraitData":
|
||||||
{
|
{
|
||||||
var data = MiniYaml.FromString(order.TargetString)[0];
|
var data = MiniYaml.FromString(order.TargetString, order.OrderString)[0];
|
||||||
var traitIndex = int.Parse(data.Key);
|
var traitIndex = Exts.ParseInt32Invariant(data.Key);
|
||||||
|
|
||||||
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "GameSaved":
|
case "GameSaved":
|
||||||
if (!orderManager.World.IsReplay)
|
if (!orderManager.World.IsReplay)
|
||||||
TextNotificationsManager.AddSystemLine("Game saved");
|
TextNotificationsManager.AddSystemLine(GameSaved);
|
||||||
|
|
||||||
foreach (var nsr in orderManager.World.WorldActor.TraitsImplementing<INotifyGameSaved>())
|
foreach (var nsr in orderManager.World.WorldActor.TraitsImplementing<INotifyGameSaved>())
|
||||||
nsr.GameSaved(orderManager.World);
|
nsr.GameSaved(orderManager.World);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "PauseGame":
|
case "PauseGame":
|
||||||
|
{
|
||||||
|
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||||
|
if (client != null)
|
||||||
{
|
{
|
||||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
var pause = order.TargetString == "Pause";
|
||||||
if (client != null)
|
|
||||||
{
|
|
||||||
var pause = order.TargetString == "Pause";
|
|
||||||
|
|
||||||
// Prevent injected unpause orders from restarting a finished game
|
// Prevent injected unpause orders from restarting a finished game
|
||||||
if (orderManager.World.IsGameOver && !pause)
|
if (orderManager.World.IsGameOver && !pause)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1)
|
if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1)
|
||||||
{
|
TextNotificationsManager.AddSystemLine(pause ? GamePaused : GameUnpaused, "player", client.Name);
|
||||||
var pausetext = $"The game is {(pause ? "paused" : "un-paused")} by {client.Name}";
|
|
||||||
TextNotificationsManager.AddSystemLine(pausetext);
|
|
||||||
}
|
|
||||||
|
|
||||||
orderManager.World.Paused = pause;
|
orderManager.World.Paused = pause;
|
||||||
orderManager.World.PredictedPaused = pause;
|
orderManager.World.PredictedPaused = pause;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "HandshakeRequest":
|
case "HandshakeRequest":
|
||||||
|
{
|
||||||
|
// Switch to the server's mod if we need and are able to
|
||||||
|
var mod = Game.ModData.Manifest;
|
||||||
|
var request = HandshakeRequest.Deserialize(order.TargetString, order.OrderString);
|
||||||
|
|
||||||
|
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
|
||||||
|
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) &&
|
||||||
|
Game.ExternalMods.TryGetValue(externalKey, out var external))
|
||||||
{
|
{
|
||||||
// Switch to the server's mod if we need and are able to
|
// The ConnectionFailedLogic will prompt the user to switch mods
|
||||||
var mod = Game.ModData.Manifest;
|
CurrentServerSettings.ServerExternalMod = external;
|
||||||
var request = HandshakeRequest.Deserialize(order.TargetString);
|
orderManager.Connection.Dispose();
|
||||||
|
|
||||||
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
|
|
||||||
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
|
|
||||||
CurrentServerSettings.ServerExternalMod = external;
|
|
||||||
orderManager.Connection.Dispose();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Game.Settings.Player.Name = Settings.SanitizedPlayerName(Game.Settings.Player.Name);
|
|
||||||
Game.Settings.Save();
|
|
||||||
|
|
||||||
// Otherwise send the handshake with our current settings and let the server reject us
|
|
||||||
var info = new Session.Client()
|
|
||||||
{
|
|
||||||
Name = Game.Settings.Player.Name,
|
|
||||||
PreferredColor = Game.Settings.Player.Color,
|
|
||||||
Color = Game.Settings.Player.Color,
|
|
||||||
Faction = "Random",
|
|
||||||
SpawnPoint = 0,
|
|
||||||
Team = 0,
|
|
||||||
State = Session.ClientState.Invalid
|
|
||||||
};
|
|
||||||
|
|
||||||
var localProfile = Game.LocalPlayerProfile;
|
|
||||||
var response = new HandshakeResponse()
|
|
||||||
{
|
|
||||||
Client = info,
|
|
||||||
Mod = mod.Id,
|
|
||||||
Version = mod.Metadata.Version,
|
|
||||||
Password = CurrentServerSettings.Password,
|
|
||||||
Fingerprint = localProfile.Fingerprint,
|
|
||||||
OrdersProtocol = ProtocolVersion.Orders
|
|
||||||
};
|
|
||||||
|
|
||||||
if (request.AuthToken != null && response.Fingerprint != null)
|
|
||||||
response.AuthSignature = localProfile.Sign(request.AuthToken);
|
|
||||||
|
|
||||||
orderManager.IssueOrder(new Order("HandshakeResponse", null, false)
|
|
||||||
{
|
|
||||||
Type = OrderType.Handshake,
|
|
||||||
IsImmediate = true,
|
|
||||||
TargetString = response.Serialize()
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Game.Settings.Player.Name = Settings.SanitizedPlayerName(Game.Settings.Player.Name);
|
||||||
|
Game.Settings.Save();
|
||||||
|
|
||||||
|
// Otherwise send the handshake with our current settings and let the server reject us
|
||||||
|
var info = new Session.Client()
|
||||||
|
{
|
||||||
|
Name = Game.Settings.Player.Name,
|
||||||
|
PreferredColor = Game.Settings.Player.Color,
|
||||||
|
Color = Game.Settings.Player.Color,
|
||||||
|
Faction = "Random",
|
||||||
|
SpawnPoint = 0,
|
||||||
|
Team = 0,
|
||||||
|
State = Session.ClientState.Invalid
|
||||||
|
};
|
||||||
|
|
||||||
|
var localProfile = Game.LocalPlayerProfile;
|
||||||
|
var response = new HandshakeResponse()
|
||||||
|
{
|
||||||
|
Client = info,
|
||||||
|
Mod = mod.Id,
|
||||||
|
Version = mod.Metadata.Version,
|
||||||
|
Password = CurrentServerSettings.Password,
|
||||||
|
Fingerprint = localProfile.Fingerprint,
|
||||||
|
OrdersProtocol = ProtocolVersion.Orders
|
||||||
|
};
|
||||||
|
|
||||||
|
if (request.AuthToken != null && response.Fingerprint != null)
|
||||||
|
response.AuthSignature = localProfile.Sign(request.AuthToken);
|
||||||
|
|
||||||
|
orderManager.IssueOrder(new Order("HandshakeResponse", null, false)
|
||||||
|
{
|
||||||
|
Type = OrderType.Handshake,
|
||||||
|
IsImmediate = true,
|
||||||
|
TargetString = response.Serialize()
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "ServerError":
|
case "ServerError":
|
||||||
{
|
{
|
||||||
orderManager.ServerError = order.TargetString;
|
orderManager.ServerError = order.TargetString;
|
||||||
orderManager.AuthenticationFailed = false;
|
orderManager.AuthenticationFailed = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "AuthenticationError":
|
case "AuthenticationError":
|
||||||
{
|
{
|
||||||
// The ConnectionFailedLogic will prompt the user for the password
|
// The ConnectionFailedLogic will prompt the user for the password
|
||||||
orderManager.ServerError = order.TargetString;
|
orderManager.ServerError = order.TargetString;
|
||||||
orderManager.AuthenticationFailed = true;
|
orderManager.AuthenticationFailed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SyncInfo":
|
case "SyncInfo":
|
||||||
{
|
{
|
||||||
orderManager.LobbyInfo = Session.Deserialize(order.TargetString);
|
orderManager.LobbyInfo = Session.Deserialize(order.TargetString, order.OrderString);
|
||||||
Game.SyncLobbyInfo();
|
Game.SyncLobbyInfo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SyncLobbyClients":
|
case "SyncLobbyClients":
|
||||||
|
{
|
||||||
|
var clients = new List<Session.Client>();
|
||||||
|
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||||
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var clients = new List<Session.Client>();
|
var strings = node.Key.Split('@');
|
||||||
var nodes = MiniYaml.FromString(order.TargetString);
|
if (strings[0] == "Client")
|
||||||
foreach (var node in nodes)
|
clients.Add(Session.Client.Deserialize(node.Value));
|
||||||
{
|
|
||||||
var strings = node.Key.Split('@');
|
|
||||||
if (strings[0] == "Client")
|
|
||||||
clients.Add(Session.Client.Deserialize(node.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
orderManager.LobbyInfo.Clients = clients;
|
|
||||||
Game.SyncLobbyInfo();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orderManager.LobbyInfo.Clients = clients;
|
||||||
|
Game.SyncLobbyInfo();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "SyncLobbySlots":
|
case "SyncLobbySlots":
|
||||||
|
{
|
||||||
|
var slots = new Dictionary<string, Session.Slot>();
|
||||||
|
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||||
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var slots = new Dictionary<string, Session.Slot>();
|
var strings = node.Key.Split('@');
|
||||||
var nodes = MiniYaml.FromString(order.TargetString);
|
if (strings[0] == "Slot")
|
||||||
foreach (var node in nodes)
|
|
||||||
{
|
{
|
||||||
var strings = node.Key.Split('@');
|
var slot = Session.Slot.Deserialize(node.Value);
|
||||||
if (strings[0] == "Slot")
|
slots.Add(slot.PlayerReference, slot);
|
||||||
{
|
|
||||||
var slot = Session.Slot.Deserialize(node.Value);
|
|
||||||
slots.Add(slot.PlayerReference, slot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
orderManager.LobbyInfo.Slots = slots;
|
|
||||||
Game.SyncLobbyInfo();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orderManager.LobbyInfo.Slots = slots;
|
||||||
|
Game.SyncLobbyInfo();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "SyncLobbyGlobalSettings":
|
case "SyncLobbyGlobalSettings":
|
||||||
|
{
|
||||||
|
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||||
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var nodes = MiniYaml.FromString(order.TargetString);
|
var strings = node.Key.Split('@');
|
||||||
foreach (var node in nodes)
|
if (strings[0] == "GlobalSettings")
|
||||||
{
|
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
|
||||||
var strings = node.Key.Split('@');
|
|
||||||
if (strings[0] == "GlobalSettings")
|
|
||||||
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Game.SyncLobbyInfo();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Game.SyncLobbyInfo();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "SyncConnectionQuality":
|
case "SyncConnectionQuality":
|
||||||
|
{
|
||||||
|
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
||||||
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var nodes = MiniYaml.FromString(order.TargetString);
|
var strings = node.Key.Split('@');
|
||||||
foreach (var node in nodes)
|
if (strings[0] == "ConnectionQuality")
|
||||||
{
|
{
|
||||||
var strings = node.Key.Split('@');
|
var client = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.Index == Exts.ParseInt32Invariant(strings[1]));
|
||||||
if (strings[0] == "ConnectionQuality")
|
if (client != null)
|
||||||
{
|
client.ConnectionQuality = FieldLoader.GetValue<Session.ConnectionQuality>("ConnectionQuality", node.Value.Value);
|
||||||
var client = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.Index == int.Parse(strings[1]));
|
|
||||||
if (client != null)
|
|
||||||
client.ConnectionQuality = FieldLoader.GetValue<Session.ConnectionQuality>("ConnectionQuality", node.Value.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "SyncMapPool":
|
||||||
|
{
|
||||||
|
orderManager.ServerMapPool = FieldLoader.GetValue<HashSet<string>>("SyncMapPool", order.TargetString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
if (world == null)
|
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;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user