Compare commits
1761 Commits
playtest-2
...
playtest-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82acdbc32a | ||
|
|
462a3ef3c0 | ||
|
|
d4e6815f64 | ||
|
|
723ffdf33d | ||
|
|
8376b09129 | ||
|
|
fa9ce3e2ac | ||
|
|
605681b252 | ||
|
|
a3c5945f2a | ||
|
|
d7ef22d64f | ||
|
|
d907192be0 | ||
|
|
74f8db0578 | ||
|
|
9dca6ef1c3 | ||
|
|
c093e7c90b | ||
|
|
66cf912da0 | ||
|
|
42baa0c42f | ||
|
|
305ba1c567 | ||
|
|
a84b7591f6 | ||
|
|
9d8f0634b1 | ||
|
|
4cd4e1f8ea | ||
|
|
3207d01cf2 | ||
|
|
9fc0f79703 | ||
|
|
1a2d43fc99 | ||
|
|
b944b21325 | ||
|
|
c7e0bc4c08 | ||
|
|
a69417f0a6 | ||
|
|
c36609cc9f | ||
|
|
1edf313090 | ||
|
|
813d48dd70 | ||
|
|
9caf12d133 | ||
|
|
de5bcbbca5 | ||
|
|
8aa548f70c | ||
|
|
ce6e73dc92 | ||
|
|
4a02e6c6cc | ||
|
|
d6a31bb0cc | ||
|
|
f5daa19a1c | ||
|
|
a96e445e4d | ||
|
|
30b1f926f2 | ||
|
|
58e8b123db | ||
|
|
be04d232c0 | ||
|
|
659ec5e335 | ||
|
|
7f37454666 | ||
|
|
433d69af7a | ||
|
|
21c21e4963 | ||
|
|
1c0885c636 | ||
|
|
dccb3ce9ce | ||
|
|
22b39f35aa | ||
|
|
5ab3276a2d | ||
|
|
36420114e0 | ||
|
|
7cda031888 | ||
|
|
fc85a4864d | ||
|
|
99226c3df5 | ||
|
|
ed395c8ace | ||
|
|
edbded8f0a | ||
|
|
c095690619 | ||
|
|
628cc837ef | ||
|
|
0c32fca6c0 | ||
|
|
56b5ace109 | ||
|
|
5572650da2 | ||
|
|
41669d246f | ||
|
|
49c837e7d0 | ||
|
|
bc5e7d1497 | ||
|
|
78bef8a98f | ||
|
|
4f3d8f4caa | ||
|
|
0369f7516d | ||
|
|
3b0415678c | ||
|
|
69867b6c06 | ||
|
|
231bf01f18 | ||
|
|
56fe08cb00 | ||
|
|
f752e04b03 | ||
|
|
a50e72f68d | ||
|
|
0958197df2 | ||
|
|
f336a956cf | ||
|
|
19fa03435b | ||
|
|
fa65e7fd3f | ||
|
|
f794cf69f9 | ||
|
|
366dc5383c | ||
|
|
fd2b14f464 | ||
|
|
590976a8e7 | ||
|
|
bdbb651b98 | ||
|
|
703618be19 | ||
|
|
1f37728ecf | ||
|
|
d955efff14 | ||
|
|
3fdee06dc7 | ||
|
|
c0cd7259b3 | ||
|
|
c31f2abfc9 | ||
|
|
06df75ffee | ||
|
|
855e839b77 | ||
|
|
06437df9b0 | ||
|
|
f4af5c1764 | ||
|
|
277699cbd5 | ||
|
|
e4cac1fffc | ||
|
|
e164e48aae | ||
|
|
5eec9d29cb | ||
|
|
94abd8a928 | ||
|
|
b18c2fe855 | ||
|
|
02a7ff87db | ||
|
|
7c0f6ead3a | ||
|
|
d72b1ffd49 | ||
|
|
c82be175e1 | ||
|
|
dac35a60ad | ||
|
|
ee02af3605 | ||
|
|
74ed202b29 | ||
|
|
300281695a | ||
|
|
de22556153 | ||
|
|
5bcb1a678c | ||
|
|
8a9426a0d4 | ||
|
|
dd9ab16401 | ||
|
|
12e6932930 | ||
|
|
95f18d4bc3 | ||
|
|
8c9cc93185 | ||
|
|
b58a8aaa0f | ||
|
|
c5b7728ac9 | ||
|
|
c9dddc342c | ||
|
|
8433bc0948 | ||
|
|
52a916012f | ||
|
|
2fe7e1bff9 | ||
|
|
0788e5ff3e | ||
|
|
0fb5853b7a | ||
|
|
b30285e38d | ||
|
|
02e4bfba95 | ||
|
|
b5f5d5f9d5 | ||
|
|
f2b3a9f837 | ||
|
|
6af14c16c9 | ||
|
|
211f7160dc | ||
|
|
8a1463a471 | ||
|
|
b623214e04 | ||
|
|
6b536ca88a | ||
|
|
445b736885 | ||
|
|
e8dd85419f | ||
|
|
b1fd392486 | ||
|
|
e487c3366d | ||
|
|
ade27ad8b9 | ||
|
|
34bcae9abb | ||
|
|
8894fdeaf9 | ||
|
|
ff14b75e1b | ||
|
|
b5ef9c29cf | ||
|
|
f344ccb714 | ||
|
|
9b71317280 | ||
|
|
1ac6912c2a | ||
|
|
3188532e59 | ||
|
|
d9d8c23c63 | ||
|
|
dfe0c15399 | ||
|
|
0751b30d33 | ||
|
|
f4dc29f9db | ||
|
|
ce7f9e71c1 | ||
|
|
05f21fcbe2 | ||
|
|
00f2ba1a53 | ||
|
|
991e0a4c9a | ||
|
|
aa28881726 | ||
|
|
6d288aba2f | ||
|
|
0980856072 | ||
|
|
0d98405bdc | ||
|
|
69441a4fee | ||
|
|
a65bb17d68 | ||
|
|
6e6bf1ca81 | ||
|
|
474463111f | ||
|
|
0b4a54ab54 | ||
|
|
65c0cf1065 | ||
|
|
1c2ce0dcc0 | ||
|
|
e251126dd4 | ||
|
|
f2a4e7b984 | ||
|
|
9e659cacf2 | ||
|
|
c5e9567875 | ||
|
|
44f1af7059 | ||
|
|
8f511a3bb6 | ||
|
|
4f7a01a291 | ||
|
|
af6330b1bd | ||
|
|
55ff0ac1f4 | ||
|
|
51bbfc39b0 | ||
|
|
90bb2db349 | ||
|
|
0d36bc19c6 | ||
|
|
a9a7777293 | ||
|
|
bf66068557 | ||
|
|
68eec52cef | ||
|
|
a065e6a47c | ||
|
|
efe135e38b | ||
|
|
07e47b6a28 | ||
|
|
8f5d8de1c2 | ||
|
|
2867334c00 | ||
|
|
dc390a7301 | ||
|
|
ad683d9226 | ||
|
|
1c2eaa2654 | ||
|
|
7ef1dccdcf | ||
|
|
ff488b77b5 | ||
|
|
92cceea2b8 | ||
|
|
8b522680e3 | ||
|
|
7507333cd3 | ||
|
|
f470f9ab91 | ||
|
|
a120b9d37e | ||
|
|
1b1b9dc29b | ||
|
|
ef04e2e1e8 | ||
|
|
321d4b8afd | ||
|
|
c3e6c4685f | ||
|
|
6362bbd176 | ||
|
|
07fb5e8027 | ||
|
|
8fe82ed976 | ||
|
|
25b8e7fefc | ||
|
|
ad4a443fc2 | ||
|
|
01eaa6b228 | ||
|
|
c442bd83f8 | ||
|
|
ff799303b0 | ||
|
|
f09241d263 | ||
|
|
1db982276a | ||
|
|
718c6d03cc | ||
|
|
57fba4e18e | ||
|
|
b219731173 | ||
|
|
a0eea7bcc0 | ||
|
|
0c3071b9c6 | ||
|
|
0066010792 | ||
|
|
0b61954e39 | ||
|
|
a0cd008da6 | ||
|
|
3d2ba9d5bf | ||
|
|
01e6babd54 | ||
|
|
96d023de87 | ||
|
|
3ca2bb1d23 | ||
|
|
595717fff0 | ||
|
|
d838d08570 | ||
|
|
265f915442 | ||
|
|
dcac966d49 | ||
|
|
4110c199fb | ||
|
|
14c0d011ea | ||
|
|
a167f9680f | ||
|
|
062dc2bd40 | ||
|
|
1ce9acd442 | ||
|
|
8a285f9b19 | ||
|
|
164abfdae1 | ||
|
|
bd2b3d9793 | ||
|
|
cbd0583289 | ||
|
|
023d80b94d | ||
|
|
5254348819 | ||
|
|
4ec5a4b34a | ||
|
|
e4ba9733fe | ||
|
|
a332fba702 | ||
|
|
83561d639d | ||
|
|
9dd4f938da | ||
|
|
1a2aafa17c | ||
|
|
a4e80e1153 | ||
|
|
a261abbc3f | ||
|
|
27f780c5d1 | ||
|
|
11ab4a7935 | ||
|
|
4dd532f60e | ||
|
|
138715b509 | ||
|
|
d97df78f4f | ||
|
|
56c0680685 | ||
|
|
95f675becd | ||
|
|
a5ef6801c9 | ||
|
|
75a47aabb5 | ||
|
|
73f29005bd | ||
|
|
f1d439a07f | ||
|
|
9ce4ef0bff | ||
|
|
9e081763ad | ||
|
|
378f66a1ff | ||
|
|
3d0efa1cbe | ||
|
|
cae07bb408 | ||
|
|
c94c1f8a6c | ||
|
|
0847cd33bd | ||
|
|
dc40442118 | ||
|
|
2959a2c137 | ||
|
|
bf00577d33 | ||
|
|
c4bd9fb7aa | ||
|
|
482f2fc335 | ||
|
|
925e042455 | ||
|
|
c525c48d25 | ||
|
|
f90dba2c6b | ||
|
|
e28f45f785 | ||
|
|
87eca8f2c0 | ||
|
|
960f829221 | ||
|
|
8a4303cc94 | ||
|
|
7a4ac01348 | ||
|
|
6dda4fa9f7 | ||
|
|
af2b32e7ba | ||
|
|
069b7c5500 | ||
|
|
ae1983faba | ||
|
|
bcfa0c9ae9 | ||
|
|
88ba974ea5 | ||
|
|
721c03d9af | ||
|
|
384435f8eb | ||
|
|
e808549637 | ||
|
|
bde13a8572 | ||
|
|
98c4eaca83 | ||
|
|
e64c0a35c5 | ||
|
|
6bedc4697b | ||
|
|
2dbefaf375 | ||
|
|
2050d55b21 | ||
|
|
d61178de41 | ||
|
|
9cb2d19654 | ||
|
|
8a59982420 | ||
|
|
c5aee7b2cf | ||
|
|
d0285b058b | ||
|
|
64e84554d3 | ||
|
|
8a18c2e4b6 | ||
|
|
43bb9e4302 | ||
|
|
cb8921dc22 | ||
|
|
64933ed04b | ||
|
|
040fbf9694 | ||
|
|
4500d964b3 | ||
|
|
fceab4f388 | ||
|
|
b3d468aca1 | ||
|
|
3b0b15abb9 | ||
|
|
f0cf728825 | ||
|
|
c35ab081ff | ||
|
|
1f3403717b | ||
|
|
a6f3db0a45 | ||
|
|
b051211842 | ||
|
|
279869b4c5 | ||
|
|
b69adb518a | ||
|
|
b7cdcf419f | ||
|
|
7cd4272350 | ||
|
|
992ba1a9a2 | ||
|
|
dab3ca0025 | ||
|
|
939f715e3c | ||
|
|
37afd6094e | ||
|
|
c916a00624 | ||
|
|
f6e5bee334 | ||
|
|
63b9f18d05 | ||
|
|
7b9dafcd19 | ||
|
|
53e9f44972 | ||
|
|
52fd564eac | ||
|
|
edaf11cb89 | ||
|
|
ad122c8e32 | ||
|
|
8ee6957e6a | ||
|
|
65e28d5562 | ||
|
|
422a228cea | ||
|
|
0b01b73111 | ||
|
|
555aac3f64 | ||
|
|
5b70d344cc | ||
|
|
71ce515d6d | ||
|
|
a4f9ceaf09 | ||
|
|
d4135d608e | ||
|
|
4b3f7034b2 | ||
|
|
e64c77fdde | ||
|
|
8d0fe52dd8 | ||
|
|
157d1b32dc | ||
|
|
b06cbd7a95 | ||
|
|
2cccae96fe | ||
|
|
e758678140 | ||
|
|
6c96405ab2 | ||
|
|
63aa34cb35 | ||
|
|
5a2a448c32 | ||
|
|
5e52d067c8 | ||
|
|
bf960b6eae | ||
|
|
ede5412526 | ||
|
|
78c41b84a1 | ||
|
|
99c289e063 | ||
|
|
3402031399 | ||
|
|
837c70f857 | ||
|
|
4f6095c3d4 | ||
|
|
2d4119e88d | ||
|
|
4991f2f892 | ||
|
|
10def52ad9 | ||
|
|
99c1a4448b | ||
|
|
8223161959 | ||
|
|
67ba3e55de | ||
|
|
6d7c73d498 | ||
|
|
80bb828fe5 | ||
|
|
b0dca05e50 | ||
|
|
8b4500146f | ||
|
|
ee35cbc0d0 | ||
|
|
0b6f335c9f | ||
|
|
40e8061797 | ||
|
|
bb8e6ab03c | ||
|
|
807bd4d06a | ||
|
|
e1a7fb1cc8 | ||
|
|
5032b2b872 | ||
|
|
640e9d68b7 | ||
|
|
049d0283f9 | ||
|
|
3f0c3a8b9c | ||
|
|
68fddf818c | ||
|
|
6997c442c6 | ||
|
|
78677fd8ab | ||
|
|
20a16ad5f8 | ||
|
|
19613ed833 | ||
|
|
63bc73d9d2 | ||
|
|
5c7b8955dc | ||
|
|
2c51e791ad | ||
|
|
c3fcbf77ed | ||
|
|
6321432d97 | ||
|
|
4135079290 | ||
|
|
287428b487 | ||
|
|
af23888b95 | ||
|
|
4a554431ff | ||
|
|
947f53a991 | ||
|
|
835537fcc2 | ||
|
|
08ba35763c | ||
|
|
14ad4e168e | ||
|
|
a614375982 | ||
|
|
e4bb13ea07 | ||
|
|
84add8a03d | ||
|
|
f5aa2f153a | ||
|
|
f56b0ea0d0 | ||
|
|
77b06ac9f7 | ||
|
|
3f2007cd2c | ||
|
|
6f87c565ac | ||
|
|
949b993a4a | ||
|
|
ca7e7c2304 | ||
|
|
38e52c062e | ||
|
|
8be3ac863b | ||
|
|
e1b78c4821 | ||
|
|
c1da198f5d | ||
|
|
474de014f8 | ||
|
|
c3752d1c18 | ||
|
|
f67b6f6cad | ||
|
|
9b2e291a46 | ||
|
|
867efcc6e8 | ||
|
|
847bbf5710 | ||
|
|
f013a003a0 | ||
|
|
7cdc8c4ec5 | ||
|
|
3dd1fd6b00 | ||
|
|
e13a7aed90 | ||
|
|
5b8f148c50 | ||
|
|
04c3cd6ec5 | ||
|
|
05c83a9dbb | ||
|
|
3ef0b3be95 | ||
|
|
84e7eb144b | ||
|
|
75d65b3d20 | ||
|
|
12af8506f8 | ||
|
|
0d24f2c08b | ||
|
|
dca07d240c | ||
|
|
ba011ffc5f | ||
|
|
faf12f93a4 | ||
|
|
1dc44c4047 | ||
|
|
9cd3981b94 | ||
|
|
44be6cea94 | ||
|
|
5bf7fe852c | ||
|
|
5f80e93aee | ||
|
|
80b92fb667 | ||
|
|
29d21545a6 | ||
|
|
326f8115a0 | ||
|
|
129db98a2f | ||
|
|
15fe2d5594 | ||
|
|
e3e012a9ed | ||
|
|
04648a66e6 | ||
|
|
edd0068d88 | ||
|
|
79d786708b | ||
|
|
921da2f19e | ||
|
|
0b94a0639e | ||
|
|
fb93281beb | ||
|
|
b75976b6d8 | ||
|
|
b0aea7b810 | ||
|
|
f65c3a09db | ||
|
|
d4d6d5b7c0 | ||
|
|
56ff98a2a3 | ||
|
|
6d438a9d61 | ||
|
|
e21f94f36a | ||
|
|
34543e2952 | ||
|
|
b1ffe0edd5 | ||
|
|
19ecddcd86 | ||
|
|
982c97dc6c | ||
|
|
27c602fc30 | ||
|
|
943751547e | ||
|
|
24ed5f7a1a | ||
|
|
0d654d5e53 | ||
|
|
ffa015dc21 | ||
|
|
a6eb00f326 | ||
|
|
5a12f44a25 | ||
|
|
5ffb564376 | ||
|
|
6bcf2f718c | ||
|
|
7c2be4ce3c | ||
|
|
25935bbe99 | ||
|
|
2ba52f1b94 | ||
|
|
e2e541a251 | ||
|
|
363a0e1d1e | ||
|
|
80945cd08a | ||
|
|
a17e1671f0 | ||
|
|
b619dd14c5 | ||
|
|
55cf40ec52 | ||
|
|
f3e44094a1 | ||
|
|
43094742fb | ||
|
|
70549fce14 | ||
|
|
360a5b293d | ||
|
|
f4965915ee | ||
|
|
39e48d9e8d | ||
|
|
d8ebb96077 | ||
|
|
11e5d19f32 | ||
|
|
82d0546d16 | ||
|
|
a0f17b15ec | ||
|
|
46caa2d889 | ||
|
|
475468ccc7 | ||
|
|
f3dc168dbd | ||
|
|
4ffd81bd41 | ||
|
|
0f149f1143 | ||
|
|
8513de0b47 | ||
|
|
614603089e | ||
|
|
e280e0f31c | ||
|
|
c739447598 | ||
|
|
e60f7bb125 | ||
|
|
18e36b96db | ||
|
|
2b57b6be1d | ||
|
|
45d4a2c7e2 | ||
|
|
6fccd6be84 | ||
|
|
767ac1c4e2 | ||
|
|
b14caf004d | ||
|
|
d72d25c369 | ||
|
|
640e52d4b4 | ||
|
|
9ba51c6b51 | ||
|
|
8297fcff30 | ||
|
|
aefa49a831 | ||
|
|
e251377f7c | ||
|
|
760a1245c5 | ||
|
|
4f016f149f | ||
|
|
c14b585433 | ||
|
|
2d78dae01a | ||
|
|
b0036a2d3e | ||
|
|
3091504c7e | ||
|
|
bd678659a2 | ||
|
|
06edc3dff1 | ||
|
|
c2208ce8fe | ||
|
|
1add57e5ad | ||
|
|
435c999abf | ||
|
|
c82b8244e8 | ||
|
|
8703cfc4f4 | ||
|
|
63411f9938 | ||
|
|
60f35f779d | ||
|
|
1a2ef49100 | ||
|
|
6fbdc2c221 | ||
|
|
14b5504ea7 | ||
|
|
6f4f0c4e8f | ||
|
|
ac92162825 | ||
|
|
a25558b550 | ||
|
|
e8d9e2dfa9 | ||
|
|
aa878c9dc8 | ||
|
|
7aa50d412c | ||
|
|
69949f9d53 | ||
|
|
0c8ae195ae | ||
|
|
e6682d2108 | ||
|
|
9fa6c47dc5 | ||
|
|
f5b169ab54 | ||
|
|
09b32f7f98 | ||
|
|
621c85059e | ||
|
|
b62cf7ee9a | ||
|
|
5d118e2634 | ||
|
|
807e9b5496 | ||
|
|
0c47a0a710 | ||
|
|
35eb246080 | ||
|
|
7188f88ba1 | ||
|
|
fcc8f53b59 | ||
|
|
a9da6bb2d8 | ||
|
|
f7286b525c | ||
|
|
4517734fbe | ||
|
|
bd882c98c7 | ||
|
|
ed9880f801 | ||
|
|
8ae5383698 | ||
|
|
98e7058486 | ||
|
|
0aced08204 | ||
|
|
e54b88a6cb | ||
|
|
b8a71215eb | ||
|
|
7005da3592 | ||
|
|
243e2b2a2a | ||
|
|
a85ac26367 | ||
|
|
bedfa622d7 | ||
|
|
0b86936dcd | ||
|
|
6c348620f3 | ||
|
|
ad269555d9 | ||
|
|
5db07097e8 | ||
|
|
b401f601de | ||
|
|
33a1bb8e6b | ||
|
|
4ecf4f9f3f | ||
|
|
81020e70fa | ||
|
|
74c35edbd9 | ||
|
|
64908c8e70 | ||
|
|
889be47b23 | ||
|
|
4fc232f2a6 | ||
|
|
a3f8b41e25 | ||
|
|
c664af4fe2 | ||
|
|
a00348dac1 | ||
|
|
111d9e4230 | ||
|
|
7cdc98c8fa | ||
|
|
583d85cc2e | ||
|
|
08de346e31 | ||
|
|
5242716887 | ||
|
|
57143087d7 | ||
|
|
f612d82797 | ||
|
|
c22b3f30bb | ||
|
|
147804ac30 | ||
|
|
949ef1662d | ||
|
|
efe65701e4 | ||
|
|
425c678cd9 | ||
|
|
71956cb2a2 | ||
|
|
34ccaf6e9d | ||
|
|
5c8a537efd | ||
|
|
c100e64c8e | ||
|
|
8f415bc7af | ||
|
|
44aaf4dd07 | ||
|
|
c041ea7d39 | ||
|
|
e743e6dd61 | ||
|
|
af3d6792b8 | ||
|
|
bd138db9e2 | ||
|
|
d9a8a0619c | ||
|
|
686f158117 | ||
|
|
3de0b7982e | ||
|
|
3f5e5e43b3 | ||
|
|
5c3d4a7fe4 | ||
|
|
347148e02f | ||
|
|
fde4f8d0e5 | ||
|
|
95c0846ced | ||
|
|
40e207200a | ||
|
|
acc2c11e69 | ||
|
|
df484633f7 | ||
|
|
9498f067bc | ||
|
|
625dca6435 | ||
|
|
3181102415 | ||
|
|
6e6c828c85 | ||
|
|
d8349a429a | ||
|
|
75f642bd09 | ||
|
|
858d782af1 | ||
|
|
02d9ba020d | ||
|
|
a2a34dafde | ||
|
|
ab26878033 | ||
|
|
7f677f1842 | ||
|
|
5abbdc37cb | ||
|
|
625dc1dd35 | ||
|
|
ee0d37f2b1 | ||
|
|
38a22ebd55 | ||
|
|
043e6f662f | ||
|
|
eb897d755e | ||
|
|
e7dcbb3c2d | ||
|
|
ba763ac0f0 | ||
|
|
a75818026a | ||
|
|
6bd631618c | ||
|
|
757c4d84c7 | ||
|
|
b2498fec7d | ||
|
|
0d6a7b3c52 | ||
|
|
a691112c54 | ||
|
|
3c66ca709a | ||
|
|
0080e98390 | ||
|
|
7b7ccf4128 | ||
|
|
899298442a | ||
|
|
f6d13baf4b | ||
|
|
90a2b677f1 | ||
|
|
fe72dd4140 | ||
|
|
6b63114aaa | ||
|
|
5e6f14c9ee | ||
|
|
5765e51c56 | ||
|
|
1809817b3f | ||
|
|
d8f45714a7 | ||
|
|
56b665f243 | ||
|
|
c781eb0cab | ||
|
|
28adb915f5 | ||
|
|
97c96c46f4 | ||
|
|
0bbcff973f | ||
|
|
ae7fc11472 | ||
|
|
70c2ec15d3 | ||
|
|
3f106bef72 | ||
|
|
5b085b6c15 | ||
|
|
c29f1590c9 | ||
|
|
e2fd7ce7ed | ||
|
|
1b9a86c0a0 | ||
|
|
946cd8f322 | ||
|
|
7cd0d8c079 | ||
|
|
ea02b90636 | ||
|
|
bb2ee37cc0 | ||
|
|
70771da45a | ||
|
|
49ac9079a2 | ||
|
|
04afa4a72c | ||
|
|
6b98a75658 | ||
|
|
07b9c941b4 | ||
|
|
eda3dfa50f | ||
|
|
a985452907 | ||
|
|
1339faa5f4 | ||
|
|
d95c6e821c | ||
|
|
1536530f78 | ||
|
|
d3a8b07f05 | ||
|
|
43e0cca663 | ||
|
|
7f404f64a6 | ||
|
|
3be0e9e8a5 | ||
|
|
d2a3659078 | ||
|
|
b8e261ff2f | ||
|
|
ca45e02265 | ||
|
|
3453d13188 | ||
|
|
e3aa2dc6c0 | ||
|
|
f88b6d78ff | ||
|
|
7754e486ee | ||
|
|
7e67889294 | ||
|
|
c52913716c | ||
|
|
dc8c0221e7 | ||
|
|
99b27bbe7b | ||
|
|
60b85c933e | ||
|
|
8a38ac0d24 | ||
|
|
4901de24b1 | ||
|
|
81561778a2 | ||
|
|
63b76d1b53 | ||
|
|
02b6a260af | ||
|
|
9a5d352e41 | ||
|
|
125a7b8c88 | ||
|
|
ee0d958cd1 | ||
|
|
9ae27b8e60 | ||
|
|
1b00cef30f | ||
|
|
56a9acd035 | ||
|
|
6ccd000257 | ||
|
|
fb5624880b | ||
|
|
67aa0cdede | ||
|
|
7ba6a49378 | ||
|
|
68ca09e896 | ||
|
|
145f6abc09 | ||
|
|
216029dc27 | ||
|
|
2a681d3791 | ||
|
|
63499c6334 | ||
|
|
57ce88cc9a | ||
|
|
e2284f660c | ||
|
|
3513d37702 | ||
|
|
737cdd7851 | ||
|
|
a522457bb6 | ||
|
|
c21bf31ebc | ||
|
|
a73d710bec | ||
|
|
9ed2e699c6 | ||
|
|
2f0f5f4cda | ||
|
|
8402d7d476 | ||
|
|
378c447ded | ||
|
|
df836620dc | ||
|
|
c1e1765c2f | ||
|
|
0b67b5bfae | ||
|
|
dfd5a960ed | ||
|
|
2d45e67bca | ||
|
|
7e7d94ca89 | ||
|
|
77779023d5 | ||
|
|
1fc1bdc849 | ||
|
|
16babc1975 | ||
|
|
215898c7ec | ||
|
|
fc1d8d2355 | ||
|
|
f98a74f70d | ||
|
|
89bb800dbf | ||
|
|
09cb38bc6e | ||
|
|
c40675cfba | ||
|
|
bcf4ff3b7c | ||
|
|
539bb09d50 | ||
|
|
51c09ddae9 | ||
|
|
08dbfe0cbd | ||
|
|
11a2e6e19b | ||
|
|
d3589c051d | ||
|
|
6e547469d6 | ||
|
|
cc1f10dd35 | ||
|
|
29fc2b80d9 | ||
|
|
ce254f8b46 | ||
|
|
81da717f19 | ||
|
|
16198c121c | ||
|
|
dde10249d5 | ||
|
|
58fcffa429 | ||
|
|
ac623d784a | ||
|
|
92478a219e | ||
|
|
3ab6d3f00a | ||
|
|
32aaac1dc2 | ||
|
|
ae3a1c2561 | ||
|
|
1b1868fca6 | ||
|
|
54340591e3 | ||
|
|
d438508994 | ||
|
|
cc58fe1a0f | ||
|
|
8201a57b10 | ||
|
|
2c8c6e50da | ||
|
|
10ac07bf9f | ||
|
|
8339c6843e | ||
|
|
2599cb26d8 | ||
|
|
df858e06d6 | ||
|
|
f49536ea12 | ||
|
|
aa14c9c570 | ||
|
|
1073a7124f | ||
|
|
542c5dcfc3 | ||
|
|
88e2314776 | ||
|
|
fd9758dcbf | ||
|
|
bbf5970bc1 | ||
|
|
e827e9952e | ||
|
|
dc6be0fd77 | ||
|
|
47b6f564e3 | ||
|
|
d830bca706 | ||
|
|
5f86f56bed | ||
|
|
0134f63f4d | ||
|
|
b88ebd8499 | ||
|
|
93998dc4a7 | ||
|
|
aef65d353d | ||
|
|
5a8f91aa21 | ||
|
|
cea9ceb72e | ||
|
|
f2eb42a4b2 | ||
|
|
ee3c54b572 | ||
|
|
ea72c50fb4 | ||
|
|
1628ce64db | ||
|
|
8d3ff9d2fc | ||
|
|
013ec52108 | ||
|
|
e8748200f7 | ||
|
|
3f3687f71d | ||
|
|
a9d1b771a0 | ||
|
|
8dec998d8f | ||
|
|
999af0c05b | ||
|
|
4435bdec3c | ||
|
|
e00887e4e1 | ||
|
|
75554123f6 | ||
|
|
bf5bd63635 | ||
|
|
804bff1b0e | ||
|
|
0e5f33ef93 | ||
|
|
df72d303b8 | ||
|
|
b597c000d6 | ||
|
|
6bcf194874 | ||
|
|
a1a50d6c98 | ||
|
|
e060d6eb05 | ||
|
|
a5ea98ae35 | ||
|
|
4f34029556 | ||
|
|
5f4ed5f16b | ||
|
|
b0329aad35 | ||
|
|
91fbd618ce | ||
|
|
9e34299085 | ||
|
|
0a36d6f995 | ||
|
|
678f249c63 | ||
|
|
a03e794140 | ||
|
|
8a98ad51fd | ||
|
|
0ded8f8080 | ||
|
|
36a86c2cd8 | ||
|
|
90ea611cee | ||
|
|
bd6d69c5a1 | ||
|
|
8e1dce4bbe | ||
|
|
7439f8b20a | ||
|
|
8c042a243e | ||
|
|
834de4efbe | ||
|
|
9d8c2bb4c4 | ||
|
|
f5de8be3f0 | ||
|
|
c8df1e864c | ||
|
|
2037e37d4e | ||
|
|
c1822d1cef | ||
|
|
82692b9d7f | ||
|
|
185bef39b0 | ||
|
|
5fe166dfd3 | ||
|
|
07ec2d03fb | ||
|
|
c3c5dbfa35 | ||
|
|
d1f7fb8fb8 | ||
|
|
13145557c8 | ||
|
|
c15af9f68a | ||
|
|
6083eb4ac8 | ||
|
|
07db77fb8d | ||
|
|
5f42c7c8df | ||
|
|
1969ae361c | ||
|
|
d050fe9f26 | ||
|
|
6eb8a4568b | ||
|
|
bbe068f6cb | ||
|
|
320228f9d9 | ||
|
|
13ceda3259 | ||
|
|
b02a3d0f8f | ||
|
|
c9ee902510 | ||
|
|
c1cb9ea6be | ||
|
|
6a31b1f9f3 | ||
|
|
3f328a14be | ||
|
|
709512b166 | ||
|
|
550db7e958 | ||
|
|
1fc3785f79 | ||
|
|
89042014bd | ||
|
|
4ec19b3486 | ||
|
|
7ec74749be | ||
|
|
c827d1a4ab | ||
|
|
660130653c | ||
|
|
ea04a7fec5 | ||
|
|
9d481854f3 | ||
|
|
85b9cf0a69 | ||
|
|
a1811b4b04 | ||
|
|
c224bfdc0d | ||
|
|
8ced155ca3 | ||
|
|
aa998a46d9 | ||
|
|
cae43808d9 | ||
|
|
c0d270b87d | ||
|
|
1b69ff017d | ||
|
|
b71402f64d | ||
|
|
2677e9c013 | ||
|
|
aed2b8afae | ||
|
|
7eb64ea6fc | ||
|
|
79f321cb44 | ||
|
|
6eb4fe8980 | ||
|
|
9a30d260a1 | ||
|
|
581b5cfacf | ||
|
|
c6dc0b58be | ||
|
|
6a1a6b6397 | ||
|
|
1cf4838b08 | ||
|
|
f0e69c3f64 | ||
|
|
56153aac9f | ||
|
|
ddef76e833 | ||
|
|
0522576f73 | ||
|
|
512eaf2746 | ||
|
|
714b38c97c | ||
|
|
bc676fbf78 | ||
|
|
ea0bcbd1cc | ||
|
|
24b9482cc1 | ||
|
|
9f723be65a | ||
|
|
a1e6ac85dc | ||
|
|
e22b6de4e8 | ||
|
|
a1a583ea0a | ||
|
|
86515610a5 | ||
|
|
2bac492a65 | ||
|
|
135823fced | ||
|
|
a152bf7324 | ||
|
|
3e5666ca53 | ||
|
|
7df39f3522 | ||
|
|
4c08e449e0 | ||
|
|
0dbd8264b8 | ||
|
|
6c81590b20 | ||
|
|
99033ab016 | ||
|
|
fe15748cc0 | ||
|
|
aaa3b49496 | ||
|
|
ecb7c16751 | ||
|
|
d2935672ca | ||
|
|
2583a7af31 | ||
|
|
62e7c7a318 | ||
|
|
9de8d8854d | ||
|
|
648c56bca1 | ||
|
|
0f90713aba | ||
|
|
30f14dcc4c | ||
|
|
ac0969d688 | ||
|
|
b254eb0f3d | ||
|
|
61df7974b0 | ||
|
|
515aba0ee7 | ||
|
|
af3362c62f | ||
|
|
3bc28ba6e2 | ||
|
|
4f43b157a8 | ||
|
|
f74d1c3cf8 | ||
|
|
2866342522 | ||
|
|
8e19463450 | ||
|
|
dab8ee4f94 | ||
|
|
c71af0e613 | ||
|
|
60b123c641 | ||
|
|
0260884369 | ||
|
|
ee95d2591f | ||
|
|
7735107deb | ||
|
|
058fb51f4c | ||
|
|
0e7ad43425 | ||
|
|
ea243b8558 | ||
|
|
e685731b33 | ||
|
|
889425ab0f | ||
|
|
9049ae6f20 | ||
|
|
83357af14c | ||
|
|
00356b8bbd | ||
|
|
b54a724aea | ||
|
|
a6cb20a4ec | ||
|
|
5220da1bae | ||
|
|
2f1edd4516 | ||
|
|
a7004b2db7 | ||
|
|
153bd14f9e | ||
|
|
d2611ebfb4 | ||
|
|
eadc8ad689 | ||
|
|
0203476da9 | ||
|
|
7e4c3acda3 | ||
|
|
e082497a1a | ||
|
|
9605e5ad9c | ||
|
|
da4fb27fca | ||
|
|
57d3321d0f | ||
|
|
fa8bfc6ca0 | ||
|
|
0f1ff3f2fc | ||
|
|
91f626c42a | ||
|
|
6c33d47ef3 | ||
|
|
831bed2c4d | ||
|
|
d67f696bd0 | ||
|
|
d8a4d7fd1d | ||
|
|
f5d1fe4bc4 | ||
|
|
ed72e61f8f | ||
|
|
9cd55df584 | ||
|
|
13ee62c181 | ||
|
|
addfdf50fa | ||
|
|
a17af87a5e | ||
|
|
44fc4a1d0f | ||
|
|
257ef95963 | ||
|
|
9bb41630e7 | ||
|
|
f5ab9d95fe | ||
|
|
c096934db8 | ||
|
|
1813edc74b | ||
|
|
1bc95a290f | ||
|
|
74cced319c | ||
|
|
bd30c66f95 | ||
|
|
2ab3917f29 | ||
|
|
4b4b0125a2 | ||
|
|
240c96b781 | ||
|
|
6dc189b7d1 | ||
|
|
cd1fe2d23b | ||
|
|
2af8296f48 | ||
|
|
7c085e49c7 | ||
|
|
13fbc412d2 | ||
|
|
a9cd2d41c7 | ||
|
|
7a93b9ea8c | ||
|
|
04b456d6c2 | ||
|
|
2f130b17ba | ||
|
|
9cc631ca7e | ||
|
|
ab09ce21b4 | ||
|
|
70e2769a85 | ||
|
|
bf332b6619 | ||
|
|
f83e27d647 | ||
|
|
a67cfabd1e | ||
|
|
8dbaa0c49f | ||
|
|
92d1d64dce | ||
|
|
31b3647c09 | ||
|
|
d37336456d | ||
|
|
3bacd81b8b | ||
|
|
0d24ccc47a | ||
|
|
1312c1aa72 | ||
|
|
19dd23e349 | ||
|
|
242d589c45 | ||
|
|
d149624b84 | ||
|
|
860ec642b8 | ||
|
|
248b8d1102 | ||
|
|
6f0509d235 | ||
|
|
cb8530fbae | ||
|
|
c4ab7041b8 | ||
|
|
ee29d0f9c7 | ||
|
|
1b5f2f1b39 | ||
|
|
0df3b34c52 | ||
|
|
556413c91d | ||
|
|
f3bc450e20 | ||
|
|
6556b33cef | ||
|
|
15c2800601 | ||
|
|
d660ce9c47 | ||
|
|
6770c08bf9 | ||
|
|
99ac128820 | ||
|
|
fe05382b24 | ||
|
|
3c60a515f7 | ||
|
|
b67954451a | ||
|
|
2f6f214bac | ||
|
|
413d564f1d | ||
|
|
1326bca65c | ||
|
|
721210eafe | ||
|
|
c6dacb50e8 | ||
|
|
495faea96b | ||
|
|
b6b417d42f | ||
|
|
6fb228ddd1 | ||
|
|
87b92b53a4 | ||
|
|
631297417c | ||
|
|
001efc9409 | ||
|
|
8d20487cb6 | ||
|
|
abea3a0f74 | ||
|
|
8b944e9c82 | ||
|
|
7a9e0863d6 | ||
|
|
40c728269c | ||
|
|
6907081c2b | ||
|
|
a058b1f5bd | ||
|
|
8ac2815c9e | ||
|
|
942a0c8712 | ||
|
|
cdac14b92b | ||
|
|
718bf88b9a | ||
|
|
eb4de47362 | ||
|
|
c3dfac7ade | ||
|
|
5aeae694be | ||
|
|
bdfe025059 | ||
|
|
8a2b63c944 | ||
|
|
9f96d4159a | ||
|
|
955464ee1d | ||
|
|
3ecaf76804 | ||
|
|
a36eb585d3 | ||
|
|
bf7ec4aec1 | ||
|
|
b448f2d324 | ||
|
|
b12c15ea9d | ||
|
|
eae6d33cd9 | ||
|
|
3c24a3f9c7 | ||
|
|
bb27837149 | ||
|
|
87534022b3 | ||
|
|
d2c08c72bd | ||
|
|
0261dcaa7a | ||
|
|
d14ba6b04d | ||
|
|
a69d86c587 | ||
|
|
8fb3bbe5f9 | ||
|
|
1c00a2fbec | ||
|
|
82ce4717aa | ||
|
|
2b23bde925 | ||
|
|
bc52e8b6a9 | ||
|
|
fb296d7dcb | ||
|
|
6997b973ee | ||
|
|
c968a2a902 | ||
|
|
5fcc049040 | ||
|
|
ff20c1c59d | ||
|
|
2c5f1f343f | ||
|
|
b147da388a | ||
|
|
4a60d56753 | ||
|
|
9d905d8291 | ||
|
|
07815143f1 | ||
|
|
727084c5fc | ||
|
|
b3d290edd9 | ||
|
|
9852bd08e4 | ||
|
|
3bde4ebbaf | ||
|
|
f44a2ea9a3 | ||
|
|
5eaba4f893 | ||
|
|
e82aa9977e | ||
|
|
00ece1ba55 | ||
|
|
c9022bcb73 | ||
|
|
c51327c4cc | ||
|
|
695b7865d3 | ||
|
|
fa6ff32f65 | ||
|
|
137d384304 | ||
|
|
31bd32e7ef | ||
|
|
e00efbf53d | ||
|
|
a537346580 | ||
|
|
e9cc89a336 | ||
|
|
975da89400 | ||
|
|
5ed3f55ed2 | ||
|
|
b9bfbfd5ac | ||
|
|
d42edfc0b9 | ||
|
|
58b105f0d4 | ||
|
|
a502e85e68 | ||
|
|
a59f4b2c4a | ||
|
|
f0e24f6d21 | ||
|
|
8c627aa185 | ||
|
|
e1ade59a32 | ||
|
|
290ed17c9d | ||
|
|
98b25ddd5e | ||
|
|
31267aa22d | ||
|
|
1d23c23d06 | ||
|
|
5416910249 | ||
|
|
73547c31ec | ||
|
|
2db312a792 | ||
|
|
0f01df5474 | ||
|
|
270c566570 | ||
|
|
225bcbbd22 | ||
|
|
d53601daa6 | ||
|
|
dd9d600ef9 | ||
|
|
3310f14dea | ||
|
|
a71da0a25a | ||
|
|
9b1cec7712 | ||
|
|
9916e4c4ac | ||
|
|
31cec0c17f | ||
|
|
430c7a4d7d | ||
|
|
7d83d4d47f | ||
|
|
2fa1c05ed0 | ||
|
|
f7fd3dfff8 | ||
|
|
93c45255f1 | ||
|
|
280dd8e2a5 | ||
|
|
5d83706eae | ||
|
|
4275e87c57 | ||
|
|
c5a6577cee | ||
|
|
ade5d211e9 | ||
|
|
e05b86b935 | ||
|
|
e8ee31cd6e | ||
|
|
59bed108b1 | ||
|
|
05420ab5ce | ||
|
|
32bc561878 | ||
|
|
311b6fcd83 | ||
|
|
c0da9f1eab | ||
|
|
98434ef96b | ||
|
|
9b9c116097 | ||
|
|
3c77df276a | ||
|
|
a680cae00c | ||
|
|
01d47566cc | ||
|
|
9e92340ea7 | ||
|
|
8416dc3f2d | ||
|
|
c33290c19b | ||
|
|
1a56cee9a1 | ||
|
|
7f3130c7a6 | ||
|
|
be4466115d | ||
|
|
0c42f59656 | ||
|
|
3a56532f0b | ||
|
|
442752b6ba | ||
|
|
0898655175 | ||
|
|
3bee524c6b | ||
|
|
2ed4cb8aff | ||
|
|
19760b04bd | ||
|
|
6edd5d7bfa | ||
|
|
a3ccc81892 | ||
|
|
3b5bfb4bf4 | ||
|
|
5ff9d9a1f1 | ||
|
|
f056cbba13 | ||
|
|
67598dd151 | ||
|
|
166583b1ec | ||
|
|
e1e76411f7 | ||
|
|
8ba6d13b2f | ||
|
|
3790a4a6a4 | ||
|
|
e8e0e155e5 | ||
|
|
b366db7175 | ||
|
|
3ce32fe354 | ||
|
|
2b361ad41e | ||
|
|
f9f540f47e | ||
|
|
c9b0ddf772 | ||
|
|
139af0c2bc | ||
|
|
d88198e61e | ||
|
|
df9398a871 | ||
|
|
884e6cdb51 | ||
|
|
4269fc67d5 | ||
|
|
69248132ad | ||
|
|
1dc0a603c7 | ||
|
|
091d756c14 | ||
|
|
9bcbbdd3fc | ||
|
|
a930c123ed | ||
|
|
62c083bb01 | ||
|
|
5cc1405f3f | ||
|
|
0e33d08384 | ||
|
|
dc11b82fc9 | ||
|
|
6e0917169d | ||
|
|
4cc33b2871 | ||
|
|
3a020e96fe | ||
|
|
6f3b4ecae9 | ||
|
|
d370cb48c5 | ||
|
|
9ed809943d | ||
|
|
8bce6eb3ac | ||
|
|
279e7eb1c9 | ||
|
|
defaf92752 | ||
|
|
2d08f2bbfd | ||
|
|
a13d046304 | ||
|
|
a2a668077c | ||
|
|
3a7aeb5324 | ||
|
|
9d4d4bb924 | ||
|
|
a428aaa602 | ||
|
|
2bc03b4d84 | ||
|
|
289c4ef2b7 | ||
|
|
80b6a5a27f | ||
|
|
7f2ac477a2 | ||
|
|
69b375dc48 | ||
|
|
e8bae2e50a | ||
|
|
6b3eee8481 | ||
|
|
3d73d5ef29 | ||
|
|
a0d49729f5 | ||
|
|
a573052f2e | ||
|
|
994ba35507 | ||
|
|
2d178f5033 | ||
|
|
f018fdecdd | ||
|
|
fa0adb5a1b | ||
|
|
3e0834b4ef | ||
|
|
fc5f8fcd31 | ||
|
|
2f955e01f5 | ||
|
|
c958bf9680 | ||
|
|
98b87004cc | ||
|
|
8588af1001 | ||
|
|
9eab92e90a | ||
|
|
2424ddc79a | ||
|
|
df798fb620 | ||
|
|
67face8cf0 | ||
|
|
4eefa637a3 | ||
|
|
be8e2cf3a4 | ||
|
|
f08a0b113e | ||
|
|
3a8957c6f3 | ||
|
|
99006a02ed | ||
|
|
77e1e77387 | ||
|
|
0604a58581 | ||
|
|
58e482c05a | ||
|
|
eba266aecf | ||
|
|
5bf4daddec | ||
|
|
54c08748e0 | ||
|
|
b8e343bee9 | ||
|
|
5ae4662f08 | ||
|
|
b3159d7515 | ||
|
|
a1e62158e2 | ||
|
|
777d966958 | ||
|
|
0b75991fbc | ||
|
|
e0e219793f | ||
|
|
8f412f869d | ||
|
|
6421c17515 | ||
|
|
408f30b5cd | ||
|
|
db74f155bb | ||
|
|
978de64903 | ||
|
|
e616cd1bcb | ||
|
|
2c84c43607 | ||
|
|
442d91537e | ||
|
|
7f92d64d84 | ||
|
|
c7700a8a2b | ||
|
|
e389c00a11 | ||
|
|
52b597d5d2 | ||
|
|
8a587ddeab | ||
|
|
512dee0ac0 | ||
|
|
024beacafb | ||
|
|
b4256df9c1 | ||
|
|
c860bf19ee | ||
|
|
06ea7bf923 | ||
|
|
3551eb3128 | ||
|
|
7f94d67d39 | ||
|
|
72c0c7e38b | ||
|
|
2a2785d8c9 | ||
|
|
7a3dae428a | ||
|
|
dcd3e8d444 | ||
|
|
68710e48a6 | ||
|
|
c88d1bbefa | ||
|
|
d509d3f5f9 | ||
|
|
8a4945d20a | ||
|
|
0ea6a5626f | ||
|
|
864cc4becc | ||
|
|
d2257f9784 | ||
|
|
f70457a66f | ||
|
|
278a4acf96 | ||
|
|
be3412ee74 | ||
|
|
27f9f35efb | ||
|
|
ef1aee5e95 | ||
|
|
962d6496bd | ||
|
|
91c5f2cabe | ||
|
|
a9c6430924 | ||
|
|
2ea2815529 | ||
|
|
5eb4c6a0bb | ||
|
|
4145723ef0 | ||
|
|
b8ba1b36fe | ||
|
|
f63e15f5de | ||
|
|
2f44b016b0 | ||
|
|
b7bba5d55a | ||
|
|
fba4c5049c | ||
|
|
573a6cf645 | ||
|
|
eff7e803bf | ||
|
|
58f55b808a | ||
|
|
777a927c04 | ||
|
|
0249116206 | ||
|
|
2d0e7040db | ||
|
|
edd3a2eb75 | ||
|
|
29f4f5a0cd | ||
|
|
ba7e1319ac | ||
|
|
7a1169744e | ||
|
|
453d59ae16 | ||
|
|
35e9fade06 | ||
|
|
8fc042fed1 | ||
|
|
2c5fce5e3c | ||
|
|
7a93ff3258 | ||
|
|
9291263609 | ||
|
|
f4a5878a53 | ||
|
|
1ecb3bf99a | ||
|
|
c88994a0dd | ||
|
|
29b0999da3 | ||
|
|
b08117dc93 | ||
|
|
99322cee8f | ||
|
|
e201e410f4 | ||
|
|
24f64ae1a8 | ||
|
|
1f3f489328 | ||
|
|
31056d4253 | ||
|
|
70892a6661 | ||
|
|
8e94e1d5ec | ||
|
|
2e6f444285 | ||
|
|
dcaa658678 | ||
|
|
249bc4abc8 | ||
|
|
757ca0561f | ||
|
|
001134ce59 | ||
|
|
f8ed768e39 | ||
|
|
1c6ca394c1 | ||
|
|
627fd4e68b | ||
|
|
7bfe83cce7 | ||
|
|
d169210531 | ||
|
|
902006bf53 | ||
|
|
dcb70d12e3 | ||
|
|
cd90c70cdf | ||
|
|
f1f5df3749 | ||
|
|
b2f18ad0ad | ||
|
|
af0b7621d2 | ||
|
|
2bd7869059 | ||
|
|
e1816a6da0 | ||
|
|
f3777a25e6 | ||
|
|
5e1468facb | ||
|
|
3e6e5a83f3 | ||
|
|
25c095619a | ||
|
|
407372268d | ||
|
|
aa798c252b | ||
|
|
9c658ad36b | ||
|
|
2923077b3d | ||
|
|
cbdf6c3747 | ||
|
|
9687988976 | ||
|
|
64e76e1a90 | ||
|
|
df8295fa2c | ||
|
|
0ac277a88d | ||
|
|
5a548d6acc | ||
|
|
1262a9c6c9 | ||
|
|
5ecb3eec16 | ||
|
|
2ea6bfba7b | ||
|
|
6af354ff99 | ||
|
|
0a02bd524a | ||
|
|
f9b058d36b | ||
|
|
bc51461427 | ||
|
|
fca12fd707 | ||
|
|
1f1373509e | ||
|
|
a8900d9860 | ||
|
|
6967c1fff3 | ||
|
|
2742985520 | ||
|
|
347a09e6cf | ||
|
|
e1a28078bb | ||
|
|
27562ab88f | ||
|
|
9371cecc00 | ||
|
|
feba9f2b9f | ||
|
|
6d55161043 | ||
|
|
7356f2506b | ||
|
|
ad4425d11e | ||
|
|
c8ab409d38 | ||
|
|
9a587d2aaa | ||
|
|
bcbd2418b4 | ||
|
|
8a776c7138 | ||
|
|
6810469634 | ||
|
|
5a7a09a6a7 | ||
|
|
e60e7beeef | ||
|
|
9313638997 | ||
|
|
1af2ab566e | ||
|
|
de6b8b6a74 | ||
|
|
62207313a0 | ||
|
|
b3b10729cd | ||
|
|
91f99eeb4c | ||
|
|
a1b3450b47 | ||
|
|
da4bf7f191 | ||
|
|
a893cf9cb6 | ||
|
|
9fa5dcc055 | ||
|
|
fe146cb77a | ||
|
|
fc49d6943a | ||
|
|
44b2dda585 | ||
|
|
3980e4fa90 | ||
|
|
ca2bef3cd1 | ||
|
|
cbd82c7204 | ||
|
|
82115c6bf7 | ||
|
|
2a26ddc622 | ||
|
|
6535411744 | ||
|
|
7c02b4d264 | ||
|
|
7e79e69eae | ||
|
|
dacacdf130 | ||
|
|
8fede9d6ba | ||
|
|
d10c592987 | ||
|
|
acccb01c76 | ||
|
|
6876fe45e1 | ||
|
|
52a4b5acd7 | ||
|
|
92b6401360 | ||
|
|
ea24a15011 | ||
|
|
243dc965c0 | ||
|
|
f1f9098109 | ||
|
|
79b3f9bf06 | ||
|
|
dff245a9ce | ||
|
|
91f9fa0f1f | ||
|
|
6f919fc232 | ||
|
|
ce277481c3 | ||
|
|
d2039a14e1 | ||
|
|
a27ef16911 | ||
|
|
dc3dc7df73 | ||
|
|
872adbec0a | ||
|
|
c64cfea179 | ||
|
|
84dff779ac | ||
|
|
0d4b81fe6f | ||
|
|
0735345674 | ||
|
|
bb15bd20c0 | ||
|
|
95f5d162ef | ||
|
|
7967a462a1 | ||
|
|
e225785744 | ||
|
|
46dcdfa58e | ||
|
|
f8debe340f | ||
|
|
83f99727a7 | ||
|
|
cbf84f62d4 | ||
|
|
fa68954dda | ||
|
|
98caae106f | ||
|
|
3bc42543fa | ||
|
|
57d955ec72 | ||
|
|
96e333a30e | ||
|
|
560f1a6466 | ||
|
|
4042d5b179 | ||
|
|
9d62ce214c | ||
|
|
f65de2dd43 | ||
|
|
7b58f03f1c | ||
|
|
52577c1de9 | ||
|
|
fc3f200357 | ||
|
|
d89f14dcbc | ||
|
|
96b8273916 | ||
|
|
eda79d8626 | ||
|
|
0c50057220 | ||
|
|
10676be377 | ||
|
|
1385aca783 | ||
|
|
121959efe4 | ||
|
|
8d2ec78713 | ||
|
|
01371f2c65 | ||
|
|
b344bba59a | ||
|
|
a6d393f19b | ||
|
|
7e19d6a205 | ||
|
|
92a9f1e234 | ||
|
|
c39f7e521a | ||
|
|
c9b9efe745 | ||
|
|
a5a371f1ff | ||
|
|
b491e892ff | ||
|
|
771932354b | ||
|
|
89f270b67c | ||
|
|
3276373745 | ||
|
|
5667081764 | ||
|
|
b23d533006 | ||
|
|
1f01d0b6b1 | ||
|
|
f1a9a5180d | ||
|
|
bb8a634ba8 | ||
|
|
f9294f0e9e | ||
|
|
9770967b04 | ||
|
|
0bbb32e8ac | ||
|
|
61d64287e1 | ||
|
|
abee274f88 | ||
|
|
53e6d974f0 | ||
|
|
e3fd54e147 | ||
|
|
52d39db84a | ||
|
|
27ddae3df9 | ||
|
|
4c7e3d8f3a | ||
|
|
646495fc5f | ||
|
|
a9661a233a | ||
|
|
2d05e10819 | ||
|
|
5a0bcc01a6 | ||
|
|
0d3c624bbc | ||
|
|
26fbcf6076 | ||
|
|
418fca3d9e | ||
|
|
dd366f8cf9 | ||
|
|
6b63e88056 | ||
|
|
a96e2fb588 | ||
|
|
ca2f966c3b | ||
|
|
e161d9daa7 | ||
|
|
aa834db1e3 | ||
|
|
a1df91b665 | ||
|
|
bc286b78bf | ||
|
|
19c7e14393 | ||
|
|
1a9dfc0893 | ||
|
|
fe129956bb | ||
|
|
eec7de4646 | ||
|
|
605181efe4 | ||
|
|
fc0ed75a94 | ||
|
|
d95c4146e2 | ||
|
|
38f0d50648 | ||
|
|
808d8e63bc | ||
|
|
55967035d9 | ||
|
|
13a101f11f | ||
|
|
35a0a3cf90 | ||
|
|
08d8f5d8d9 | ||
|
|
f7c9eccf7a | ||
|
|
6ba9e64380 | ||
|
|
8b0a3ea680 | ||
|
|
e12ff2c59d | ||
|
|
afbdb395b2 | ||
|
|
3d381e6e32 | ||
|
|
a02737107e | ||
|
|
590ab88c45 | ||
|
|
7a7c07e9c4 | ||
|
|
40aafe586d | ||
|
|
6b93f955a4 | ||
|
|
75a3bb4f0b | ||
|
|
441e18b898 | ||
|
|
b8e64df4b1 | ||
|
|
d15e7f76fc | ||
|
|
3f510b6d93 | ||
|
|
d60c05eff3 | ||
|
|
7c0e4b25ae | ||
|
|
852241d98e | ||
|
|
8deba81214 | ||
|
|
887a093f46 | ||
|
|
4778dba36d | ||
|
|
438b2240f4 | ||
|
|
58a6333834 | ||
|
|
f8feec685f | ||
|
|
79404cd397 | ||
|
|
6a6c5848c2 | ||
|
|
420c8ebc4c | ||
|
|
c0ea95ca46 | ||
|
|
1a8d971145 | ||
|
|
7afcb9d757 | ||
|
|
8a5c0736f5 | ||
|
|
12b6bb9448 | ||
|
|
e2a6b55d44 | ||
|
|
c240c0a24b | ||
|
|
2528b79610 | ||
|
|
e13fd4816e | ||
|
|
1f6e0f582a | ||
|
|
bbbed49f82 | ||
|
|
cefb2e7cc6 | ||
|
|
470bc4e092 | ||
|
|
08c7c80bb7 | ||
|
|
5832ec76d4 | ||
|
|
594e5b80d7 | ||
|
|
14a434975a | ||
|
|
6854b23bcc | ||
|
|
0c52d275fa | ||
|
|
e63b9b4986 | ||
|
|
0e270bec56 | ||
|
|
f5f06b86ad | ||
|
|
fcc3008b00 | ||
|
|
0bdd46451e | ||
|
|
c35e9fb016 | ||
|
|
80e92849da | ||
|
|
dcd8eccee4 | ||
|
|
5adcbe4c78 | ||
|
|
0b93556c06 | ||
|
|
7e9d291223 | ||
|
|
1dc26a9b8e | ||
|
|
3dbc6400a6 | ||
|
|
adf1aeb06a | ||
|
|
be224934a5 | ||
|
|
6b74093c04 | ||
|
|
65c796dec7 | ||
|
|
503e706d45 | ||
|
|
a1f974bd40 | ||
|
|
11171ff649 | ||
|
|
3ff1888d74 | ||
|
|
cbea08e1fe | ||
|
|
b622afd7fd | ||
|
|
7694d0842b | ||
|
|
a90e6940ab | ||
|
|
96c3825b6a | ||
|
|
a6467cb515 | ||
|
|
d52ba83f96 | ||
|
|
2473b8763b | ||
|
|
7073279ab8 | ||
|
|
ed43071792 | ||
|
|
4a1e4f3e16 | ||
|
|
555c43843b | ||
|
|
a0d75d1e63 | ||
|
|
1a7a47fa08 | ||
|
|
c2279d3071 | ||
|
|
ed295ae315 | ||
|
|
53b781960c | ||
|
|
6b794ba3e5 | ||
|
|
2f95e56256 | ||
|
|
c8644adc85 | ||
|
|
8b81078929 | ||
|
|
76a10283c4 | ||
|
|
0975102e92 | ||
|
|
445d943549 | ||
|
|
554486fb0b | ||
|
|
1c1af89bb9 | ||
|
|
536c130a39 | ||
|
|
5aa67a42c1 | ||
|
|
f661d1ba48 | ||
|
|
717b9ba1e6 | ||
|
|
c57cc96145 | ||
|
|
16fa1ef144 | ||
|
|
788f0f3e24 | ||
|
|
ccf9c8fb22 | ||
|
|
ee31146501 | ||
|
|
f176a0ed83 | ||
|
|
e7cfd2765c | ||
|
|
96c4554644 | ||
|
|
df94f0ec8b | ||
|
|
9ee7294c81 | ||
|
|
5cf622fb6e | ||
|
|
8711d8799c | ||
|
|
e9a6a3afc5 | ||
|
|
9c29264be7 | ||
|
|
e6c9d5fc96 | ||
|
|
d09476c603 | ||
|
|
5bda6852a4 | ||
|
|
641b05eb21 | ||
|
|
62fd11d5c4 | ||
|
|
e807664437 | ||
|
|
e46440b94f | ||
|
|
30ea1e23b4 | ||
|
|
4db2fd980a | ||
|
|
9580d61fb0 | ||
|
|
27aefcc0ce | ||
|
|
b8b1a4930c | ||
|
|
d7a40f3bdb | ||
|
|
fb0031d34a | ||
|
|
b3821e71dc | ||
|
|
8a90413b87 | ||
|
|
115984054d | ||
|
|
69addda86c | ||
|
|
c2379f251d | ||
|
|
3422bf1b59 | ||
|
|
e830d1e9c2 | ||
|
|
f767c24601 | ||
|
|
2c09b1414c | ||
|
|
df1191db5b | ||
|
|
62433ecb8b | ||
|
|
09f680201f | ||
|
|
5ba662cfae | ||
|
|
d6d0fd597d | ||
|
|
fec1c813a5 | ||
|
|
547b6d19dc | ||
|
|
5e6dd50c3b | ||
|
|
4251ed69bb | ||
|
|
b05ee80d5c | ||
|
|
84ced8704d | ||
|
|
58313520f0 | ||
|
|
0d8ef1a1dd | ||
|
|
28ddab7cc2 | ||
|
|
8f06b0a836 | ||
|
|
3b768dacf5 | ||
|
|
d67b3245e0 | ||
|
|
a1dd1fc5d1 | ||
|
|
889153ce81 | ||
|
|
30e5c807b0 | ||
|
|
db2fded24d | ||
|
|
82a9809192 | ||
|
|
f6b40b2bce | ||
|
|
fd75e03d9c | ||
|
|
d6a05f2ea2 | ||
|
|
f51603e440 | ||
|
|
f341c9a8a6 | ||
|
|
765944cfa2 | ||
|
|
15c926b6b9 | ||
|
|
b486112c98 | ||
|
|
223a0015a6 | ||
|
|
632af7c7e6 | ||
|
|
142870d78a | ||
|
|
b0aa32cd1b | ||
|
|
281229e8b4 | ||
|
|
8f0d53ffd9 | ||
|
|
ddfa5a4d35 | ||
|
|
cbf2e2e2ef | ||
|
|
62803aff88 | ||
|
|
bc9cd57fa0 | ||
|
|
93fa9bdfb8 | ||
|
|
808c5f0951 | ||
|
|
a70637f28f | ||
|
|
a12e4657ee | ||
|
|
53db1230ab | ||
|
|
207e09fea9 | ||
|
|
b86b638700 | ||
|
|
995c33a942 | ||
|
|
6d6efd5fe8 | ||
|
|
2782620081 | ||
|
|
be2ca77acf | ||
|
|
87790069e9 | ||
|
|
0a374e2264 | ||
|
|
2eee911c58 | ||
|
|
bb65646c4d | ||
|
|
fea700fb72 | ||
|
|
560c3230cd | ||
|
|
e990a83b7a | ||
|
|
02a2624bcc | ||
|
|
c7c78eda80 | ||
|
|
b1200b8078 | ||
|
|
0cb25f1044 | ||
|
|
8ee728891d | ||
|
|
bbaa475287 | ||
|
|
7bc17b59f5 | ||
|
|
514652bb6a | ||
|
|
3e7665146a | ||
|
|
16d0f8a5a6 | ||
|
|
aeab9a8116 | ||
|
|
f9a5669eb4 | ||
|
|
868736fe1a | ||
|
|
0984de6bea | ||
|
|
69ffd70d3f | ||
|
|
2fae69c0ba | ||
|
|
f1d66a4c70 | ||
|
|
84ce33fe9c | ||
|
|
e583165dff | ||
|
|
7bedba5837 | ||
|
|
8c9b9df125 | ||
|
|
96641873ae | ||
|
|
8edd9de278 | ||
|
|
67754e8693 | ||
|
|
b88495c689 | ||
|
|
71b13c7b5d | ||
|
|
b3f39bffce | ||
|
|
b44a9ab5fc | ||
|
|
e34255b0b2 | ||
|
|
2fee62af4d | ||
|
|
fc1032cd9e | ||
|
|
60195e2842 | ||
|
|
6fed31717c | ||
|
|
aee3eb99c3 | ||
|
|
3aa6fd3dc4 | ||
|
|
5e74e58b22 | ||
|
|
fef7a018f2 | ||
|
|
1d4891b017 | ||
|
|
e9a803e3c1 | ||
|
|
4abe7d5895 | ||
|
|
cd68db5c11 | ||
|
|
ae9f437bb8 | ||
|
|
a047893049 | ||
|
|
5b2733decf | ||
|
|
ff60540fac | ||
|
|
a68a91bb39 | ||
|
|
0bf8d2241f | ||
|
|
06850e6271 | ||
|
|
62fa3b7c9c | ||
|
|
ce09b402d0 | ||
|
|
6e7ad9df25 | ||
|
|
fb20479379 | ||
|
|
9d181e88d2 | ||
|
|
e90fc1ef39 | ||
|
|
ce013f17d6 | ||
|
|
78253ce284 | ||
|
|
2671e40c1d | ||
|
|
04cda69ef9 | ||
|
|
b4c483ce1a | ||
|
|
9a9f58d744 | ||
|
|
d38fe542a2 | ||
|
|
80503fbf36 | ||
|
|
99a23b4056 | ||
|
|
13a7de4b6b | ||
|
|
73bba97aaa | ||
|
|
d6e9cdab5b | ||
|
|
1a177bc2de | ||
|
|
e0b3e631fe | ||
|
|
2518a353af | ||
|
|
989800efff | ||
|
|
c02846e2cb | ||
|
|
09db4a0e25 | ||
|
|
a85da9d86c | ||
|
|
920d00bbae | ||
|
|
62475279ee | ||
|
|
d8e979d283 | ||
|
|
299b8880dd | ||
|
|
61027e4067 | ||
|
|
a7249c10dc |
940
.editorconfig
940
.editorconfig
@@ -8,99 +8,915 @@ end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
; 4-column tab indentation
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
; 4-column tab indentation and .NET coding conventions
|
||||
[*.cs]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = true
|
||||
#### Code Style Rules
|
||||
#### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/
|
||||
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
# Severity Levels: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options#severity-level
|
||||
# Below we enable specific rules by setting severity to warning.
|
||||
# Rules are disabled by setting severity to silent (to still allow use in IDE) or none (to prevent all use).
|
||||
# Rules are listed below with any options available.
|
||||
# Options are commented out if they match the defaults.
|
||||
|
||||
csharp_prefer_braces = when_multiline:suggestion
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
### Language Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/language-rules
|
||||
|
||||
#### Naming styles ####
|
||||
## this and Me preferences
|
||||
|
||||
# IDE0003/IDE0009 Remove 'this' or 'Me' qualification/Add 'this' or 'Me' qualification
|
||||
#dotnet_style_qualification_for_field = false
|
||||
#dotnet_style_qualification_for_property = false
|
||||
#dotnet_style_qualification_for_method = false
|
||||
#dotnet_style_qualification_for_event = false
|
||||
dotnet_diagnostic.IDE0003.severity = warning
|
||||
dotnet_diagnostic.IDE0009.severity = warning
|
||||
|
||||
## Use languages keywords for types
|
||||
|
||||
# IDE0049 Use language keywords instead of framework type names for type references
|
||||
#dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
#dotnet_style_predefined_type_for_member_access = true
|
||||
dotnet_diagnostic.IDE0049.severity = warning
|
||||
|
||||
## Modifier preferences
|
||||
|
||||
# IDE0036 Order modifiers
|
||||
#csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
|
||||
# IDE0040 Add accessibility modifiers
|
||||
dotnet_style_require_accessibility_modifiers = omit_if_default
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0044 Add readonly modifier
|
||||
#dotnet_style_readonly_field = true
|
||||
dotnet_diagnostic.IDE0044.severity = warning
|
||||
|
||||
# IDE0062 Make local function static
|
||||
#csharp_prefer_static_local_function = true
|
||||
dotnet_diagnostic.IDE0062.severity = warning
|
||||
|
||||
# IDE0064 Make struct fields writable
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0064.severity = warning
|
||||
|
||||
## Parentheses preferences
|
||||
|
||||
# IDE0047/IDE0048 Remove unnecessary parentheses/Add parentheses for clarity
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary
|
||||
#dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||
#dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||
dotnet_diagnostic.IDE0047.severity = warning
|
||||
dotnet_diagnostic.IDE0048.severity = warning
|
||||
|
||||
## Expression-level preferences
|
||||
|
||||
# IDE0010 Add missing cases to switch statement
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0010.severity = silent
|
||||
|
||||
# IDE0017 Use object initializers
|
||||
#dotnet_style_object_initializer = true
|
||||
dotnet_diagnostic.IDE0017.severity = warning
|
||||
|
||||
# IDE0018 Inline variable declaration
|
||||
#csharp_style_inlined_variable_declaration = true
|
||||
dotnet_diagnostic.IDE0018.severity = warning
|
||||
|
||||
# IDE0028 Use collection initializers
|
||||
#dotnet_style_collection_initializer = true
|
||||
dotnet_diagnostic.IDE0028.severity = warning
|
||||
|
||||
# IDE0032 Use auto-implemented property
|
||||
#dotnet_style_prefer_auto_properties = true
|
||||
dotnet_diagnostic.IDE0032.severity = warning
|
||||
|
||||
# IDE0033 Use explicitly provided tuple name
|
||||
#dotnet_style_explicit_tuple_names = true
|
||||
dotnet_diagnostic.IDE0033.severity = warning
|
||||
|
||||
# IDE0034 Simplify 'default' expression
|
||||
#csharp_prefer_simple_default_expression = true
|
||||
dotnet_diagnostic.IDE0034.severity = warning
|
||||
|
||||
# IDE0037 Use inferred member name
|
||||
#dotnet_style_prefer_inferred_tuple_names = true
|
||||
#dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||
dotnet_diagnostic.IDE0037.severity = silent
|
||||
|
||||
# IDE0039 Use local function instead of lambda
|
||||
#csharp_style_prefer_local_over_anonymous_function = true
|
||||
dotnet_diagnostic.IDE0039.severity = warning
|
||||
|
||||
# IDE0042 Deconstruct variable declaration
|
||||
#csharp_style_deconstructed_variable_declaration = true
|
||||
dotnet_diagnostic.IDE0042.severity = warning
|
||||
|
||||
# IDE0045 Use conditional expression for assignment
|
||||
#dotnet_style_prefer_conditional_expression_over_assignment = true
|
||||
dotnet_diagnostic.IDE0045.severity = silent
|
||||
|
||||
# IDE0046 Use conditional expression for return
|
||||
#dotnet_style_prefer_conditional_expression_over_return = true
|
||||
dotnet_diagnostic.IDE0046.severity = silent
|
||||
|
||||
# IDE0050 Convert anonymous type to tuple
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0050.severity = silent
|
||||
|
||||
# IDE0054/IDE0074 Use compound assignment/Use coalesce compound assignment
|
||||
#dotnet_style_prefer_compound_assignment = true
|
||||
dotnet_diagnostic.IDE0054.severity = warning
|
||||
dotnet_diagnostic.IDE0074.severity = warning
|
||||
|
||||
# IDE0056 Use index operator
|
||||
#csharp_style_prefer_index_operator = true
|
||||
dotnet_diagnostic.IDE0056.severity = warning
|
||||
|
||||
# IDE0057 Use range operator
|
||||
#csharp_style_prefer_range_operator = true
|
||||
dotnet_diagnostic.IDE0057.severity = warning
|
||||
|
||||
# IDE0070 Use 'System.HashCode.Combine'
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0070.severity = warning
|
||||
|
||||
# IDE0071 Simplify interpolation
|
||||
#dotnet_style_prefer_simplified_interpolation = true
|
||||
dotnet_diagnostic.IDE0071.severity = warning
|
||||
|
||||
# IDE0072 Add missing cases to switch expression
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0072.severity = silent
|
||||
|
||||
# IDE0075 Simplify conditional expression
|
||||
#dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
dotnet_diagnostic.IDE0075.severity = warning
|
||||
|
||||
# IDE0082 Convert 'typeof' to 'nameof'
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0082.severity = warning
|
||||
|
||||
# IDE0090 Simplify 'new' expression
|
||||
#csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||
dotnet_diagnostic.IDE0090.severity = warning
|
||||
|
||||
# IDE0180 Use tuple to swap values
|
||||
#csharp_style_prefer_tuple_swap = true
|
||||
dotnet_diagnostic.IDE0180.severity = warning
|
||||
|
||||
## Namespace declaration preferences
|
||||
|
||||
# IDE0160/IDE0161 Use block-scoped namespace/Use file-scoped namespace
|
||||
#csharp_style_namespace_declarations = block_scoped
|
||||
dotnet_diagnostic.IDE0160.severity = warning
|
||||
dotnet_diagnostic.IDE0161.severity = warning
|
||||
|
||||
## Null-checking preferences
|
||||
|
||||
# IDE0016 Use throw expression
|
||||
#csharp_style_throw_expression = true
|
||||
dotnet_diagnostic.IDE0016.severity = silent
|
||||
|
||||
# IDE0029/IDE0030/IDE0270 Use coalesce expression (non-nullable types)/Use coalesce expression (nullable types)/Use coalesce expression (if null)
|
||||
#dotnet_style_coalesce_expression = true
|
||||
dotnet_diagnostic.IDE0029.severity = warning
|
||||
dotnet_diagnostic.IDE0030.severity = warning
|
||||
dotnet_diagnostic.IDE0270.severity = silent
|
||||
|
||||
# IDE0031 Use null propagation
|
||||
#dotnet_style_null_propagation = true
|
||||
dotnet_diagnostic.IDE0031.severity = warning
|
||||
|
||||
# IDE0041 Use 'is null' check
|
||||
#dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||
dotnet_diagnostic.IDE0041.severity = warning
|
||||
|
||||
# IDE0150 Prefer 'null' check over type check
|
||||
#csharp_style_prefer_null_check_over_type_check = true
|
||||
dotnet_diagnostic.IDE0150.severity = warning
|
||||
|
||||
# IDE1005 Use conditional delegate call
|
||||
csharp_style_conditional_delegate_call = true # true is the default, but the rule is not triggered if this is not specified.
|
||||
dotnet_diagnostic.IDE1005.severity = warning
|
||||
|
||||
## var preferences
|
||||
|
||||
# IDE0007/IDE0008 Use 'var' instead of explicit type/Use explicit type instead of 'var'
|
||||
csharp_style_var_for_built_in_types = true
|
||||
csharp_style_var_when_type_is_apparent = true
|
||||
csharp_style_var_elsewhere = true
|
||||
dotnet_diagnostic.IDE0007.severity = warning
|
||||
dotnet_diagnostic.IDE0008.severity = warning
|
||||
|
||||
## Expression-bodied-members
|
||||
|
||||
# IDE0021 Use expression body for constructors
|
||||
#csharp_style_expression_bodied_constructors = false
|
||||
dotnet_diagnostic.IDE0021.severity = silent
|
||||
|
||||
# IDE0022 Use expression body for methods
|
||||
#csharp_style_expression_bodied_methods = false
|
||||
dotnet_diagnostic.IDE0022.severity = silent
|
||||
|
||||
# IDE0023/IDE0024 Use expression body for conversion operators/Use expression body for operators
|
||||
#csharp_style_expression_bodied_operators = false
|
||||
dotnet_diagnostic.IDE0023.severity = silent
|
||||
dotnet_diagnostic.IDE0024.severity = silent
|
||||
|
||||
# IDE0025 Use expression body for properties
|
||||
#csharp_style_expression_bodied_properties = true
|
||||
dotnet_diagnostic.IDE0025.severity = silent
|
||||
|
||||
# IDE0026 Use expression body for indexers
|
||||
#csharp_style_expression_bodied_indexers = true
|
||||
dotnet_diagnostic.IDE0026.severity = silent
|
||||
|
||||
# IDE0027 Use expression body for accessors
|
||||
#csharp_style_expression_bodied_accessors = true
|
||||
dotnet_diagnostic.IDE0027.severity = warning
|
||||
|
||||
# IDE0053 Use expression body for lambdas
|
||||
# This rule is buggy and not enforced for builds. ':warning' will at least enforce it in the IDE.
|
||||
csharp_style_expression_bodied_lambdas = when_on_single_line:warning
|
||||
dotnet_diagnostic.IDE0053.severity = warning
|
||||
|
||||
# IDE0061 Use expression body for local functions
|
||||
csharp_style_expression_bodied_local_functions = when_on_single_line
|
||||
dotnet_diagnostic.IDE0061.severity = warning
|
||||
|
||||
## Pattern matching preferences
|
||||
|
||||
# IDE0019 Use pattern matching to avoid 'as' followed by a 'null' check
|
||||
#csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
dotnet_diagnostic.IDE0019.severity = warning
|
||||
|
||||
# IDE0020/IDE0038 Use pattern matching to avoid 'is' check followed by a cast (with variable)/Use pattern matching to avoid 'is' check followed by a cast (without variable)
|
||||
#csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
dotnet_diagnostic.IDE0020.severity = warning
|
||||
dotnet_diagnostic.IDE0038.severity = warning
|
||||
|
||||
# IDE0066 Use switch expression
|
||||
#csharp_style_prefer_switch_expression = true
|
||||
dotnet_diagnostic.IDE0066.severity = silent
|
||||
|
||||
# IDE0078 Use pattern matching
|
||||
#csharp_style_prefer_pattern_matching = true
|
||||
dotnet_diagnostic.IDE0078.severity = silent
|
||||
|
||||
# IDE0083 Use pattern matching ('not' operator)
|
||||
#csharp_style_prefer_not_pattern = true
|
||||
dotnet_diagnostic.IDE0083.severity = warning
|
||||
|
||||
# IDE0170 Simplify property pattern
|
||||
#csharp_style_prefer_extended_property_pattern = true
|
||||
dotnet_diagnostic.IDE0170.severity = silent # Requires C# 10
|
||||
|
||||
## Code block preferences
|
||||
|
||||
# IDE0011 Add braces
|
||||
#csharp_prefer_braces = true
|
||||
# No options match the style used in OpenRA.
|
||||
dotnet_diagnostic.IDE0011.severity = none
|
||||
|
||||
# IDE0063 Use simple 'using' statement
|
||||
#csharp_prefer_simple_using_statement = true
|
||||
dotnet_diagnostic.IDE0063.severity = silent
|
||||
|
||||
## 'using' directive preferences
|
||||
|
||||
# IDE0065 'using' directive placement
|
||||
#csharp_using_directive_placement = outside_namespace
|
||||
dotnet_diagnostic.IDE0065.severity = silent
|
||||
|
||||
## File header preferences
|
||||
|
||||
# IDE0073 Require file header
|
||||
#file_header_template = unset
|
||||
# This rule does not allow us to enforce our desired header, as it prefixes the header lines with // comments, meaning we can't apply a region.
|
||||
dotnet_diagnostic.IDE0073.severity = none
|
||||
|
||||
## Namespace naming preferences
|
||||
|
||||
# IDE0130 Namespace does not match folder structure
|
||||
#dotnet_style_namespace_match_folder = true
|
||||
# This rule doesn't appear to work (never reports violations)
|
||||
dotnet_diagnostic.IDE0130.severity = none
|
||||
|
||||
### Unnecessary Code Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules
|
||||
|
||||
# IDE0001 Simplify name
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0001.severity = warning
|
||||
|
||||
# IDE0002 Simplify member access
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0002.severity = warning
|
||||
|
||||
# IDE0004 Remove unnecessary cast
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0004.severity = warning
|
||||
|
||||
# IDE0005 Remove unnecessary import
|
||||
# No options
|
||||
# IDE0005 is only enabled in the IDE by default. https://github.com/dotnet/roslyn/issues/41640
|
||||
# To enable it for builds outside the IDE the 'GenerateDocumentationFile' property must be enabled on the build.
|
||||
# GenerateDocumentationFile generates additional warnings about XML docs, so disable any we don't care about.
|
||||
dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
|
||||
# IDE0035 Remove unreachable code
|
||||
# No options
|
||||
# Duplicates compiler warning CS0162
|
||||
dotnet_diagnostic.IDE0035.severity = none
|
||||
|
||||
# IDE0051 Remove unused private member
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0051.severity = warning
|
||||
|
||||
# IDE0052 Remove unread private member
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0052.severity = warning
|
||||
|
||||
# IDE0058 Remove unnecessary expression value
|
||||
#csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
dotnet_diagnostic.IDE0058.severity = silent
|
||||
|
||||
# IDE0059 Remove unnecessary value assignment
|
||||
#csharp_style_unused_value_assignment_preference = discard_variable
|
||||
dotnet_diagnostic.IDE0059.severity = warning
|
||||
|
||||
# IDE0060 Remove unused parameter
|
||||
dotnet_code_quality_unused_parameters = non_public
|
||||
dotnet_diagnostic.IDE0060.severity = warning
|
||||
|
||||
# IDE0079 Remove unnecessary suppression
|
||||
#dotnet_remove_unnecessary_suppression_exclusions = none
|
||||
dotnet_diagnostic.IDE0079.severity = warning
|
||||
|
||||
# IDE0080 Remove unnecessary suppression operator
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0080.severity = warning
|
||||
|
||||
# IDE0100 Remove unnecessary equality operator
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0100.severity = warning
|
||||
|
||||
# IDE0110 Remove unnecessary discard
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0110.severity = warning
|
||||
|
||||
### Miscellaneous Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/miscellaneous-rules
|
||||
|
||||
# IDE0076 Remove invalid global 'SuppressMessageAttribute'
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0076.severity = warning
|
||||
|
||||
# IDE0077 Avoid legacy format target in global 'SuppressMessageAttribute'
|
||||
# No options
|
||||
dotnet_diagnostic.IDE0077.severity = warning
|
||||
|
||||
### Formatting Rules (IDE0055)
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0055
|
||||
|
||||
# We may eventually wish to enforce this rule, however some existing formatting conflicts with the rule despite being reasonable.
|
||||
# Additionally, the rule is buggy and likes to report spuriously after invoking Format Document in the IDE.
|
||||
dotnet_diagnostic.IDE0055.severity = none
|
||||
|
||||
#dotnet_sort_system_directives_first = true
|
||||
#dotnet_separate_import_directive_groups = false
|
||||
#dotnet_style_namespace_match_folder = true
|
||||
|
||||
#csharp_new_line_before_open_brace = all
|
||||
#csharp_new_line_before_else = true
|
||||
#csharp_new_line_before_catch = true
|
||||
#csharp_new_line_before_finally = true
|
||||
#csharp_new_line_before_members_in_object_initializers = true
|
||||
#csharp_new_line_before_members_in_anonymous_types = true
|
||||
#csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
#csharp_indent_case_contents = true
|
||||
#csharp_indent_switch_labels = true
|
||||
#csharp_indent_labels = one_less_than_current
|
||||
#csharp_indent_block_contents = true
|
||||
#csharp_indent_braces = false
|
||||
#csharp_indent_case_contents_when_block = true
|
||||
|
||||
#csharp_space_after_cast = false
|
||||
#csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#csharp_space_between_parentheses =
|
||||
#csharp_space_before_colon_in_inheritance_clause = true
|
||||
#csharp_space_after_colon_in_inheritance_clause = true
|
||||
#csharp_space_around_binary_operators = before_and_after
|
||||
#csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
#csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
#csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
#csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
#csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
#csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
#csharp_space_after_comma = true
|
||||
#csharp_space_before_comma = false
|
||||
#csharp_space_after_dot = false
|
||||
#csharp_space_before_dot = false
|
||||
#csharp_space_after_semicolon_in_for_statement = true
|
||||
#csharp_space_before_semicolon_in_for_statement = false
|
||||
#csharp_space_around_declaration_statements = false
|
||||
#csharp_space_before_open_square_brackets = false
|
||||
#csharp_space_between_empty_square_brackets = false
|
||||
#csharp_space_between_square_brackets = false
|
||||
|
||||
#csharp_preserve_single_line_statements = true
|
||||
#csharp_preserve_single_line_blocks = true
|
||||
|
||||
|
||||
### Naming Rules (IDE1006)
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/naming-rules
|
||||
dotnet_diagnostic.IDE1006.severity = warning
|
||||
|
||||
## Naming styles
|
||||
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
dotnet_naming_style.i_prefix_pascal_case.capitalization = pascal_case
|
||||
dotnet_naming_style.i_prefix_pascal_case.required_prefix = I
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
## Naming Symbols
|
||||
|
||||
dotnet_naming_symbols.const_private_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.const_private_field.required_modifiers = const
|
||||
dotnet_naming_symbols.const_private_field.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.const_locals.applicable_kinds = local
|
||||
dotnet_naming_symbols.const_locals.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.const_locals.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.internal_field.applicable_accessibilities = internal
|
||||
dotnet_naming_symbols.const_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.const_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.const_fields.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.static_private_or_internal_field.required_modifiers = static
|
||||
dotnet_naming_symbols.static_private_or_internal_field.applicable_accessibilities = internal, private
|
||||
dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
|
||||
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
dotnet_naming_symbols.private_or_protected_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_protected_fields.applicable_accessibilities = private, protected, private_protected
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interfaces.applicable_accessibilities = *
|
||||
|
||||
# Naming rules
|
||||
dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local
|
||||
dotnet_naming_symbols.parameters_and_locals.applicable_accessibilities = *
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_symbols.most_symbols.applicable_kinds = namespace, class, struct, enum, field, property, method, local_function, event, delegate, type_parameter
|
||||
dotnet_naming_symbols.most_symbols.applicable_accessibilities = *
|
||||
|
||||
## Naming Rules
|
||||
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.symbols = const_locals
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.symbols = const_fields
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols = static_readonly_fields
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.symbols = private_or_protected_fields
|
||||
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.style = camel_case
|
||||
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.symbols = interfaces
|
||||
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.style = i_prefix_pascal_case
|
||||
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.symbols = parameters_and_locals
|
||||
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.style = camel_case
|
||||
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.most_symbols_should_be_pascal_case.symbols = most_symbols
|
||||
dotnet_naming_rule.most_symbols_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.most_symbols_should_be_pascal_case.severity = warning
|
||||
|
||||
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.severity = none
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.symbols = static_private_or_internal_field
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
### StyleCop.Analyzers
|
||||
### https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/DOCUMENTATION.md
|
||||
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.symbols = const_private_field
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.style = pascal_case
|
||||
# Below we enable rule categories by setting severity to warning.
|
||||
# We'll only list rules to disable.
|
||||
# Individual rules we wish to disable are typically set to none severity.
|
||||
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.symbols = internal_field
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
# Covers SAxxxx and SXxxxx rules
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.LayoutRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.MaintainabilityRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.NamingRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.OrderingRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.ReadabilityRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.SpacingRules.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.SpecialRules.severity = warning
|
||||
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.severity = warning
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case
|
||||
# Rules that are covered by IDE0001 Simplify name
|
||||
dotnet_diagnostic.SA1125.severity = none # UseShorthandForNullableTypes
|
||||
|
||||
# Naming rules
|
||||
# Rules that are covered by IDE0047 Remove unnecessary parentheses
|
||||
dotnet_diagnostic.SA1119.severity = none # StatementMustNotUseUnnecessaryParenthesis
|
||||
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
#require a space after a keyword in a control flow statement such as a for loop
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
# Rules that are covered by IDE0055 Formatting Rules
|
||||
dotnet_diagnostic.SA1027.severity = none # UseTabsCorrectly
|
||||
|
||||
#Formatting - wrapping options
|
||||
# Rules that are covered by IDE1006 Naming Rules
|
||||
dotnet_diagnostic.SA1300.severity = none # ElementMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1302.severity = none # InterfaceNamesMustBeginWithI
|
||||
dotnet_diagnostic.SA1303.severity = none # ConstFieldNamesMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1304.severity = none # NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1306.severity = none # FieldNamesMustBeginWithLowerCaseLetter
|
||||
dotnet_diagnostic.SA1307.severity = none # AccessibleFieldsMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1311.severity = none # StaticReadonlyFieldsMustBeginWithUpperCaseLetter
|
||||
dotnet_diagnostic.SA1312.severity = none # VariableNamesMustBeginWithLowerCaseLetter
|
||||
dotnet_diagnostic.SA1313.severity = none # ParameterNamesMustBeginWithLowerCaseLetter
|
||||
|
||||
#leave code block on single line
|
||||
csharp_preserve_single_line_blocks = true
|
||||
#leave statements and member declarations on the same line
|
||||
csharp_preserve_single_line_statements = true
|
||||
# Rules that conflict with OpenRA project style conventions
|
||||
dotnet_diagnostic.SA1101.severity = none # PrefixLocalCallsWithThis
|
||||
dotnet_diagnostic.SA1107.severity = none # CodeMustNotContainMultipleStatementsOnOneLine
|
||||
dotnet_diagnostic.SA1116.severity = none # SplitParametersMustStartOnLineAfterDeclaration
|
||||
dotnet_diagnostic.SA1117.severity = none # ParametersMustBeOnSameLineOrSeparateLines
|
||||
dotnet_diagnostic.SA1118.severity = none # ParameterMustNotSpanMultipleLines
|
||||
dotnet_diagnostic.SA1122.severity = none # UseStringEmptyForEmptyStrings
|
||||
dotnet_diagnostic.SA1124.severity = none # DoNotUseRegions
|
||||
dotnet_diagnostic.SA1127.severity = none # GenericTypeConstraintsMustBeOnOwnLine
|
||||
dotnet_diagnostic.SA1132.severity = none # DoNotCombineFields
|
||||
dotnet_diagnostic.SA1135.severity = none # UsingDirectivesMustBeQualified
|
||||
dotnet_diagnostic.SA1136.severity = none # EnumValuesShouldBeOnSeparateLines
|
||||
dotnet_diagnostic.SA1200.severity = none # UsingDirectivesMustBePlacedCorrectly
|
||||
dotnet_diagnostic.SA1201.severity = none # ElementsMustAppearInTheCorrectOrder
|
||||
dotnet_diagnostic.SA1202.severity = none # ElementsMustBeOrderedByAccess
|
||||
dotnet_diagnostic.SA1204.severity = none # StaticElementsMustAppearBeforeInstanceElements
|
||||
dotnet_diagnostic.SA1214.severity = none # ReadonlyElementsMustAppearBeforeNonReadonlyElements
|
||||
dotnet_diagnostic.SX1309.severity = none # FieldNamesMustBeginWithUnderscore
|
||||
dotnet_diagnostic.SX1309S.severity = none # StaticFieldNamesMustBeginWithUnderscore
|
||||
dotnet_diagnostic.SA1314.severity = none # TypeParameterNamesMustBeginWithT
|
||||
dotnet_diagnostic.SA1400.severity = none # AccessModifierMustBeDeclared
|
||||
dotnet_diagnostic.SA1401.severity = none # FieldsMustBePrivate
|
||||
dotnet_diagnostic.SA1402.severity = none # FileMayOnlyContainASingleType
|
||||
dotnet_diagnostic.SA1407.severity = none # ArithmeticExpressionsMustDeclarePrecedence
|
||||
dotnet_diagnostic.SA1413.severity = none # UseTrailingCommasInMultiLineInitializers
|
||||
dotnet_diagnostic.SA1501.severity = none # StatementMustNotBeOnSingleLine
|
||||
dotnet_diagnostic.SA1502.severity = none # ElementMustNotBeOnSingleLine
|
||||
dotnet_diagnostic.SA1503.severity = none # BracesMustNotBeOmitted
|
||||
dotnet_diagnostic.SA1516.severity = none # ElementsMustBeSeparatedByBlankLine
|
||||
dotnet_diagnostic.SA1519.severity = none # BracesMustNotBeOmittedFromMultiLineChildStatement
|
||||
dotnet_diagnostic.SA1520.severity = none # UseBracesConsistently
|
||||
dotnet_diagnostic.SA1600.severity = none # ElementsMustBeDocumented
|
||||
dotnet_diagnostic.SA1601.severity = none # PartialElementsMustBeDocumented
|
||||
dotnet_diagnostic.SA1602.severity = none # EnumerationItemsMustBeDocumented
|
||||
dotnet_diagnostic.SA1611.severity = none # ElementParametersShouldBeDocumented
|
||||
dotnet_diagnostic.SA1615.severity = none # ElementReturnValueShouldBeDocumented
|
||||
dotnet_diagnostic.SA1618.severity = none # GenericTypeParametersShouldBeDocumented
|
||||
dotnet_diagnostic.SA1623.severity = none # PropertySummaryDocumentationShouldMatchAccessors
|
||||
dotnet_diagnostic.SA1633.severity = none # FileMustHaveHeader
|
||||
dotnet_diagnostic.SA1642.severity = none # ConstructorSummaryDocumentationShouldBeginWithStandardText
|
||||
dotnet_diagnostic.SA1649.severity = none # FileNameMustMatchTypeName
|
||||
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
#### Code Quality Rules
|
||||
#### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/
|
||||
|
||||
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
# Below we enable specific rules by setting severity to warning.
|
||||
# Rules are listed below with any options available.
|
||||
# Options are commented out if they match the defaults.
|
||||
|
||||
; 4-column tab indentation
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
# Rule options that apply over multiple rules are set here.
|
||||
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-quality-rule-options
|
||||
dotnet_code_quality.api_surface = all
|
||||
|
||||
### Design Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/design-warnings
|
||||
|
||||
# Collections should implement generic interface.
|
||||
#dotnet_code_quality.CA1010.additional_required_generic_interfaces =
|
||||
dotnet_diagnostic.CA1010.severity = warning
|
||||
|
||||
# Abstract types should not have public constructors.
|
||||
dotnet_diagnostic.CA1012.severity = warning
|
||||
|
||||
# Mark attributes with 'AttributeUsageAttribute'.
|
||||
dotnet_diagnostic.CA1018.severity = warning
|
||||
|
||||
# Override methods on comparable types.
|
||||
dotnet_diagnostic.CA1036.severity = warning
|
||||
|
||||
# Provide ObsoleteAttribute message.
|
||||
dotnet_diagnostic.CA1041.severity = warning
|
||||
|
||||
# Do not declare protected members in sealed types.
|
||||
dotnet_diagnostic.CA1047.severity = warning
|
||||
|
||||
# Declare types in namespaces.
|
||||
dotnet_diagnostic.CA1050.severity = warning
|
||||
|
||||
# Static holder types should be 'Static' or 'NotInheritable'.
|
||||
dotnet_diagnostic.CA1052.severity = warning
|
||||
|
||||
# Do not hide base class methods.
|
||||
dotnet_diagnostic.CA1061.severity = warning
|
||||
|
||||
# Exceptions should be public.
|
||||
dotnet_diagnostic.CA1064.severity = warning
|
||||
|
||||
# Implement 'IEquatable' when overriding 'Equals'.
|
||||
dotnet_diagnostic.CA1066.severity = warning
|
||||
|
||||
# Override 'Equals' when implementing 'IEquatable'.
|
||||
dotnet_diagnostic.CA1067.severity = warning
|
||||
|
||||
# 'CancellationToken' parameters must come last.
|
||||
dotnet_diagnostic.CA1068.severity = warning
|
||||
|
||||
# Do not declare event fields as virtual.
|
||||
dotnet_diagnostic.CA1070.severity = warning
|
||||
|
||||
### Documentation Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/documentation-warnings
|
||||
|
||||
# Avoid using 'cref' tags with a prefix.
|
||||
dotnet_diagnostic.CA1200.severity = warning
|
||||
|
||||
### Globalization Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/globalization-warnings
|
||||
|
||||
### Portability and Interoperability Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/interoperability-warnings
|
||||
|
||||
# Do not use 'OutAttribute' on string parameters for P/Invokes.
|
||||
dotnet_diagnostic.CA1417.severity = warning
|
||||
|
||||
### Maintainability Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/maintainability-warnings
|
||||
|
||||
# Use 'nameof' in place of string.
|
||||
dotnet_diagnostic.CA1507.severity = warning
|
||||
|
||||
### Naming Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/naming-warnings
|
||||
|
||||
# Do not prefix enum values with type name.
|
||||
dotnet_code_quality.CA1712.enum_values_prefix_trigger = AnyEnumValue
|
||||
dotnet_diagnostic.CA1712.severity = warning
|
||||
|
||||
# Flags enums should have plural names.
|
||||
dotnet_diagnostic.CA1714.severity = warning
|
||||
|
||||
# Only 'FlagsAttribute' enums should have plural names.
|
||||
dotnet_diagnostic.CA1717.severity = warning
|
||||
|
||||
### Performance Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/performance-warnings
|
||||
|
||||
# Use Literals Where Appropriate.
|
||||
#dotnet_code_quality.CA1802.required_modifiers = static
|
||||
dotnet_diagnostic.CA1802.severity = warning
|
||||
|
||||
# Remove empty finalizers.
|
||||
dotnet_diagnostic.CA1821.severity = warning
|
||||
|
||||
# Mark members as static.
|
||||
dotnet_code_quality.CA1822.api_surface = private,internal
|
||||
dotnet_diagnostic.CA1822.severity = warning
|
||||
|
||||
# Avoid unused private fields.
|
||||
dotnet_diagnostic.CA1823.severity = warning
|
||||
|
||||
# Avoid zero-length array allocations.
|
||||
dotnet_diagnostic.CA1825.severity = warning
|
||||
|
||||
# Use property instead of Linq Enumerable method.
|
||||
#dotnet_code_quality.CA1826.exclude_ordefault_methods = false
|
||||
dotnet_diagnostic.CA1826.severity = warning
|
||||
|
||||
# Do not use Count/LongCount when Any can be used.
|
||||
dotnet_diagnostic.CA1827.severity = warning
|
||||
|
||||
# Do not use CountAsync/LongCountAsync when AnyAsync can be used.
|
||||
dotnet_diagnostic.CA1828.severity = warning
|
||||
|
||||
# Use Length/Count property instead of Enumerable.Count method.
|
||||
dotnet_diagnostic.CA1829.severity = warning
|
||||
|
||||
# Prefer strongly-typed Append and Insert method overloads on StringBuilder.
|
||||
dotnet_diagnostic.CA1830.severity = warning
|
||||
|
||||
# Use AsSpan instead of Range-based indexers for string when appropriate.
|
||||
dotnet_diagnostic.CA1831.severity = warning
|
||||
|
||||
# Use AsSpan or AsMemory instead of Range-based indexers for getting ReadOnlySpan or ReadOnlyMemory portion of an array.
|
||||
dotnet_diagnostic.CA1832.severity = warning
|
||||
|
||||
# Use AsSpan or AsMemory instead of Range-based indexers for getting Span or Memory portion of an array.
|
||||
dotnet_diagnostic.CA1833.severity = warning
|
||||
|
||||
# Use StringBuilder.Append(char) for single character strings.
|
||||
dotnet_diagnostic.CA1834.severity = warning
|
||||
|
||||
# Prefer the memory-based overloads of ReadAsync/WriteAsync methods in stream-based classes.
|
||||
dotnet_diagnostic.CA1835.severity = warning
|
||||
|
||||
# Prefer IsEmpty over Count when available.
|
||||
dotnet_diagnostic.CA1836.severity = warning
|
||||
|
||||
# Use Environment.ProcessId instead of Process.GetCurrentProcess().Id.
|
||||
dotnet_diagnostic.CA1837.severity = warning
|
||||
|
||||
# Avoid StringBuilder parameters for P/Invokes.
|
||||
dotnet_diagnostic.CA1838.severity = warning
|
||||
|
||||
# Use Environment.ProcessPath instead of Process.GetCurrentProcess().MainModule.FileName.
|
||||
dotnet_diagnostic.CA1839.severity = warning
|
||||
|
||||
# Use Environment.CurrentManagedThreadId instead of Thread.CurrentThread.ManagedThreadId.
|
||||
dotnet_diagnostic.CA1840.severity = warning
|
||||
|
||||
# Prefer Dictionary Contains methods.
|
||||
dotnet_diagnostic.CA1841.severity = warning
|
||||
|
||||
# Do not use 'WhenAll' with a single task.
|
||||
dotnet_diagnostic.CA1842.severity = warning
|
||||
|
||||
# Do not use 'WaitAll' with a single task.
|
||||
dotnet_diagnostic.CA1843.severity = warning
|
||||
|
||||
# Provide memory-based overrides of async methods when subclassing 'Stream'.
|
||||
dotnet_diagnostic.CA1844.severity = warning
|
||||
|
||||
# Use span-based 'string.Concat'. (Not available on mono)
|
||||
dotnet_diagnostic.CA1845.severity = none
|
||||
|
||||
# Prefer AsSpan over Substring.
|
||||
dotnet_diagnostic.CA1846.severity = warning
|
||||
|
||||
# Use string.Contains(char) instead of string.Contains(string) with single characters.
|
||||
dotnet_diagnostic.CA1847.severity = warning
|
||||
|
||||
# Call async methods when in an async method.
|
||||
dotnet_diagnostic.CA1849.severity = warning
|
||||
|
||||
# Prefer static HashData method over ComputeHash. (Not available on mono)
|
||||
dotnet_diagnostic.CA1850.severity = none
|
||||
|
||||
# Seal internal types.
|
||||
dotnet_diagnostic.CA1852.severity = warning
|
||||
|
||||
# Unnecessary call to 'Dictionary.ContainsKey(key)'.
|
||||
dotnet_diagnostic.CA1853.severity = warning
|
||||
|
||||
# Prefer the IDictionary.TryGetValue(TKey, out TValue) method.
|
||||
dotnet_diagnostic.CA1854.severity = warning
|
||||
|
||||
# Use Span<T>.Clear() instead of Span<T>.Fill().
|
||||
dotnet_diagnostic.CA1855.severity = warning
|
||||
|
||||
# Use StartsWith instead of IndexOf.
|
||||
dotnet_diagnostic.CA1858.severity = warning
|
||||
|
||||
# Avoid using 'Enumerable.Any()' extension method.
|
||||
dotnet_diagnostic.CA1860.severity = warning
|
||||
|
||||
### Reliability Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/reliability-warnings
|
||||
|
||||
# Do not assign property within its setter.
|
||||
dotnet_diagnostic.CA2011.severity = warning
|
||||
|
||||
# Use ValueTasks correctly.
|
||||
dotnet_diagnostic.CA2012.severity = warning
|
||||
|
||||
# Do not use ReferenceEquals with value types.
|
||||
dotnet_diagnostic.CA2013.severity = warning
|
||||
|
||||
# Do not use stackalloc in loops.
|
||||
dotnet_diagnostic.CA2014.severity = warning
|
||||
|
||||
# Forward the CancellationToken parameter to methods that take one.
|
||||
dotnet_diagnostic.CA2016.severity = warning
|
||||
|
||||
# The 'count' argument to Buffer.BlockCopy should specify the number of bytes to copy.
|
||||
dotnet_diagnostic.CA2018.severity = warning
|
||||
|
||||
# ThreadStatic fields should not use inline initialization.
|
||||
dotnet_diagnostic.CA2019.severity = warning
|
||||
|
||||
### Security Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/security-warnings
|
||||
|
||||
# Do Not Use Broken Cryptographic Algorithms.
|
||||
dotnet_diagnostic.CA5351.severity = warning
|
||||
|
||||
### Usage Rules
|
||||
### https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/usage-warnings
|
||||
|
||||
# Call GC.SuppressFinalize correctly.
|
||||
dotnet_diagnostic.CA1816.severity = warning
|
||||
|
||||
# Rethrow to preserve stack details.
|
||||
dotnet_diagnostic.CA2200.severity = warning
|
||||
|
||||
# Initialize value type static fields inline.
|
||||
dotnet_diagnostic.CA2207.severity = warning
|
||||
|
||||
# Instantiate argument exceptions correctly.
|
||||
dotnet_diagnostic.CA2208.severity = warning
|
||||
|
||||
# Dispose methods should call base class dispose.
|
||||
dotnet_diagnostic.CA2215.severity = warning
|
||||
|
||||
# Disposable types should declare finalizer.
|
||||
dotnet_diagnostic.CA2216.severity = warning
|
||||
|
||||
# Override GetHashCode on overriding Equals.
|
||||
dotnet_diagnostic.CA2218.severity = warning
|
||||
|
||||
# Overload operator equals on overriding ValueType.Equals.
|
||||
dotnet_diagnostic.CA2231.severity = warning
|
||||
|
||||
# Provide correct arguments to formatting methods.
|
||||
#dotnet_code_quality.CA2241.additional_string_formatting_methods =
|
||||
dotnet_code_quality.CA2241.try_determine_additional_string_formatting_methods_automatically = true
|
||||
dotnet_diagnostic.CA2241.severity = warning
|
||||
|
||||
# Test for NaN correctly.
|
||||
dotnet_diagnostic.CA2242.severity = warning
|
||||
|
||||
# Attribute string literals should parse correctly.
|
||||
dotnet_diagnostic.CA2243.severity = warning
|
||||
|
||||
# Do not duplicate indexed element initializations.
|
||||
dotnet_diagnostic.CA2244.severity = warning
|
||||
|
||||
# Do not assign a property to itself.
|
||||
dotnet_diagnostic.CA2245.severity = warning
|
||||
|
||||
# Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum instead of TaskContinuationOptions enum.
|
||||
dotnet_diagnostic.CA2247.severity = warning
|
||||
|
||||
# Provide correct enum argument to Enum.HasFlag.
|
||||
dotnet_diagnostic.CA2248.severity = warning
|
||||
|
||||
# Use ThrowIfCancellationRequested.
|
||||
dotnet_diagnostic.CA2250.severity = warning
|
||||
|
||||
# Ensure ThreadStatic is only used with static fields.
|
||||
dotnet_diagnostic.CA2259.severity = warning
|
||||
|
||||
### Roslynator
|
||||
### https://github.com/JosefPihrt/Roslynator/tree/main/docs/analyzers
|
||||
|
||||
# Below we enable specific rules by setting severity to warning.
|
||||
|
||||
# Use 'Count' property instead of 'Any' method.
|
||||
dotnet_diagnostic.RCS1080.severity = warning
|
||||
|
||||
# Use read-only auto-implemented property.
|
||||
dotnet_diagnostic.RCS1170.severity = warning
|
||||
|
||||
# Unnecessary interpolated string.
|
||||
dotnet_diagnostic.RCS1214.severity = warning
|
||||
|
||||
# Unnecessary usage of verbatim string literal.
|
||||
dotnet_diagnostic.RCS1192.severity = warning
|
||||
|
||||
# Use pattern matching instead of combination of 'as' operator and null check.
|
||||
dotnet_diagnostic.RCS1221.severity = warning
|
||||
|
||||
# Expression is always equal to 'true'.
|
||||
dotnet_diagnostic.RCS1215.severity = warning
|
||||
|
||||
# Use StringComparison when comparing strings.
|
||||
dotnet_diagnostic.RCS1155.severity = warning
|
||||
|
||||
# Abstract type should not have public constructors.
|
||||
dotnet_diagnostic.RCS1160.severity = warning
|
||||
|
||||
# Optimize 'Dictionary<TKey, TValue>.ContainsKey' call.
|
||||
dotnet_diagnostic.RCS1235.severity = warning
|
||||
|
||||
# Call extension method as instance method.
|
||||
dotnet_diagnostic.RCS1196.severity = warning
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -10,5 +10,5 @@ contact_links:
|
||||
url: https://discord.openra.net/
|
||||
about: Join the community Discord server for community discussion and support.
|
||||
- name: OpenRA IRC Channel
|
||||
url: https://webchat.freenode.net/#openra
|
||||
about: Join our development IRC channel on freenode for discussion of development topics.
|
||||
url: https://web.libera.chat/#openra
|
||||
about: Join our development IRC channel on Libera for discussion of development topics.
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -13,4 +13,4 @@ You can help speed up the review process by following a few steps:
|
||||
* Respond to review comments as soon as you reasonably can. Reviewers will usually prioritize Pull Requests that are still fresh in their minds. Make sure to leave a comment when you push new changes, otherwise GitHub does not automatically notify reviewers!
|
||||
* Leave a polite comment asking for reviews if a week or more has passed without feedback.
|
||||
|
||||
If you need any help you can ask in the #openra IRC channel on freenode (most active during European evenings).
|
||||
If you need any help you can ask on Discord (https://discord.openra.net) or in the #openra IRC channel on Libera (not as active as Discord).
|
||||
48
.github/workflows/ci.yaml
vendored
48
.github/workflows/ci.yaml
vendored
@@ -1,48 +0,0 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed ]
|
||||
|
||||
jobs:
|
||||
linux-mono:
|
||||
name: Linux (mono)
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
mono --version
|
||||
make check
|
||||
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult bin/OpenRA.Test.dll
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make test
|
||||
|
||||
windows:
|
||||
name: Windows (Framework 4.7)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check Code
|
||||
shell: powershell
|
||||
run: |
|
||||
.\make.ps1 check
|
||||
Invoke-Expression "$home\.nuget\packages\nunit.consolerunner\3.11.1\tools\nunit3-console.exe --noresult bin/OpenRA.Test.dll"
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
chocolatey install lua --version 5.1.5.52
|
||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||
.\make.ps1 check-scripts
|
||||
.\make.ps1 test
|
||||
81
.github/workflows/ci.yml
vendored
Normal file
81
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed, 'prep-*' ]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (.NET 6.0)
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
make check
|
||||
make tests
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make TREAT_WARNINGS_AS_ERRORS=true test
|
||||
|
||||
linux-mono:
|
||||
name: Linux (mono)
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
mono --version
|
||||
make RUNTIME=mono check
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
# check-scripts does not depend on .net/mono, so is not needed here
|
||||
make RUNTIME=mono TREAT_WARNINGS_AS_ERRORS=true test
|
||||
|
||||
windows:
|
||||
name: Windows (.NET 6.0)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Check Code
|
||||
shell: powershell
|
||||
run: |
|
||||
# Work around runtime failures on the GH Actions runner
|
||||
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
|
||||
.\make.ps1 check
|
||||
.\make.ps1 tests
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
choco install lua --version 5.1.5.52
|
||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||
$ENV:TREAT_WARNINGS_AS_ERRORS = "true"
|
||||
.\make.ps1 check-scripts
|
||||
.\make.ps1 test
|
||||
78
.github/workflows/documentation.yml
vendored
78
.github/workflows/documentation.yml
vendored
@@ -8,23 +8,31 @@ on:
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
@@ -58,42 +66,78 @@ jobs:
|
||||
docs:
|
||||
name: Update docs.openra.net
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone docs.openra.net
|
||||
uses: actions/checkout@v2
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs.git
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
ref: playtest
|
||||
|
||||
- name: Clone docs.openra.net (Release)
|
||||
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: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/playtest/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/playtest/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/playtest/lua.md"
|
||||
./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: Update docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/release/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/release/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/release/lua.md"
|
||||
./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: Push docs.openra.net
|
||||
- name: Commit docs.openra.net
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd docs
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git add api/*.md
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
- name: Push docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
run: |
|
||||
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
|
||||
|
||||
37
.github/workflows/itch.yml
vendored
37
.github/workflows/itch.yml
vendored
@@ -8,24 +8,23 @@ on:
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
itch:
|
||||
name: Deploy to itch.io
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository == 'openra/openra'
|
||||
steps:
|
||||
- name: Download Packages
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64.exe"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64-winportable.zip" -O "OpenRA-${GIT_TAG}-x64-win-itch.zip"
|
||||
wget -q "https://github.com${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}.dmg"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${GIT_TAG}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${GIT_TAG}-x64-win-itch.zip" .itch.toml
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
||||
|
||||
- name: Publish Windows Installer
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
@@ -33,9 +32,9 @@ jobs:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: win
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}-x64.exe"
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe
|
||||
|
||||
- name: Publish Windows Itch Bundle
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
@@ -43,7 +42,7 @@ jobs:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: itch
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
||||
|
||||
@@ -53,9 +52,9 @@ jobs:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: macos
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}.dmg"
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg
|
||||
|
||||
- name: Publish RA AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
@@ -63,7 +62,7 @@ jobs:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-ra
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
||||
|
||||
@@ -73,7 +72,7 @@ jobs:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-cnc
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
||||
|
||||
@@ -83,6 +82,6 @@ jobs:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-d2k
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
||||
|
||||
68
.github/workflows/packaging.yml
vendored
68
.github/workflows/packaging.yml
vendored
@@ -7,13 +7,45 @@ on:
|
||||
- 'playtest-*'
|
||||
- 'devtest-*'
|
||||
|
||||
permissions:
|
||||
contents: write # for release creation (svenstaro/upload-release-action)
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-20.04
|
||||
source:
|
||||
name: Source Tarball
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Source
|
||||
run: |
|
||||
mkdir -p build/source
|
||||
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/source/*
|
||||
|
||||
linux:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
@@ -21,6 +53,7 @@ jobs:
|
||||
- name: Package AppImages
|
||||
run: |
|
||||
mkdir -p build/linux
|
||||
sudo apt install libfuse2
|
||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||
|
||||
- name: Upload Packages
|
||||
@@ -33,16 +66,21 @@ jobs:
|
||||
file: build/linux/*
|
||||
|
||||
macos:
|
||||
name: macOS Disk Images
|
||||
runs-on: macos-10.15
|
||||
name: macOS Disk Image
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Disk Images
|
||||
- name: Package Disk Image
|
||||
env:
|
||||
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
|
||||
@@ -53,7 +91,7 @@ jobs:
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||
|
||||
- name: Upload Packages
|
||||
- name: Upload Package
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -64,15 +102,21 @@ jobs:
|
||||
|
||||
windows:
|
||||
name: Windows Installers
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
sudo apt install nsis
|
||||
sudo apt-get update
|
||||
sudo apt-get install nsis wine64
|
||||
|
||||
- name: Package Installers
|
||||
run: |
|
||||
|
||||
19
.gitignore
vendored
19
.gitignore
vendored
@@ -13,19 +13,10 @@ obj
|
||||
_ReSharper.*/
|
||||
/.vs
|
||||
|
||||
# Visual Studio Code
|
||||
/.vscode/settings.json
|
||||
|
||||
# binaries
|
||||
mods/*/*.dll
|
||||
mods/*/*.mdb
|
||||
mods/*/*.pdb
|
||||
/*.dll
|
||||
/*.dll.config
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.pdb
|
||||
/*.mdb
|
||||
/*.exe
|
||||
/*.exe.config
|
||||
thirdparty/download/*
|
||||
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
# backup files by various editors
|
||||
@@ -50,10 +41,6 @@ Settings.md
|
||||
openra.6
|
||||
update.log
|
||||
|
||||
# StyleCop
|
||||
*.Cache
|
||||
StyleCopViolations.xml
|
||||
|
||||
# SublimeText
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csharp",
|
||||
"openra.oraide-vscode",
|
||||
"openra.vscode-openra-lua",
|
||||
"EditorConfig.EditorConfig",
|
||||
"ms-vscode.mono-debug"
|
||||
"macabeus.vscode-fluent",
|
||||
]
|
||||
}
|
||||
|
||||
78
.vscode/launch.json
vendored
78
.vscode/launch.json
vendored
@@ -3,62 +3,60 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch (TD)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=cnc"],
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (RA)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=ra"],
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (D2k)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=d2k"],
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (TS)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=ts"],
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch Utility",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.Utility.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.Utility.exe",
|
||||
},
|
||||
"args": ["all", "--docs", "{DEV_VERSION}"],
|
||||
"env": {
|
||||
"ENGINE_DIR": ".."
|
||||
},
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
]
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"omnisharp.enableRoslynAnalyzers": true
|
||||
}
|
||||
25
.vscode/tasks.json
vendored
25
.vscode/tasks.json
vendored
@@ -1,13 +1,36 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"options": {
|
||||
"env": {
|
||||
"ENGINE_DIR": ".."
|
||||
}
|
||||
},
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "make",
|
||||
"args": ["all"],
|
||||
"args": ["all", "CONFIGURATION=Debug"],
|
||||
"windows": {
|
||||
"command": "make.cmd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Utility",
|
||||
"command": "dotnet ${workspaceRoot}/bin/OpenRA.Utility.dll ${input:modId} ${input:command}",
|
||||
"type": "shell",
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "modId",
|
||||
"description": "ID of the mod to run",
|
||||
"default": "all",
|
||||
"type": "promptString"
|
||||
}, {
|
||||
"id": "command",
|
||||
"description": "Name of the command + parameters",
|
||||
"default": "",
|
||||
"type": "promptString"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
38
AUTHORS
38
AUTHORS
@@ -2,28 +2,30 @@ OpenRA wouldn't be where it is today without the
|
||||
hard work of many contributors.
|
||||
|
||||
The OpenRA developers are:
|
||||
* Chris Forbes (chrisf)
|
||||
* Gustas Kažukauskas (PunkPun)
|
||||
* Lukas Franke (abcdefg30)
|
||||
* Paul Chote (pchote)
|
||||
* Reaperrr
|
||||
* Matthias Mailänder (Mailaender)
|
||||
|
||||
Previous developers included:
|
||||
* Alli Witheford (alzeih)
|
||||
* Caleb Anderson (RobotCaleb)
|
||||
* Chris Forbes (chrisf)
|
||||
* Curtis Shmyr (hamb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Igor Popov (ihptru)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Megan Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Oliver Brakmann (obrakmann)
|
||||
* Paul Chote (pchote)
|
||||
* Pavel Penev (penev92)
|
||||
* Reaperrr
|
||||
* Robert Pepperell (ytinasni)
|
||||
* ScottNZ
|
||||
* Tom Roostan (RoosterDragon)
|
||||
|
||||
Also thanks to:
|
||||
* abmyii
|
||||
* anvilvapre (anvilvapre)
|
||||
* Adam Valy (Tschokky)
|
||||
* Akseli Virtanen (RAGEQUIT)
|
||||
* Alexander Fast (mizipzor)
|
||||
@@ -37,6 +39,7 @@ Also thanks to:
|
||||
* Arik Lirette (Angusm3)
|
||||
* Barnaby Smith (mvi)
|
||||
* Bellator
|
||||
* Bernd Stellwag (burned42)
|
||||
* Biofreak
|
||||
* Braxton Williams (Buddytex)
|
||||
* Brendan Gluth (Mechanical_Man)
|
||||
@@ -46,6 +49,7 @@ Also thanks to:
|
||||
* Chris Cameron (Vesuvian)
|
||||
* Chris Grant (Unit158)
|
||||
* Christer Ulfsparre (Holloweye)
|
||||
* Christoph Lahner (chlah)
|
||||
* clem
|
||||
* Cody Brittain (Generalcamo)
|
||||
* Constantin Helmig (CH4Code)
|
||||
@@ -58,6 +62,7 @@ Also thanks to:
|
||||
* DeadlySurprise
|
||||
* Dmitri Suvorov (suvjunmd)
|
||||
* dtluna
|
||||
* Eduardo Cáceres (eduherminio)
|
||||
* Erasmus Schroder (rasco)
|
||||
* Eric Bajumpaa (SteelPhase)
|
||||
* Evgeniy Sergeev (evgeniysergeev)
|
||||
@@ -69,12 +74,14 @@ Also thanks to:
|
||||
* Glenn Martin Jensen (Baxxster)
|
||||
* Gordon Martin (Happy0)
|
||||
* Guido Lipke (LipkeGu)
|
||||
* Guillermo Cuenca (Wylli)
|
||||
* Hervé Matysiak (Herve-M)
|
||||
* Huw Pascoe
|
||||
* Ian T. Jacobsen (Smilex)
|
||||
* Imago
|
||||
* Iran
|
||||
* Ishan Bhargava (ishantheperson)
|
||||
* Ivaylo Draganov (dragunoff)
|
||||
* Jacob Dufault (jacobdufault)
|
||||
* James Dunne (jsd)
|
||||
* James Gilbert (DSUK)
|
||||
@@ -113,6 +120,7 @@ Also thanks to:
|
||||
* Mike Gagné (AngryBirdz)
|
||||
* Muh
|
||||
* Mustafa Alperen Seki (MustaphaTR)
|
||||
* Nathan Nichols (cracksmoka420)
|
||||
* Neil Shivkar (havok13888)
|
||||
* Nikolay Fomin (netnazgul)
|
||||
* Nooze
|
||||
@@ -135,12 +143,12 @@ Also thanks to:
|
||||
* Rikhardur Bjarni Einarsson (WolfGaming)
|
||||
* Sascha Biedermann (bidifx)
|
||||
* Sean Hunt (coppro)
|
||||
* Sebastien Kerguen (xanax)
|
||||
* Shawn Collins (UberWaffe)
|
||||
* Simon Verbeke (Saticmotion)
|
||||
* Stuart McHattie (SDJMcHattie)
|
||||
* Taryn Hill (Phrohdoh)
|
||||
* Teemu Nieminen (Temeez)
|
||||
* Thomas Christlieb (ThomasChr)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tirili
|
||||
* Tomas Einarsson (Mesacer)
|
||||
@@ -173,9 +181,17 @@ under the MIT license.
|
||||
Using FuzzyLogicLibrary (fuzzynet) by Dmitry
|
||||
Kaluzhny and released under the GNU GPL terms.
|
||||
|
||||
Using Open.Nat by Lucas Ontivero, based on the work
|
||||
of Alan McGovern and Ben Motmans and distributed
|
||||
under the MIT license.
|
||||
Using Mono.Nat by Alan McGovern, Ben Motmans,
|
||||
Nicholas Terry distributed under the MIT license.
|
||||
|
||||
Using MP3Sharp by Robert Bruke and Zane Wagner
|
||||
licensed under the GNU LGPL Version 3.
|
||||
|
||||
Using TagLib# by Stephen Shaw licensed under the
|
||||
GNU LGPL Version 2.1.
|
||||
|
||||
Using NVorbis by Andrew Ward distributed under
|
||||
the MIT license.
|
||||
|
||||
Using ICSharpCode.SharpZipLib initially by Mike
|
||||
Krueger and distributed under the GNU GPL terms.
|
||||
@@ -191,6 +207,12 @@ distributed under MIT License.
|
||||
|
||||
Using ANGLE distributed under the BS3 3-Clause license.
|
||||
|
||||
Using Pfim developed by Nick Babcock
|
||||
distributed under the MIT license.
|
||||
|
||||
Using Linguini by the Space Station 14 team
|
||||
licensed under Apache and MIT terms.
|
||||
|
||||
This site or product includes IP2Location LITE data
|
||||
available from http://www.ip2location.com.
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ further defined and clarified by project maintainers.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by private-messaging a project team member (users with a + in front
|
||||
of their name) via our IRC channel (#openra on freenode –
|
||||
[webchat](http://webchat.freenode.net/?channels=openra)). All
|
||||
of their name) via our IRC channel (#openra on Libera –
|
||||
[webchat](https://web.libera.chat/#openra)). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
58
Directory.Build.props
Normal file
58
Directory.Build.props
Normal file
@@ -0,0 +1,58 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<LangVersion>9</LangVersion>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
|
||||
<OutputPath>$(EngineRootPath)/bin</OutputPath>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ExternalConsole>false</ExternalConsole>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<Nullable>disable</Nullable>
|
||||
<Product>OpenRA</Product>
|
||||
<Copyright>Copyright (c) The OpenRA Developers and Contributors</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework Condition="'$(MSBuildRuntimeType)'!='Mono'">net6.0</TargetFramework>
|
||||
<TargetFramework Condition="'$(MSBuildRuntimeType)'=='Mono'">netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux-x64</TargetPlatform>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('OSX'))">osx-x64</TargetPlatform>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
<!-- Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
|
||||
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 -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
|
||||
<Compile Include="**/*.cs" Exclude="$(DefaultItemExcludes)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
|
||||
<!-- Disable code style analysis on Release builds to improve compile-time performance -->
|
||||
<ItemGroup Condition="'$(Configuration)'=='Release'">
|
||||
<Analyzer Remove="@(Analyzer)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- StyleCop -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
94
INSTALL.md
94
INSTALL.md
@@ -8,104 +8,76 @@ Windows
|
||||
|
||||
Compiling OpenRA requires the following dependencies:
|
||||
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
||||
* [.NET Framework 4.7.2 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net472) (or via Visual Studio 2017)
|
||||
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
|
||||
* [.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 MSBuild 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.
|
||||
|
||||
Run the game with `launch-game.cmd`. It can be handed arguments that specify the exact mod one wishes to run, for example, run `launch-game.cmd Game.Mod=ra` to launch Red Alert, `launch-game.cmd Game.Mod=cnc` to start Tiberian dawn or `launch-game.cmd Game.Mod=d2k` to launch Dune 2000.
|
||||
|
||||
Linux
|
||||
=====
|
||||
|
||||
Mono, version 5.18 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
|
||||
.NET 6 or Mono (version 6.12 or later) is required to compile OpenRA. We recommend using .NET 6 when possible, as Mono is poorly packaged by most Linux distributions (e.g. missing the required `msbuild` toolchain), and has been deprecated as a standalone project.
|
||||
|
||||
To compile OpenRA, run `make` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||
The [.NET 6 download page](https://dotnet.microsoft.com/download/dotnet/6.0) provides repositories for various package managers and binary releases for several architectures. If you prefer to use Mono, we suggest adding the [upstream repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version and the `msbuild` toolchain.
|
||||
|
||||
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||
|
||||
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
|
||||
|
||||
If you choose to use system libraries, or your system is not x86_64, you will need to install the following using your system package manager:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
|
||||
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.
|
||||
|
||||
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
|
||||
These can be installed using your package manager on various distros:
|
||||
|
||||
Arch Linux
|
||||
----------
|
||||
|
||||
It is important to note there is an unofficial [`openra-git`](https://aur.archlinux.org/packages/openra-git) package in the Arch User Repository (AUR) of Arch Linux. If manually compiling is the way you wish to go the build and runtime dependencies can be installed with:
|
||||
<details><summary>Arch Linux</summary>
|
||||
|
||||
```
|
||||
sudo pacman -S mono openal libgl freetype2 sdl2 lua51 xdg-utils zenity
|
||||
sudo pacman -S openal libgl freetype2 sdl2 lua51
|
||||
```
|
||||
|
||||
Debian/Ubuntu
|
||||
-------------
|
||||
|
||||
:warning: The `mono` packages in the Ubuntu < 19.04 and Debian < 10 repositories are too old to support OpenRA. :warning:
|
||||
|
||||
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases if needed.
|
||||
</details>
|
||||
<details><summary>Debian/Ubuntu</summary>
|
||||
|
||||
```
|
||||
sudo apt install mono-devel libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0 xdg-utils zenity wget
|
||||
sudo apt install libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0
|
||||
```
|
||||
|
||||
Fedora
|
||||
------
|
||||
|
||||
:warning: The `mono` packages in the Fedora repositories are too old to support OpenRA. :warning:
|
||||
|
||||
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases.
|
||||
|
||||
</details>
|
||||
<details><summary>Fedora</summary>
|
||||
|
||||
```
|
||||
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
|
||||
sudo dnf install SDL2 freetype "lua = 5.1" openal-soft
|
||||
```
|
||||
|
||||
Gentoo
|
||||
------
|
||||
</details>
|
||||
<details><summary>Gentoo</summary>
|
||||
|
||||
```
|
||||
sudo emerge -av dev-lang/mono dev-dotnet/libgdiplus media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/jpeg virtual/opengl '=dev-lang/lua-5.1.5*' x11-misc/xdg-utils gnome-extra/zenity
|
||||
sudo emerge -av media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/opengl '=dev-lang/lua-5.1.5*'
|
||||
```
|
||||
|
||||
Mageia
|
||||
------
|
||||
</details>
|
||||
<details><summary>Mageia</summary>
|
||||
|
||||
```
|
||||
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft xdg-utils zenity
|
||||
sudo dnf install SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft
|
||||
```
|
||||
|
||||
openSUSE
|
||||
--------
|
||||
</details>
|
||||
<details><summary>openSUSE</summary>
|
||||
|
||||
```
|
||||
sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity
|
||||
sudo zypper in openal-soft freetype2 SDL2 lua51
|
||||
```
|
||||
|
||||
Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
|
||||
----------------------------------------------------
|
||||
|
||||
</details>
|
||||
<details><summary>Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)</summary>
|
||||
The EPEL repository is required in order for the following command to run properly.
|
||||
|
||||
```
|
||||
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
|
||||
sudo yum install SDL2 freetype "lua = 5.1" openal-soft
|
||||
```
|
||||
</details>
|
||||
|
||||
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
|
||||
|
||||
macOS
|
||||
=====
|
||||
|
||||
Before compiling OpenRA you must install the following dependencies:
|
||||
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
|
||||
|
||||
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
|
||||
|
||||
The default behaviour is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`. If you choose to use system libraries you will need to install:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php) (`brew install sdl2`)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (`brew install freetype`)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html) (`brew install openal-soft`)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (`brew install lua@5.1`)
|
||||
[.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0) or [Mono](https://www.mono-project.com/download/stable/#download-mac) (version 6.12 or later) is required to compile OpenRA. We recommend using .NET 6 unless you are running a very old version of macOS (10.9 through 10.14).
|
||||
|
||||
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.
|
||||
|
||||
154
Makefile
154
Makefile
@@ -1,42 +1,40 @@
|
||||
############################# INSTRUCTIONS #############################
|
||||
#
|
||||
# to compile, run:
|
||||
# make [DEBUG=true]
|
||||
# make
|
||||
#
|
||||
# to compile using Mono (version 6.12 or greater) instead of .NET 6, run:
|
||||
# make RUNTIME=mono
|
||||
#
|
||||
# to compile using system libraries for native dependencies, run:
|
||||
# make [DEBUG=true] TARGETPLATFORM=unix-generic
|
||||
# make [RUNTIME=net6] TARGETPLATFORM=unix-generic
|
||||
#
|
||||
# to check the official mods for erroneous yaml files, run:
|
||||
# make test
|
||||
# make [RUNTIME=net6] test
|
||||
#
|
||||
# to check the engine and official mod dlls for code style violations, run:
|
||||
# make check
|
||||
# make [RUNTIME=net6] check
|
||||
#
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install
|
||||
# make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] install
|
||||
#
|
||||
# to install Linux startup scripts, desktop files, icons, and MIME metadata
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
|
||||
# using system libraries for native dependencies, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
|
||||
#
|
||||
# to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata
|
||||
# make install-linux-shortcuts
|
||||
#
|
||||
# to install Linux AppStream metadata
|
||||
# to install FreeDesktop AppStream metadata
|
||||
# make install-linux-appdata
|
||||
#
|
||||
# to install the Unix man page
|
||||
# make install-man
|
||||
#
|
||||
# for help, run:
|
||||
# make help
|
||||
#
|
||||
|
||||
############################## TOOLCHAIN ###############################
|
||||
#
|
||||
# List of .NET assemblies that we can guarantee exist
|
||||
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.exe OpenRA.Utility.exe OpenRA.Server.exe OpenRA.Platforms.Default.dll OpenRA.Game.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll
|
||||
|
||||
# These are explicitly shipped alongside our core files by the packaging script
|
||||
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
|
||||
|
||||
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
|
||||
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
|
||||
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll
|
||||
|
||||
######################### UTILITIES/SETTINGS ###########################
|
||||
#
|
||||
# Install locations for local installs and downstream packaging
|
||||
@@ -48,125 +46,165 @@ bindir ?= $(prefix)/bin
|
||||
libdir ?= $(prefix)/lib
|
||||
gameinstalldir ?= $(libdir)/openra
|
||||
|
||||
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
|
||||
DATA_INSTALL_DIR = $(DESTDIR)$(datadir)
|
||||
OPENRA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
|
||||
|
||||
# Toolchain
|
||||
CWD = $(shell pwd)
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
DOTNET = dotnet
|
||||
MONO = mono
|
||||
RM = rm
|
||||
RM_R = $(RM) -r
|
||||
RM_F = $(RM) -f
|
||||
RM_RF = $(RM) -rf
|
||||
|
||||
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
|
||||
RUNTIME ?= net6
|
||||
CONFIGURATION ?= Release
|
||||
DOTNET_RID = $(shell ${DOTNET} --info | grep RID: | cut -w -f3)
|
||||
ARCH_X64 = $(shell echo ${DOTNET_RID} | grep x64)
|
||||
|
||||
# Only for use in target version:
|
||||
VERSION := $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || (c=$$(git rev-parse --short HEAD 2>/dev/null) && echo git-$$c))
|
||||
|
||||
# Detect target platform for dependencies if not given by the user
|
||||
ifndef TARGETPLATFORM
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
ifeq ($(ARCH_X64),)
|
||||
TARGETPLATFORM = osx-arm64
|
||||
else
|
||||
TARGETPLATFORM = osx-x64
|
||||
endif
|
||||
else
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
TARGETPLATFORM = linux-x64
|
||||
else
|
||||
ifeq ($(UNAME_M),aarch64)
|
||||
TARGETPLATFORM = linux-arm64
|
||||
else
|
||||
TARGETPLATFORM = unix-generic
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.exe
|
||||
endif
|
||||
|
||||
##################### DEVELOPMENT BUILDS AND TESTS #####################
|
||||
#
|
||||
all:
|
||||
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.18."; exit 1)
|
||||
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
@echo "Compiling in ${CONFIGURATION} mode..."
|
||||
ifeq ($(RUNTIME), mono)
|
||||
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 6.12."; exit 1)
|
||||
@$(MSBUILD) -t:Build -restore -p:Configuration=${CONFIGURATION} -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
else
|
||||
@$(DOTNET) build -c ${CONFIGURATION} -nologo -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
endif
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@./fetch-geoip.sh
|
||||
|
||||
# dotnet clean and msbuild -t:Clean leave files that cause problems when switching between mono/dotnet
|
||||
# Deleting the intermediate / output directories ensures the build directory is actually clean
|
||||
clean:
|
||||
@-$(RM_RF) ./bin ./*/bin ./*/obj
|
||||
@$(MSBUILD) -t:Clean
|
||||
@-$(RM_RF) ./bin ./*/obj
|
||||
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
check:
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
@$(MSBUILD) -t:build -restore -p:Configuration=Debug
|
||||
@echo
|
||||
@echo "Checking runtime assemblies..."
|
||||
@$(OPENRA_UTILITY) all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
|
||||
@echo "Compiling in Debug mode..."
|
||||
ifeq ($(RUNTIME), mono)
|
||||
@$(MSBUILD) -t:clean\;build -restore -p:Configuration=Debug -warnaserror -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
else
|
||||
@$(DOTNET) clean -c Debug --nologo --verbosity minimal
|
||||
@$(DOTNET) build -c Debug -nologo -warnaserror -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
endif
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@$(OPENRA_UTILITY) all --check-explicit-interfaces
|
||||
@./utility.sh all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect conditional trait interface overrides..."
|
||||
@$(OPENRA_UTILITY) all --check-conditional-trait-interface-overrides
|
||||
@./utility.sh all --check-conditional-trait-interface-overrides
|
||||
|
||||
check-scripts:
|
||||
@echo
|
||||
@echo "Checking for Lua syntax errors..."
|
||||
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
|
||||
@luac -p $(shell find lua/* -iname '*.lua')
|
||||
@luac -p $(shell find mods/*/bits/scripts/* -iname '*.lua')
|
||||
@find mods/*/maps/ mods/*/scripts/ -iname "*.lua" -print0 | xargs -0n1 luac -p
|
||||
|
||||
test: all
|
||||
@echo
|
||||
@echo "Testing Tiberian Sun mod MiniYAML..."
|
||||
@$(OPENRA_UTILITY) ts --check-yaml
|
||||
@./utility.sh ts --check-yaml
|
||||
@echo
|
||||
@echo "Testing Dune 2000 mod MiniYAML..."
|
||||
@$(OPENRA_UTILITY) d2k --check-yaml
|
||||
@./utility.sh d2k --check-yaml
|
||||
@echo
|
||||
@echo "Testing Tiberian Dawn mod MiniYAML..."
|
||||
@$(OPENRA_UTILITY) cnc --check-yaml
|
||||
@./utility.sh cnc --check-yaml
|
||||
@echo
|
||||
@echo "Testing Red Alert mod MiniYAML..."
|
||||
@$(OPENRA_UTILITY) ra --check-yaml
|
||||
@./utility.sh ra --check-yaml
|
||||
|
||||
tests:
|
||||
@dotnet build OpenRA.Test/OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
@echo
|
||||
@dotnet test bin/OpenRA.Test.dll --test-adapter-path:.
|
||||
|
||||
############# 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
|
||||
@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'
|
||||
ifeq ($(VERSION),)
|
||||
$(error Unable to determine new version (requires git or override of variable VERSION))
|
||||
endif
|
||||
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
|
||||
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
|
||||
|
||||
install:
|
||||
@sh -c '. ./packaging/functions.sh; install_assemblies_mono $(CWD) $(OPENRA_INSTALL_DIR) $(TARGETPLATFORM) True True True'
|
||||
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(OPENRA_INSTALL_DIR) cnc d2k ra'
|
||||
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
|
||||
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
|
||||
|
||||
install-linux-shortcuts:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) $(OPENRA_INSTALL_DIR) $(BIN_INSTALL_DIR) $(DATA_INSTALL_DIR) $(VERSION) cnc d2k ra'
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra'
|
||||
|
||||
install-linux-appdata:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) $(DATA_INSTALL_DIR) cnc d2k ra'
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
|
||||
|
||||
install-man: all
|
||||
@mkdir -p $(DESTDIR)$(mandir)/man6/
|
||||
@./utility.sh all --man-page > $(DESTDIR)$(mandir)/man6/openra.6
|
||||
|
||||
help:
|
||||
@echo 'to compile, run:'
|
||||
@echo ' make [DEBUG=true]'
|
||||
@echo ' make'
|
||||
@echo
|
||||
@echo 'to compile using Mono (version 6.12 or greater) instead of .NET 6, run:'
|
||||
@echo ' make RUNTIME=mono'
|
||||
@echo
|
||||
@echo 'to compile using system libraries for native dependencies, run:'
|
||||
@echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
|
||||
@echo ' make [RUNTIME=net6] TARGETPLATFORM=unix-generic'
|
||||
@echo
|
||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||
@echo ' make test'
|
||||
@echo ' make [RUNTIME=net6] [TREAT_WARNINGS_AS_ERRORS=false] test'
|
||||
@echo
|
||||
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
||||
@echo ' make test'
|
||||
@echo ' make [RUNTIME=net6] check'
|
||||
@echo
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
|
||||
@echo ' make [prefix=/foo] install'
|
||||
@echo ' make [RUNTIME=net6] [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
|
||||
@echo
|
||||
@echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
|
||||
@echo 'using system libraries for native dependencies, run:'
|
||||
@echo ' make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
|
||||
@echo
|
||||
@echo 'to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata'
|
||||
@echo ' make install-linux-shortcuts'
|
||||
@echo
|
||||
@echo 'to install Linux AppStream metadata'
|
||||
@echo 'to install FreeDesktop AppStream metadata'
|
||||
@echo ' make install-linux-appdata'
|
||||
@echo
|
||||
@echo 'to install a Unix man page'
|
||||
@echo ' make install-man'
|
||||
|
||||
########################### MAKEFILE SETTINGS ##########################
|
||||
#
|
||||
@@ -174,4 +212,4 @@ help:
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata help
|
||||
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata install-man help
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -53,15 +53,15 @@ namespace OpenRA.Activities
|
||||
Activity childActivity;
|
||||
protected Activity ChildActivity
|
||||
{
|
||||
get { return SkipDoneActivities(childActivity); }
|
||||
private set { childActivity = value; }
|
||||
get => SkipDoneActivities(childActivity);
|
||||
private set => childActivity = value;
|
||||
}
|
||||
|
||||
Activity nextActivity;
|
||||
public Activity NextActivity
|
||||
{
|
||||
get { return SkipDoneActivities(nextActivity); }
|
||||
private set { nextActivity = value; }
|
||||
get => SkipDoneActivities(nextActivity);
|
||||
private set => nextActivity = value;
|
||||
}
|
||||
|
||||
internal static Activity SkipDoneActivities(Activity first)
|
||||
@@ -74,19 +74,19 @@ namespace OpenRA.Activities
|
||||
// drop valid activities queued after it. Walk the queue until we find a valid activity or
|
||||
// (more likely) run out of activities.
|
||||
while (first != null && first.State == ActivityState.Done)
|
||||
first = first.NextActivity;
|
||||
first = first.nextActivity;
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
public bool IsInterruptible { get; protected set; }
|
||||
public bool ChildHasPriority { get; protected set; }
|
||||
public bool IsCanceling { get { return State == ActivityState.Canceling; } }
|
||||
public bool IsCanceling => State == ActivityState.Canceling;
|
||||
bool finishing;
|
||||
bool firstRunCompleted;
|
||||
bool lastRun;
|
||||
|
||||
public Activity()
|
||||
protected Activity()
|
||||
{
|
||||
IsInterruptible = true;
|
||||
ChildHasPriority = true;
|
||||
@@ -95,7 +95,7 @@ namespace OpenRA.Activities
|
||||
public Activity TickOuter(Actor self)
|
||||
{
|
||||
if (State == ActivityState.Done)
|
||||
throw new InvalidOperationException("Actor {0} attempted to tick activity {1} after it had already completed.".F(self, GetType()));
|
||||
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} after it had already completed.");
|
||||
|
||||
if (State == ActivityState.Queued)
|
||||
{
|
||||
@@ -105,7 +105,7 @@ namespace OpenRA.Activities
|
||||
}
|
||||
|
||||
if (!firstRunCompleted)
|
||||
throw new InvalidOperationException("Actor {0} attempted to tick activity {1} before running its OnFirstRun method.".F(self, GetType()));
|
||||
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} before running its OnFirstRun method.");
|
||||
|
||||
// Only run the parent tick when the child is done.
|
||||
// We must always let the child finish on its own before continuing.
|
||||
@@ -120,7 +120,8 @@ namespace OpenRA.Activities
|
||||
lastRun = Tick(self);
|
||||
|
||||
// Avoid a single tick delay if the childactivity was just queued.
|
||||
if (ChildActivity != null && ChildActivity.State == ActivityState.Queued)
|
||||
var ca = ChildActivity;
|
||||
if (ca != null && ca.State == ActivityState.Queued)
|
||||
{
|
||||
if (ChildHasPriority)
|
||||
lastRun = TickChild(self) && finishing;
|
||||
@@ -206,18 +207,18 @@ namespace OpenRA.Activities
|
||||
|
||||
public void Queue(Activity activity)
|
||||
{
|
||||
if (NextActivity != null)
|
||||
NextActivity.Queue(activity);
|
||||
else
|
||||
NextActivity = activity;
|
||||
var it = this;
|
||||
while (it.nextActivity != null)
|
||||
it = it.nextActivity;
|
||||
it.nextActivity = activity;
|
||||
}
|
||||
|
||||
public void QueueChild(Activity activity)
|
||||
{
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.Queue(activity);
|
||||
if (childActivity != null)
|
||||
childActivity.Queue(activity);
|
||||
else
|
||||
ChildActivity = activity;
|
||||
childActivity = activity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -269,15 +270,21 @@ namespace OpenRA.Activities
|
||||
|
||||
public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface
|
||||
{
|
||||
if (includeChildren && ChildActivity != null)
|
||||
foreach (var a in ChildActivity.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
// Skips Done child and next activities
|
||||
if (includeChildren)
|
||||
{
|
||||
var ca = ChildActivity;
|
||||
if (ca != null)
|
||||
foreach (var a in ca.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
}
|
||||
|
||||
if (this is T)
|
||||
yield return (T)(object)this;
|
||||
|
||||
if (NextActivity != null)
|
||||
foreach (var a in NextActivity.ActivitiesImplementing<T>())
|
||||
var na = NextActivity;
|
||||
if (na != null)
|
||||
foreach (var a in na.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -22,11 +22,11 @@ namespace OpenRA.Activities
|
||||
IsInterruptible = interruptible;
|
||||
}
|
||||
|
||||
Action a;
|
||||
readonly Action a;
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
{
|
||||
a?.Invoke();
|
||||
a.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
@@ -23,9 +24,21 @@ using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
[Flags]
|
||||
public enum SystemActors
|
||||
{
|
||||
Player = 0,
|
||||
EditorPlayer = 1,
|
||||
World = 2,
|
||||
EditorWorld = 4
|
||||
}
|
||||
|
||||
public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
|
||||
{
|
||||
internal struct SyncHash
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public const int InvalidConditionToken = -1;
|
||||
|
||||
internal readonly struct SyncHash
|
||||
{
|
||||
public readonly ISync Trait;
|
||||
readonly Func<object, int> hashFunction;
|
||||
@@ -48,57 +61,49 @@ namespace OpenRA
|
||||
Activity currentActivity;
|
||||
public Activity CurrentActivity
|
||||
{
|
||||
get { return Activity.SkipDoneActivities(currentActivity); }
|
||||
private set { currentActivity = value; }
|
||||
get => Activity.SkipDoneActivities(currentActivity);
|
||||
private set => currentActivity = value;
|
||||
}
|
||||
|
||||
public int Generation;
|
||||
public Actor ReplacedByActor;
|
||||
|
||||
public IEffectiveOwner EffectiveOwner { get; private set; }
|
||||
public IOccupySpace OccupiesSpace { get; private set; }
|
||||
public ITargetable[] Targetables { get; private set; }
|
||||
public IEffectiveOwner EffectiveOwner { get; }
|
||||
public IOccupySpace OccupiesSpace { get; }
|
||||
public ITargetable[] Targetables { get; }
|
||||
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; private set; }
|
||||
|
||||
public bool IsIdle { get { return CurrentActivity == null; } }
|
||||
public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
|
||||
public bool IsIdle => CurrentActivity == null;
|
||||
public bool IsDead => Disposed || (health != null && health.IsDead);
|
||||
|
||||
public CPos Location { get { return OccupiesSpace.TopLeft; } }
|
||||
public WPos CenterPosition { get { return OccupiesSpace.CenterPosition; } }
|
||||
public CPos Location => OccupiesSpace.TopLeft;
|
||||
public WPos CenterPosition => OccupiesSpace.CenterPosition;
|
||||
|
||||
public WRot Orientation
|
||||
{
|
||||
get
|
||||
{
|
||||
return facing != null ? facing.Orientation : WRot.None;
|
||||
}
|
||||
}
|
||||
public WRot Orientation => facing?.Orientation ?? WRot.None;
|
||||
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public static readonly int InvalidConditionToken = -1;
|
||||
|
||||
class ConditionState
|
||||
sealed class ConditionState
|
||||
{
|
||||
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
||||
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
|
||||
public readonly List<VariableObserverNotifier> Notifiers = new();
|
||||
|
||||
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
||||
public readonly HashSet<int> Tokens = new HashSet<int>();
|
||||
public readonly HashSet<int> Tokens = new();
|
||||
}
|
||||
|
||||
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
|
||||
readonly Dictionary<string, ConditionState> conditionStates = new();
|
||||
|
||||
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
||||
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
|
||||
readonly Dictionary<int, string> conditionTokens = new();
|
||||
|
||||
int nextConditionToken = 1;
|
||||
|
||||
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
|
||||
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
|
||||
readonly Dictionary<string, int> conditionCache = new();
|
||||
|
||||
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
||||
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
|
||||
|
||||
internal SyncHash[] SyncHashes { get; private set; }
|
||||
internal SyncHash[] SyncHashes { get; }
|
||||
|
||||
readonly IFacing facing;
|
||||
readonly IHealth health;
|
||||
@@ -110,10 +115,8 @@ namespace OpenRA
|
||||
readonly IDefaultVisibility defaultVisibility;
|
||||
readonly INotifyBecomingIdle[] becomingIdles;
|
||||
readonly INotifyIdle[] tickIdles;
|
||||
readonly IEnumerable<ITargetablePositions> enabledTargetablePositions;
|
||||
WPos[] staticTargetablePositions;
|
||||
readonly IEnumerable<WPos> enabledTargetableWorldPositions;
|
||||
bool created;
|
||||
bool setStaticTargetablePositions;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict)
|
||||
{
|
||||
@@ -121,7 +124,7 @@ namespace OpenRA
|
||||
.FirstOrDefault(i => i.Count() > 1);
|
||||
|
||||
if (duplicateInit != null)
|
||||
throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
|
||||
throw new InvalidDataException($"Duplicate initializer '{duplicateInit.Key.Name}'");
|
||||
|
||||
var init = new ActorInitializer(this, initDict);
|
||||
|
||||
@@ -142,7 +145,6 @@ namespace OpenRA
|
||||
|
||||
Info = world.Map.Rules.Actors[name];
|
||||
|
||||
IPositionable positionable = null;
|
||||
var resolveOrdersList = new List<IResolveOrder>();
|
||||
var renderModifiersList = new List<IRenderModifier>();
|
||||
var rendersList = new List<IRender>();
|
||||
@@ -164,7 +166,6 @@ namespace OpenRA
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
// Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style
|
||||
// rules for spacing in order to keep these assignments compact and readable.
|
||||
{ if (trait is IPositionable t) positionable = t; }
|
||||
{ if (trait is IOccupySpace t) OccupiesSpace = t; }
|
||||
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; }
|
||||
{ if (trait is IFacing t) facing = t; }
|
||||
@@ -191,10 +192,9 @@ namespace OpenRA
|
||||
tickIdles = tickIdlesList.ToArray();
|
||||
Targetables = targetablesList.ToArray();
|
||||
var targetablePositions = targetablePositionsList.ToArray();
|
||||
enabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||
SyncHashes = syncHashesList.ToArray();
|
||||
|
||||
setStaticTargetablePositions = positionable == null && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,11 +229,6 @@ namespace OpenRA
|
||||
foreach (var notify in allObserverNotifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
|
||||
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
|
||||
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
|
||||
if (setStaticTargetablePositions)
|
||||
staticTargetablePositions = enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
|
||||
|
||||
// TODO: Other traits may need initialization after being notified of initial condition state.
|
||||
|
||||
// TODO: A post condition initialization notification phase may allow queueing activities instead.
|
||||
@@ -246,7 +241,7 @@ namespace OpenRA
|
||||
continue;
|
||||
|
||||
if (creationActivity != null)
|
||||
throw new InvalidOperationException("More than one enabled ICreationActivity trait: {0} and {1}".F(creationActivity.GetType().Name, ica.GetType().Name));
|
||||
throw new InvalidOperationException($"More than one enabled ICreationActivity trait: {creationActivity.GetType().Name} and {ica.GetType().Name}");
|
||||
|
||||
var activity = ica.GetCreationActivity();
|
||||
if (activity == null)
|
||||
@@ -365,8 +360,7 @@ namespace OpenRA
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var o = obj as Actor;
|
||||
return o != null && Equals(o);
|
||||
return obj is Actor o && Equals(o);
|
||||
}
|
||||
|
||||
public bool Equals(Actor other)
|
||||
@@ -487,7 +481,7 @@ namespace OpenRA
|
||||
health.InflictDamage(this, attacker, damage, false);
|
||||
}
|
||||
|
||||
public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default(BitSet<DamageType>))
|
||||
public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default)
|
||||
{
|
||||
if (Disposed || health == null)
|
||||
return;
|
||||
@@ -528,7 +522,7 @@ namespace OpenRA
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var targetable in Targetables)
|
||||
if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
|
||||
if (targetable.TargetableBy(this, byActor))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -536,11 +530,8 @@ namespace OpenRA
|
||||
|
||||
public IEnumerable<WPos> GetTargetablePositions()
|
||||
{
|
||||
if (staticTargetablePositions != null)
|
||||
return staticTargetablePositions;
|
||||
|
||||
if (enabledTargetablePositions.Any())
|
||||
return enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||
if (EnabledTargetablePositions.Any())
|
||||
return enabledTargetableWorldPositions;
|
||||
|
||||
return new[] { CenterPosition };
|
||||
}
|
||||
@@ -549,7 +540,7 @@ namespace OpenRA
|
||||
|
||||
void UpdateConditionState(string condition, int token, bool isRevoke)
|
||||
{
|
||||
ConditionState conditionState = conditionStates.GetOrAdd(condition);
|
||||
var conditionState = conditionStates.GetOrAdd(condition);
|
||||
|
||||
if (isRevoke)
|
||||
conditionState.Tokens.Remove(token);
|
||||
@@ -589,14 +580,14 @@ namespace OpenRA
|
||||
public int RevokeCondition(int token)
|
||||
{
|
||||
if (!conditionTokens.TryGetValue(token, out var condition))
|
||||
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
|
||||
throw new InvalidOperationException($"Attempting to revoke condition with invalid token {token} for {this}.");
|
||||
|
||||
conditionTokens.Remove(token);
|
||||
UpdateConditionState(condition, token, true);
|
||||
return InvalidConditionToken;
|
||||
}
|
||||
|
||||
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
|
||||
/// <summary>Returns whether the specified token is valid for RevokeCondition.</summary>
|
||||
public bool TokenValid(int token)
|
||||
{
|
||||
return conditionTokens.ContainsKey(token);
|
||||
@@ -609,14 +600,13 @@ namespace OpenRA
|
||||
Lazy<ScriptActorInterface> luaInterface;
|
||||
public void OnScriptBind(ScriptContext context)
|
||||
{
|
||||
if (luaInterface == null)
|
||||
luaInterface = Exts.Lazy(() => new ScriptActorInterface(context, this));
|
||||
luaInterface ??= Exts.Lazy(() => new ScriptActorInterface(context, this));
|
||||
}
|
||||
|
||||
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
|
||||
{
|
||||
get { return luaInterface.Value[runtime, keyValue]; }
|
||||
set { luaInterface.Value[runtime, keyValue] = value; }
|
||||
get => luaInterface.Value[runtime, keyValue];
|
||||
set => luaInterface.Value[runtime, keyValue] = value;
|
||||
}
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
@@ -629,7 +619,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue ToString(LuaRuntime runtime)
|
||||
{
|
||||
return "Actor ({0})".F(this);
|
||||
return $"Actor ({this})";
|
||||
}
|
||||
|
||||
public bool HasScriptProperty(string name)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -16,7 +16,7 @@ using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
|
||||
public readonly struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
|
||||
{
|
||||
// Coordinates are packed in a 32 bit signed int
|
||||
// X and Y are 12 bits (signed): -2048...2047
|
||||
@@ -25,13 +25,13 @@ namespace OpenRA
|
||||
public readonly int Bits;
|
||||
|
||||
// X is padded to MSB, so bit shift does the correct sign extension
|
||||
public int X { get { return Bits >> 20; } }
|
||||
public int X => Bits >> 20;
|
||||
|
||||
// Align Y with a short, cast, then shift the rest of the way
|
||||
// The signed short bit shift does the correct sign extension
|
||||
public int Y { get { return ((short)(Bits >> 4)) >> 4; } }
|
||||
public int Y => ((short)(Bits >> 4)) >> 4;
|
||||
|
||||
public byte Layer { get { return (byte)Bits; } }
|
||||
public byte Layer => (byte)Bits;
|
||||
|
||||
public CPos(int bits) { Bits = bits; }
|
||||
public CPos(int x, int y)
|
||||
@@ -41,7 +41,7 @@ namespace OpenRA
|
||||
Bits = (x & 0xFFF) << 20 | (y & 0xFFF) << 8 | layer;
|
||||
}
|
||||
|
||||
public static readonly CPos Zero = new CPos(0, 0, 0);
|
||||
public static readonly CPos Zero = new(0, 0, 0);
|
||||
|
||||
public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); }
|
||||
|
||||
@@ -56,9 +56,15 @@ namespace OpenRA
|
||||
public override int GetHashCode() { return Bits.GetHashCode(); }
|
||||
|
||||
public bool Equals(CPos other) { return Bits == other.Bits; }
|
||||
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
|
||||
public override bool Equals(object obj) { return obj is CPos cell && Equals(cell); }
|
||||
|
||||
public override string ToString() { return X + "," + Y; }
|
||||
public override string ToString()
|
||||
{
|
||||
if (Layer == 0)
|
||||
return X + "," + Y;
|
||||
|
||||
return X + "," + Y + "," + Layer;
|
||||
}
|
||||
|
||||
public MPos ToMPos(Map map)
|
||||
{
|
||||
@@ -90,7 +96,7 @@ namespace OpenRA
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
throw new LuaException($"Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
}
|
||||
@@ -99,7 +105,7 @@ namespace OpenRA
|
||||
{
|
||||
var rightType = right.WrappedClrType();
|
||||
if (!left.TryGetClrValue(out CPos a))
|
||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
|
||||
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
|
||||
|
||||
if (rightType == typeof(CPos))
|
||||
{
|
||||
@@ -112,7 +118,7 @@ namespace OpenRA
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
|
||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(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)
|
||||
@@ -132,14 +138,11 @@ namespace OpenRA
|
||||
case "X": return X;
|
||||
case "Y": return Y;
|
||||
case "Layer": return Layer;
|
||||
default: throw new LuaException("CPos does not define a member '{0}'".F(key));
|
||||
default: throw new LuaException($"CPos does not define a member '{key}'");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,12 +17,12 @@ using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
|
||||
public readonly struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
|
||||
{
|
||||
public readonly int X, Y;
|
||||
|
||||
public CVec(int x, int y) { X = x; Y = y; }
|
||||
public static readonly CVec Zero = new CVec(0, 0);
|
||||
public static readonly CVec Zero = new(0, 0);
|
||||
|
||||
public static CVec operator +(CVec a, CVec b) { return new CVec(a.X + b.X, a.Y + b.Y); }
|
||||
public static CVec operator -(CVec a, CVec b) { return new CVec(a.X - b.X, a.Y - b.Y); }
|
||||
@@ -42,8 +42,8 @@ namespace OpenRA
|
||||
|
||||
public CVec Sign() { return new CVec(Math.Sign(X), Math.Sign(Y)); }
|
||||
public CVec Abs() { return new CVec(Math.Abs(X), Math.Abs(Y)); }
|
||||
public int LengthSquared { get { return X * X + Y * Y; } }
|
||||
public int Length { get { return Exts.ISqrt(LengthSquared); } }
|
||||
public int LengthSquared => X * X + Y * Y;
|
||||
public int Length => Exts.ISqrt(LengthSquared);
|
||||
|
||||
public CVec Clamp(Rectangle r)
|
||||
{
|
||||
@@ -55,7 +55,7 @@ namespace OpenRA
|
||||
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); }
|
||||
|
||||
public bool Equals(CVec other) { return other == this; }
|
||||
public override bool Equals(object obj) { return obj is CVec && Equals((CVec)obj); }
|
||||
public override bool Equals(object obj) { return obj is CVec vec && Equals(vec); }
|
||||
|
||||
public override string ToString() { return X + "," + Y; }
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace OpenRA
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
throw new LuaException($"Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ namespace OpenRA
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
throw new LuaException($"Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
@@ -110,14 +110,11 @@ namespace OpenRA
|
||||
{
|
||||
case "X": return X;
|
||||
case "Y": return Y;
|
||||
default: throw new LuaException("CVec does not define a member '{0}'".F(key));
|
||||
default: throw new LuaException($"CVec does not define a member '{key}'");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -187,8 +187,10 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to decrypt string with exception: {0}", e);
|
||||
Console.WriteLine("String decryption failed: {0}", e);
|
||||
Log.Write("debug", "Failed to decrypt string with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("String decryption failed:");
|
||||
Console.WriteLine(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -211,8 +213,10 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to sign string with exception: {0}", e);
|
||||
Console.WriteLine("String signing failed: {0}", e);
|
||||
Log.Write("debug", "Failed to sign string with exception");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("String signing failed:");
|
||||
Console.WriteLine(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -235,8 +239,10 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to verify signature with exception: {0}", e);
|
||||
Console.WriteLine("Signature validation failed: {0}", e);
|
||||
Log.Write("debug", "Failed to verify signature with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("Signature validation failed:");
|
||||
Console.WriteLine(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -15,6 +15,6 @@ namespace OpenRA
|
||||
{
|
||||
public class DefaultPlayer : IGlobalModData
|
||||
{
|
||||
public readonly Color Color = Color.FromAhsl(0, 0, 238);
|
||||
public readonly Color Color = Color.FromArgb(0xEE, 0xEE, 0xEE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class Download
|
||||
{
|
||||
readonly object syncObject = new object();
|
||||
WebClient wc;
|
||||
|
||||
public static string FormatErrorMessage(Exception e)
|
||||
{
|
||||
var ex = e as WebException;
|
||||
if (ex == null)
|
||||
return e.Message;
|
||||
|
||||
switch (ex.Status)
|
||||
{
|
||||
case WebExceptionStatus.RequestCanceled:
|
||||
return "Cancelled";
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
return "DNS lookup failed";
|
||||
case WebExceptionStatus.Timeout:
|
||||
return "Connection timeout";
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
return "Cannot connect to remote server";
|
||||
case WebExceptionStatus.ProtocolError:
|
||||
return "File not found on remote server";
|
||||
default:
|
||||
return ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
void EnableTLS12OnWindows()
|
||||
{
|
||||
// Enable TLS 1.2 on Windows: .NET 4.7 on Windows 10 only supports obsolete protocols by default
|
||||
// SecurityProtocolType.Tls12 is not defined in the .NET 4.5 reference dlls used by mono,
|
||||
// so we must use the enum's constant value directly
|
||||
if (Platform.CurrentPlatform == PlatformType.Windows)
|
||||
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
|
||||
}
|
||||
|
||||
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
|
||||
{
|
||||
EnableTLS12OnWindows();
|
||||
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadFileCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
|
||||
wc.DownloadFileAsync(new Uri(url), path);
|
||||
}
|
||||
}
|
||||
|
||||
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
|
||||
{
|
||||
EnableTLS12OnWindows();
|
||||
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadDataCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
|
||||
wc.DownloadDataAsync(new Uri(url));
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeWebClient()
|
||||
{
|
||||
lock (syncObject)
|
||||
{
|
||||
wc.Dispose();
|
||||
wc = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelAsync()
|
||||
{
|
||||
lock (syncObject)
|
||||
wc?.CancelAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Effects
|
||||
{
|
||||
public class AsyncAction : IEffect
|
||||
{
|
||||
Action a;
|
||||
IAsyncResult ar;
|
||||
|
||||
public AsyncAction(IAsyncResult ar, Action a)
|
||||
{
|
||||
this.a = a;
|
||||
this.ar = ar;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (ar.IsCompleted)
|
||||
{
|
||||
world.AddFrameEndTask(w => { w.Remove(this); a(); });
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer r) { yield break; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,7 +17,7 @@ namespace OpenRA.Effects
|
||||
{
|
||||
public class DelayedAction : IEffect
|
||||
{
|
||||
Action a;
|
||||
readonly Action a;
|
||||
int delay;
|
||||
|
||||
public DelayedAction(int delay, Action a)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -41,7 +41,7 @@ namespace OpenRA
|
||||
|
||||
public class ExternalMods : IReadOnlyDictionary<string, ExternalMod>
|
||||
{
|
||||
readonly Dictionary<string, ExternalMod> mods = new Dictionary<string, ExternalMod>();
|
||||
readonly Dictionary<string, ExternalMod> mods = new();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
Sheet CreateSheet()
|
||||
@@ -66,12 +66,7 @@ namespace OpenRA
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
// Read registration metadata from all of them
|
||||
var sources = Enum.GetValues(typeof(SupportDirType))
|
||||
.Cast<SupportDirType>()
|
||||
.Select(t => Platform.GetSupportDir(t))
|
||||
.Distinct();
|
||||
|
||||
foreach (var source in sources)
|
||||
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
@@ -86,8 +81,8 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
Log.Write("debug", $"Failed to parse mod metadata file '{path}'");
|
||||
Log.Write("debug", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +143,7 @@ namespace OpenRA
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
var sources = new List<string>();
|
||||
var sources = new HashSet<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
@@ -167,7 +162,7 @@ namespace OpenRA
|
||||
LoadMod(yaml.Value, forceRegistration: true);
|
||||
|
||||
var lines = new List<MiniYamlNode> { yaml }.ToLines().ToArray();
|
||||
foreach (var source in sources.Distinct())
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
|
||||
@@ -179,35 +174,23 @@ namespace OpenRA
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to register current mod metadata");
|
||||
Log.Write("debug", e.ToString());
|
||||
Log.Write("debug", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes invalid mod registrations:
|
||||
/// * LaunchPath no longer exists
|
||||
/// * LaunchPath and mod id matches the active mod, but the version is different
|
||||
/// * Filename doesn't match internal key
|
||||
/// * Fails to parse as a mod registration
|
||||
/// <list type="bullet">
|
||||
/// <item>LaunchPath no longer exists.</item>
|
||||
/// <item>LaunchPath and mod id matches the active mod, but the version is different.</item>
|
||||
/// <item>Filename doesn't match internal key.</item>
|
||||
/// <item>Fails to parse as a mod registration.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
internal void ClearInvalidRegistrations(ExternalMod activeMod, ModRegistration registration)
|
||||
internal void ClearInvalidRegistrations(ModRegistration registration)
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
// User support dir may be using the modern or legacy value, or overridden by the user
|
||||
// Add all the possibilities and let the .Distinct() below ignore the duplicates
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
|
||||
}
|
||||
|
||||
var activeModKey = ExternalMod.MakeKey(activeMod);
|
||||
foreach (var source in sources.Distinct())
|
||||
foreach (var source in GetSupportDirs(registration))
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
@@ -222,19 +205,16 @@ namespace OpenRA
|
||||
var m = FieldLoader.Load<ExternalMod>(yaml);
|
||||
modKey = ExternalMod.MakeKey(m);
|
||||
|
||||
// Continue to the next entry if it is the active mod (even if the LaunchPath is bogus)
|
||||
if (modKey == activeModKey)
|
||||
continue;
|
||||
|
||||
// Continue to the next entry if this one is valid
|
||||
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey &&
|
||||
!(activeMod != null && m.LaunchPath == activeMod.LaunchPath && m.Id == activeMod.Id && m.Version != activeMod.Version))
|
||||
// HACK: Explicitly invalidate paths to OpenRA.dll to clean up bogus metadata files
|
||||
// that were created after the initial migration from .NET Framework to Core/5.
|
||||
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey && Path.GetExtension(m.LaunchPath) != ".dll")
|
||||
continue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
Log.Write("debug", $"Failed to parse mod metadata file '{path}'");
|
||||
Log.Write("debug", e);
|
||||
}
|
||||
|
||||
// Remove from the ingame mod switcher
|
||||
@@ -245,12 +225,12 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
Log.Write("debug", "Removed invalid mod metadata file '{0}'", path);
|
||||
Log.Write("debug", $"Removed invalid mod metadata file '{path}'");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
Log.Write("debug", $"Failed to remove mod metadata file '{path}'");
|
||||
Log.Write("debug", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,23 +238,10 @@ namespace OpenRA
|
||||
|
||||
internal void Unregister(Manifest mod, ModRegistration registration)
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
// User support dir may be using the modern or legacy value, or overridden by the user
|
||||
// Add all the possibilities and let the .Distinct() below ignore the duplicates
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
|
||||
}
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
mods.Remove(key);
|
||||
|
||||
foreach (var source in sources.Distinct())
|
||||
foreach (var source in GetSupportDirs(registration))
|
||||
{
|
||||
var path = Path.Combine(source, "ModMetadata", key + ".yaml");
|
||||
try
|
||||
@@ -284,16 +251,39 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
Log.Write("debug", $"Failed to remove mod metadata file '{path}'");
|
||||
Log.Write("debug", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ExternalMod this[string key] { get { return mods[key]; } }
|
||||
public int Count { get { return mods.Count; } }
|
||||
public ICollection<string> Keys { get { return mods.Keys; } }
|
||||
public ICollection<ExternalMod> Values { get { return mods.Values; } }
|
||||
static IEnumerable<string> GetSupportDirs(ModRegistration registration)
|
||||
{
|
||||
var sources = new HashSet<string>(4);
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
// User support dir may be using the modern or legacy value, or overridden by the user
|
||||
// Add all the possibilities and let the HashSet ignore the duplicates
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
|
||||
}
|
||||
|
||||
return sources;
|
||||
}
|
||||
|
||||
public ExternalMod this[string key] => mods[key];
|
||||
public int Count => mods.Count;
|
||||
public ICollection<string> Keys => mods.Keys;
|
||||
public ICollection<ExternalMod> Values => mods.Values;
|
||||
|
||||
IEnumerable<string> IReadOnlyDictionary<string, ExternalMod>.Keys => ((IReadOnlyDictionary<string, ExternalMod>)mods).Keys;
|
||||
|
||||
IEnumerable<ExternalMod> IReadOnlyDictionary<string, ExternalMod>.Values => ((IReadOnlyDictionary<string, ExternalMod>)mods).Values;
|
||||
|
||||
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
|
||||
public IEnumerator<KeyValuePair<string, ExternalMod>> GetEnumerator() { return mods.GetEnumerator(); }
|
||||
public bool TryGetValue(string key, out ExternalMod value) { return mods.TryGetValue(key, out value); }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -27,23 +27,12 @@ namespace OpenRA
|
||||
return string.Compare(str.ToUpperInvariant(), str, false) == 0;
|
||||
}
|
||||
|
||||
public static string F(this string fmt, params object[] args)
|
||||
{
|
||||
return string.Format(fmt, args);
|
||||
}
|
||||
|
||||
public static T WithDefault<T>(T def, Func<T> f)
|
||||
{
|
||||
try { return f(); }
|
||||
catch { return def; }
|
||||
}
|
||||
|
||||
public static void Do<T>(this IEnumerable<T> e, Action<T> fn)
|
||||
{
|
||||
foreach (var ee in e)
|
||||
fn(ee);
|
||||
}
|
||||
|
||||
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
|
||||
|
||||
public static IEnumerable<string> GetNamespaces(this Assembly a)
|
||||
@@ -51,21 +40,16 @@ namespace OpenRA
|
||||
return a.GetTypes().Select(t => t.Namespace).Distinct().Where(n => n != null);
|
||||
}
|
||||
|
||||
public static bool HasAttribute<T>(this MemberInfo mi)
|
||||
public static bool HasAttribute<TAttribute>(this MemberInfo mi)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return mi.GetCustomAttributes(typeof(T), true).Length != 0;
|
||||
return Attribute.IsDefined(mi, typeof(TAttribute));
|
||||
}
|
||||
|
||||
public static T[] GetCustomAttributes<T>(this MemberInfo mi, bool inherit)
|
||||
where T : class
|
||||
public static TAttribute[] GetCustomAttributes<TAttribute>(this MemberInfo mi, bool inherit)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return (T[])mi.GetCustomAttributes(typeof(T), inherit);
|
||||
}
|
||||
|
||||
public static T[] GetCustomAttributes<T>(this ParameterInfo mi)
|
||||
where T : class
|
||||
{
|
||||
return (T[])mi.GetCustomAttributes(typeof(T), true);
|
||||
return (TAttribute[])mi.GetCustomAttributes(typeof(TAttribute), inherit);
|
||||
}
|
||||
|
||||
public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
|
||||
@@ -107,7 +91,7 @@ namespace OpenRA
|
||||
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
|
||||
// - the triangles CAB and DAB must have opposite sense
|
||||
// Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other
|
||||
// Assumes that lines are not colinear
|
||||
// Assumes that lines are not collinear
|
||||
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
|
||||
}
|
||||
|
||||
@@ -125,13 +109,23 @@ namespace OpenRA
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
// SAFETY: Dictionary cannot be modified whilst the ref is alive.
|
||||
ref var value = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, k, out var exists);
|
||||
if (!exists)
|
||||
value = v;
|
||||
return value;
|
||||
#else
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = v);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
|
||||
{
|
||||
// Cannot use CollectionsMarshal.GetValueRefOrAddDefault here,
|
||||
// the creation function could mutate the dictionary which would invalidate the ref.
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = createFn(k));
|
||||
return ret;
|
||||
@@ -155,13 +149,13 @@ namespace OpenRA
|
||||
static T Random<T>(IEnumerable<T> ts, MersenneTwister r, bool throws)
|
||||
{
|
||||
var xs = ts as ICollection<T>;
|
||||
xs = xs ?? ts.ToList();
|
||||
xs ??= ts.ToList();
|
||||
if (xs.Count == 0)
|
||||
{
|
||||
if (throws)
|
||||
throw new ArgumentException("Collection must not be empty.", "ts");
|
||||
throw new ArgumentException("Collection must not be empty.", nameof(ts));
|
||||
else
|
||||
return default(T);
|
||||
return default;
|
||||
}
|
||||
else
|
||||
return xs.ElementAt(r.Next(xs.Count));
|
||||
@@ -236,9 +230,9 @@ namespace OpenRA
|
||||
{
|
||||
if (!e.MoveNext())
|
||||
if (throws)
|
||||
throw new ArgumentException("Collection must not be empty.", "ts");
|
||||
throw new ArgumentException("Collection must not be empty.", nameof(ts));
|
||||
else
|
||||
return default(T);
|
||||
return default;
|
||||
t = e.Current;
|
||||
u = selector(t);
|
||||
while (e.MoveNext())
|
||||
@@ -278,7 +272,7 @@ namespace OpenRA
|
||||
public static int ISqrt(int number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
|
||||
{
|
||||
if (number < 0)
|
||||
throw new InvalidOperationException("Attempted to calculate the square root of a negative integer: {0}".F(number));
|
||||
throw new InvalidOperationException($"Attempted to calculate the square root of a negative integer: {number}");
|
||||
|
||||
return (int)ISqrt((uint)number, round);
|
||||
}
|
||||
@@ -319,7 +313,7 @@ namespace OpenRA
|
||||
public static long ISqrt(long number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
|
||||
{
|
||||
if (number < 0)
|
||||
throw new InvalidOperationException("Attempted to calculate the square root of a negative integer: {0}".F(number));
|
||||
throw new InvalidOperationException($"Attempted to calculate the square root of a negative integer: {number}");
|
||||
|
||||
return (long)ISqrt((ulong)number, round);
|
||||
}
|
||||
@@ -357,6 +351,11 @@ namespace OpenRA
|
||||
return root;
|
||||
}
|
||||
|
||||
public static int MultiplyBySqrtTwo(short number)
|
||||
{
|
||||
return number * 46341 / 32768;
|
||||
}
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||
@@ -375,6 +374,11 @@ namespace OpenRA
|
||||
return ts.Concat(moreTs);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Exclude<T>(this IEnumerable<T> ts, params T[] exclusions)
|
||||
{
|
||||
return ts.Except(exclusions);
|
||||
}
|
||||
|
||||
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return new HashSet<T>(source);
|
||||
@@ -392,12 +396,13 @@ namespace OpenRA
|
||||
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
||||
{
|
||||
// Fall back on ToString() if null functions are provided:
|
||||
logKey = logKey ?? (s => s.ToString());
|
||||
logValue = logValue ?? (s => s.ToString());
|
||||
logKey ??= s => s.ToString();
|
||||
logValue ??= s => s.ToString();
|
||||
|
||||
// Try to build a dictionary and log all duplicates found (if any):
|
||||
var dupKeys = new Dictionary<TKey, List<string>>();
|
||||
var d = new Dictionary<TKey, TElement>();
|
||||
var capacity = source is ICollection<TSource> collection ? collection.Count : 0;
|
||||
var d = new Dictionary<TKey, TElement>(capacity);
|
||||
foreach (var item in source)
|
||||
{
|
||||
var key = keySelector(item);
|
||||
@@ -408,29 +413,28 @@ namespace OpenRA
|
||||
continue;
|
||||
|
||||
// Check for a key conflict:
|
||||
if (d.ContainsKey(key))
|
||||
if (!d.TryAdd(key, element))
|
||||
{
|
||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||
{
|
||||
// Log the initial conflicting value already inserted:
|
||||
dupKeyMessages = new List<string>();
|
||||
dupKeyMessages.Add(logValue(d[key]));
|
||||
dupKeyMessages = new List<string>
|
||||
{
|
||||
logValue(d[key])
|
||||
};
|
||||
dupKeys.Add(key, dupKeyMessages);
|
||||
}
|
||||
|
||||
// Log this conflicting value:
|
||||
dupKeyMessages.Add(logValue(element));
|
||||
continue;
|
||||
}
|
||||
|
||||
d.Add(key, element);
|
||||
}
|
||||
|
||||
// If any duplicates were found, throw a descriptive error
|
||||
if (dupKeys.Count > 0)
|
||||
{
|
||||
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => "{0}: [{1}]".F(logKey(p.Key), string.Join(",", p.Value))));
|
||||
var msg = "{0}, duplicate values found for the following keys: {1}".F(debugName, badKeysFormatted);
|
||||
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => $"{logKey(p.Key)}: [{string.Join(",", p.Value)}]"));
|
||||
var msg = $"{debugName}, duplicate values found for the following keys: {badKeysFormatted}";
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
|
||||
@@ -511,8 +515,7 @@ namespace OpenRA
|
||||
|
||||
public static bool IsTraitEnabled<T>(this T trait)
|
||||
{
|
||||
var disabledTrait = trait as IDisabledTrait;
|
||||
return disabledTrait == null || !disabledTrait.IsTraitDisabled;
|
||||
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
|
||||
}
|
||||
|
||||
public static T FirstEnabledTraitOrDefault<T>(this IEnumerable<T> ts)
|
||||
@@ -522,7 +525,7 @@ namespace OpenRA
|
||||
if (t.IsTraitEnabled())
|
||||
return t;
|
||||
|
||||
return default(T);
|
||||
return default;
|
||||
}
|
||||
|
||||
public static T FirstEnabledTraitOrDefault<T>(this T[] ts)
|
||||
@@ -532,8 +535,72 @@ namespace OpenRA
|
||||
if (t.IsTraitEnabled())
|
||||
return t;
|
||||
|
||||
return default(T);
|
||||
return default;
|
||||
}
|
||||
|
||||
public static T FirstEnabledConditionalTraitOrDefault<T>(this IEnumerable<T> ts) where T : IDisabledTrait
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var t in ts)
|
||||
if (!t.IsTraitDisabled)
|
||||
return t;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static T FirstEnabledConditionalTraitOrDefault<T>(this T[] ts) where T : IDisabledTrait
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var t in ts)
|
||||
if (!t.IsTraitDisabled)
|
||||
return t;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static LineSplitEnumerator SplitLines(this string str, char separator)
|
||||
{
|
||||
return new LineSplitEnumerator(str.AsSpan(), separator);
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct LineSplitEnumerator
|
||||
{
|
||||
ReadOnlySpan<char> str;
|
||||
readonly char separator;
|
||||
|
||||
public LineSplitEnumerator(ReadOnlySpan<char> str, char separator)
|
||||
{
|
||||
this.str = str;
|
||||
this.separator = separator;
|
||||
Current = default;
|
||||
}
|
||||
|
||||
public LineSplitEnumerator GetEnumerator() => this;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
var span = str;
|
||||
|
||||
// Reach the end of the string
|
||||
if (span.Length == 0)
|
||||
return false;
|
||||
|
||||
var index = span.IndexOf(separator);
|
||||
if (index == -1)
|
||||
{
|
||||
// The remaining string is an empty string
|
||||
str = ReadOnlySpan<char>.Empty;
|
||||
Current = span;
|
||||
return true;
|
||||
}
|
||||
|
||||
Current = span[..index];
|
||||
str = span[(index + 1)..];
|
||||
return true;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<char> Current { get; private set; }
|
||||
}
|
||||
|
||||
public static class Enum<T>
|
||||
@@ -549,7 +616,7 @@ namespace OpenRA
|
||||
|
||||
if (values.Any(x => !names.Contains(x)))
|
||||
{
|
||||
value = default(T);
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -72,32 +72,14 @@ namespace OpenRA
|
||||
return "";
|
||||
|
||||
var t = v.GetType();
|
||||
|
||||
if (t == typeof(Color))
|
||||
{
|
||||
return ((Color)v).ToString();
|
||||
}
|
||||
|
||||
if (t == typeof(Rectangle))
|
||||
{
|
||||
var r = (Rectangle)v;
|
||||
return "{0},{1},{2},{3}".F(r.X, r.Y, r.Width, r.Height);
|
||||
}
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(BitSet<>))
|
||||
{
|
||||
return ((IEnumerable<string>)v).Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
if (t.IsArray && t.GetArrayRank() == 1)
|
||||
{
|
||||
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
{
|
||||
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
// This is only for documentation generation
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
@@ -112,17 +94,14 @@ namespace OpenRA
|
||||
var formattedKey = FormatValue(key);
|
||||
var formattedValue = FormatValue(value);
|
||||
|
||||
result += "{0}: {1}{2}".F(formattedKey, formattedValue, Environment.NewLine);
|
||||
result += $"{formattedKey}: {formattedValue}{Environment.NewLine}";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Primitives.Cache<,>))
|
||||
return ""; // TODO
|
||||
|
||||
if (t == typeof(DateTime))
|
||||
return ((DateTime)v).ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
|
||||
if (v is DateTime d)
|
||||
return d.ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
|
||||
|
||||
// Try the TypeConverter
|
||||
var conv = TypeDescriptor.GetConverter(t);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,6 +17,7 @@ using System.Net;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Checksum;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
@@ -25,11 +26,14 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public Color[] Palette { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public Color[] Palette { get; }
|
||||
public byte[] Data { get; }
|
||||
public SpriteFrameType Type { get; }
|
||||
public Dictionary<string, string> EmbeddedData = new();
|
||||
|
||||
public int PixelStride => Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4;
|
||||
|
||||
public Png(Stream s)
|
||||
{
|
||||
@@ -38,9 +42,8 @@ namespace OpenRA.FileFormats
|
||||
|
||||
s.Position += 8;
|
||||
var headerParsed = false;
|
||||
var isPaletted = false;
|
||||
var is24Bit = false;
|
||||
var data = new List<byte>();
|
||||
Type = SpriteFrameType.Rgba32;
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -64,19 +67,17 @@ namespace OpenRA.FileFormats
|
||||
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
|
||||
var bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadByte();
|
||||
isPaletted = IsPaletted(bitDepth, colorType);
|
||||
is24Bit = colorType == PngColorType.Color;
|
||||
var colorType = (PngColorType)ms.ReadUInt8();
|
||||
if (IsPaletted(bitDepth, colorType))
|
||||
Type = SpriteFrameType.Indexed8;
|
||||
else if (colorType == PngColorType.Color)
|
||||
Type = SpriteFrameType.Rgb24;
|
||||
|
||||
var dataLength = Width * Height;
|
||||
if (!isPaletted)
|
||||
dataLength *= 4;
|
||||
Data = new byte[Width * Height * PixelStride];
|
||||
|
||||
Data = new byte[dataLength];
|
||||
|
||||
var compression = ms.ReadByte();
|
||||
/*var filter = */ms.ReadByte();
|
||||
var interlace = ms.ReadByte();
|
||||
var compression = ms.ReadUInt8();
|
||||
/*var filter = */ms.ReadUInt8();
|
||||
var interlace = ms.ReadUInt8();
|
||||
|
||||
if (compression != 0)
|
||||
throw new InvalidDataException("Compression method not supported");
|
||||
@@ -94,7 +95,7 @@ namespace OpenRA.FileFormats
|
||||
Palette = new Color[256];
|
||||
for (var i = 0; i < length / 3; i++)
|
||||
{
|
||||
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte();
|
||||
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
|
||||
Palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
@@ -107,7 +108,7 @@ namespace OpenRA.FileFormats
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]);
|
||||
Palette[i] = Color.FromArgb(ms.ReadUInt8(), Palette[i]);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -133,39 +134,63 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
using (var ds = new InflaterInputStream(ns))
|
||||
{
|
||||
var pxStride = isPaletted ? 1 : is24Bit ? 3 : 4;
|
||||
var srcStride = Width * pxStride;
|
||||
var destStride = Width * (isPaletted ? 1 : 4);
|
||||
var pxStride = PixelStride;
|
||||
var rowStride = Width * pxStride;
|
||||
|
||||
var prevLine = new byte[srcStride];
|
||||
Span<byte> prevLine = new byte[rowStride];
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)ds.ReadByte();
|
||||
var line = ds.ReadBytes(srcStride);
|
||||
var filter = (PngFilter)ds.ReadUInt8();
|
||||
ds.ReadBytes(Data, y * rowStride, rowStride);
|
||||
var line = Data.AsSpan(y * rowStride, rowStride);
|
||||
|
||||
for (var i = 0; i < srcStride; i++)
|
||||
line[i] = i < pxStride
|
||||
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0)
|
||||
: UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
||||
|
||||
if (is24Bit)
|
||||
switch (filter)
|
||||
{
|
||||
// Fold alpha channel into RGB data
|
||||
for (var i = 0; i < line.Length / 3; i++)
|
||||
{
|
||||
Array.Copy(line, 3 * i, Data, y * destStride + 4 * i, 3);
|
||||
Data[y * destStride + 4 * i + 3] = 255;
|
||||
}
|
||||
case PngFilter.None:
|
||||
break;
|
||||
case PngFilter.Sub:
|
||||
for (var i = pxStride; i < rowStride; i++)
|
||||
line[i] += line[i - pxStride];
|
||||
break;
|
||||
case PngFilter.Up:
|
||||
for (var i = 0; i < rowStride; i++)
|
||||
line[i] += prevLine[i];
|
||||
break;
|
||||
case PngFilter.Average:
|
||||
for (var i = 0; i < pxStride; i++)
|
||||
line[i] += Average(0, prevLine[i]);
|
||||
for (var i = pxStride; i < rowStride; i++)
|
||||
line[i] += Average(line[i - pxStride], prevLine[i]);
|
||||
break;
|
||||
case PngFilter.Paeth:
|
||||
for (var i = 0; i < pxStride; i++)
|
||||
line[i] += Paeth(0, prevLine[i], 0);
|
||||
for (var i = pxStride; i < rowStride; i++)
|
||||
line[i] += Paeth(line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
else
|
||||
Array.Copy(line, 0, Data, y * destStride, line.Length);
|
||||
|
||||
prevLine = line;
|
||||
}
|
||||
|
||||
static byte Average(byte a, byte b) => (byte)((a + b) / 2);
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPaletted && Palette == null)
|
||||
if (Type == SpriteFrameType.Indexed8 && Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
return;
|
||||
@@ -175,7 +200,7 @@ namespace OpenRA.FileFormats
|
||||
}
|
||||
}
|
||||
|
||||
public Png(byte[] data, int width, int height, Color[] palette = null,
|
||||
public Png(byte[] data, SpriteFrameType type, int width, int height, Color[] palette = null,
|
||||
Dictionary<string, string> embeddedData = null)
|
||||
{
|
||||
var expectLength = width * height;
|
||||
@@ -185,11 +210,46 @@ namespace OpenRA.FileFormats
|
||||
if (data.Length != expectLength)
|
||||
throw new InvalidDataException("Input data does not match expected length");
|
||||
|
||||
Type = type;
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
Palette = palette;
|
||||
Data = data;
|
||||
switch (type)
|
||||
{
|
||||
case SpriteFrameType.Indexed8:
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
{
|
||||
// Data is already in a compatible format
|
||||
Data = data;
|
||||
if (type == SpriteFrameType.Indexed8)
|
||||
Palette = palette;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SpriteFrameType.Bgra32:
|
||||
case SpriteFrameType.Bgr24:
|
||||
{
|
||||
// Convert to big endian
|
||||
Data = new byte[data.Length];
|
||||
var stride = PixelStride;
|
||||
for (var i = 0; i < width * height; i++)
|
||||
{
|
||||
Data[stride * i] = data[stride * i + 2];
|
||||
Data[stride * i + 1] = data[stride * i + 1];
|
||||
Data[stride * i + 2] = data[stride * i + 0];
|
||||
|
||||
if (type == SpriteFrameType.Bgra32)
|
||||
Data[stride * i + 3] = data[stride * i + 3];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"Unhandled SpriteFrameType {type}");
|
||||
}
|
||||
|
||||
if (embeddedData != null)
|
||||
EmbeddedData = embeddedData;
|
||||
@@ -203,34 +263,9 @@ namespace OpenRA.FileFormats
|
||||
return isPng;
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
enum PngColorType : byte { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter : byte { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static bool IsPaletted(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
@@ -246,7 +281,7 @@ namespace OpenRA.FileFormats
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
|
||||
void WritePngChunk(Stream output, string type, Stream input)
|
||||
static void WritePngChunk(Stream output, string type, Stream input)
|
||||
{
|
||||
input.Position = 0;
|
||||
|
||||
@@ -274,9 +309,8 @@ namespace OpenRA.FileFormats
|
||||
header.Write(IPAddress.HostToNetworkOrder(Height));
|
||||
header.WriteByte(8); // Bit depth
|
||||
|
||||
var colorType = Palette != null
|
||||
? PngColorType.Indexed | PngColorType.Color
|
||||
: PngColorType.Color | PngColorType.Alpha;
|
||||
var colorType = Type == SpriteFrameType.Indexed8 ? PngColorType.Indexed | PngColorType.Color :
|
||||
Type == SpriteFrameType.Rgb24 ? PngColorType.Color : PngColorType.Color | PngColorType.Alpha;
|
||||
header.WriteByte((byte)colorType);
|
||||
|
||||
header.WriteByte(0); // Compression
|
||||
@@ -286,7 +320,7 @@ namespace OpenRA.FileFormats
|
||||
WritePngChunk(output, "IHDR", header);
|
||||
}
|
||||
|
||||
bool alphaPalette = false;
|
||||
var alphaPalette = false;
|
||||
if (Palette != null)
|
||||
{
|
||||
using (var palette = new MemoryStream())
|
||||
@@ -318,12 +352,12 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
using (var compressed = new DeflaterOutputStream(data))
|
||||
{
|
||||
var stride = Width * (Palette != null ? 1 : 4);
|
||||
var rowStride = Width * PixelStride;
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
// Write uncompressed scanlines for simplicity
|
||||
compressed.WriteByte(0);
|
||||
compressed.Write(Data, y * stride, stride);
|
||||
compressed.Write(Data, y * rowStride, rowStride);
|
||||
}
|
||||
|
||||
compressed.Flush();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.FileFormats
|
||||
public ReplayMetadata(GameInformation info)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException("info");
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
|
||||
GameInfo = info;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace OpenRA.FileFormats
|
||||
// Read version
|
||||
var version = fs.ReadInt32();
|
||||
if (version != MetaVersion)
|
||||
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
|
||||
throw new NotSupportedException($"Metadata version {version} is not supported");
|
||||
|
||||
// Read game info (max 100K limit as a safeguard against corrupted files)
|
||||
var data = fs.ReadString(Encoding.UTF8, 1024 * 100);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -28,17 +28,17 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public class FileSystem : IReadOnlyFileSystem
|
||||
{
|
||||
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
|
||||
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
|
||||
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
|
||||
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys;
|
||||
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new();
|
||||
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new();
|
||||
readonly string modID;
|
||||
|
||||
// Mod packages that should not be disposed
|
||||
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
|
||||
readonly List<IReadOnlyPackage> modPackages = new();
|
||||
readonly IReadOnlyDictionary<string, Manifest> installedMods;
|
||||
readonly IPackageLoader[] packageLoaders;
|
||||
|
||||
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||
Cache<string, List<IReadOnlyPackage>> fileIndex = new(_ => new List<IReadOnlyPackage>());
|
||||
|
||||
public FileSystem(string modID, IReadOnlyDictionary<string, Manifest> installedMods, IPackageLoader[] packageLoaders)
|
||||
{
|
||||
@@ -63,7 +63,7 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
// Raw directories are the easiest and one of the most common cases, so try these first
|
||||
var resolvedPath = Platform.ResolvePath(filename);
|
||||
if (!resolvedPath.Contains("|") && Directory.Exists(resolvedPath))
|
||||
if (!resolvedPath.Contains('|') && Directory.Exists(resolvedPath))
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Children of another package require special handling
|
||||
@@ -85,17 +85,17 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
if (optional)
|
||||
name = name.Substring(1);
|
||||
name = name[1..];
|
||||
|
||||
try
|
||||
{
|
||||
IReadOnlyPackage package;
|
||||
if (name.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
name = name.Substring(1);
|
||||
name = name[1..];
|
||||
|
||||
if (!installedMods.TryGetValue(name, out var mod))
|
||||
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
|
||||
throw new InvalidOperationException($"Could not load mod '{name}'. Available mods: {installedMods.Keys.JoinWith(", ")}");
|
||||
|
||||
package = mod.Package;
|
||||
modPackages.Add(package);
|
||||
@@ -104,7 +104,7 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
package = OpenPackage(name);
|
||||
if (package == null)
|
||||
throw new InvalidOperationException("Could not open package '{0}', file not found or its format is not supported.".F(name));
|
||||
throw new InvalidOperationException($"Could not open package '{name}', file not found or its format is not supported.");
|
||||
}
|
||||
|
||||
Mount(package, explicitName);
|
||||
@@ -203,7 +203,7 @@ namespace OpenRA.FileSystem
|
||||
public Stream Open(string filename)
|
||||
{
|
||||
if (!TryOpen(filename, out var s))
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
throw new FileNotFoundException($"File not found: {filename}", filename);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -211,9 +211,9 @@ namespace OpenRA.FileSystem
|
||||
public bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && explicitMounts.TryGetValue(path.Substring(0, explicitSplit), out package))
|
||||
if (explicitSplit > 0 && explicitMounts.TryGetValue(path[..explicitSplit], out package))
|
||||
{
|
||||
filename = path.Substring(explicitSplit + 1);
|
||||
filename = path[(explicitSplit + 1)..];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -228,9 +228,9 @@ namespace OpenRA.FileSystem
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
|
||||
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
|
||||
if (s != null)
|
||||
return true;
|
||||
}
|
||||
@@ -263,15 +263,15 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
|
||||
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
if (explicitPackage.Contains(filename[(explicitSplit + 1)..]))
|
||||
return true;
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given filename references an external mod via an explicit mount
|
||||
/// Returns true if the given filename references an external mod via an explicit mount.
|
||||
/// </summary>
|
||||
public bool IsExternalModFile(string filename)
|
||||
{
|
||||
@@ -279,7 +279,7 @@ namespace OpenRA.FileSystem
|
||||
if (explicitSplit < 0)
|
||||
return false;
|
||||
|
||||
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
if (!explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||
return false;
|
||||
|
||||
if (installedMods[modID].Package == explicitPackage)
|
||||
@@ -297,8 +297,8 @@ namespace OpenRA.FileSystem
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && !path.StartsWith("^"))
|
||||
{
|
||||
var parent = path.Substring(0, explicitSplit);
|
||||
var filename = path.Substring(explicitSplit + 1);
|
||||
var parent = path[..explicitSplit];
|
||||
var filename = path[(explicitSplit + 1)..];
|
||||
|
||||
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
|
||||
if (parentPath == null)
|
||||
@@ -306,10 +306,10 @@ namespace OpenRA.FileSystem
|
||||
|
||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out var mod))
|
||||
if (!installedMods.TryGetValue(parentPath[1..], out var mod))
|
||||
return null;
|
||||
|
||||
if (!(mod.Package is Folder))
|
||||
if (mod.Package is not Folder)
|
||||
return null;
|
||||
|
||||
path = Path.Combine(mod.Package.Name, filename);
|
||||
@@ -322,6 +322,28 @@ namespace OpenRA.FileSystem
|
||||
return File.Exists(resolvedPath) ? resolvedPath : null;
|
||||
}
|
||||
|
||||
public static string ResolveCaseInsensitivePath(string path)
|
||||
{
|
||||
var resolved = Path.GetPathRoot(path);
|
||||
|
||||
if (resolved == null)
|
||||
return null;
|
||||
|
||||
foreach (var name in path[resolved.Length..].Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
|
||||
{
|
||||
// Filter out paths of the form /foo/bar/./baz
|
||||
if (name == ".")
|
||||
continue;
|
||||
|
||||
resolved = Directory.GetFileSystemEntries(resolved).FirstOrDefault(e => e.Equals(Path.Combine(resolved, name), StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (resolved == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public string GetPrefix(IReadOnlyPackage package)
|
||||
{
|
||||
return explicitMounts.ContainsValue(package) ? explicitMounts.First(f => f.Value == package).Key : null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -18,24 +18,22 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class Folder : IReadWritePackage
|
||||
{
|
||||
readonly string path;
|
||||
public string Name { get; }
|
||||
|
||||
public Folder(string path)
|
||||
{
|
||||
this.path = path;
|
||||
Name = path;
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public string Name { get { return path; } }
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
// Order may vary on different file systems and it matters for hashing.
|
||||
return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)
|
||||
.Concat(Directory.GetDirectories(path))
|
||||
return Directory.GetFiles(Name, "*", SearchOption.TopDirectoryOnly)
|
||||
.Concat(Directory.GetDirectories(Name))
|
||||
.Select(Path.GetFileName)
|
||||
.OrderBy(f => f);
|
||||
}
|
||||
@@ -43,14 +41,14 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
try { return File.OpenRead(Path.Combine(path, filename)); }
|
||||
try { return File.OpenRead(Path.Combine(Name, filename)); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
var combined = Path.Combine(path, filename);
|
||||
return combined.StartsWith(path, StringComparison.Ordinal) && File.Exists(combined);
|
||||
var combined = Path.Combine(Name, filename);
|
||||
return combined.StartsWith(Name, StringComparison.Ordinal) && File.Exists(combined);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
@@ -82,7 +80,7 @@ namespace OpenRA.FileSystem
|
||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||
// full parent path too. We need to be careful to not add a second path
|
||||
// prefix to these hacked packages.
|
||||
var filePath = filename.StartsWith(path) ? filename : Path.Combine(path, filename);
|
||||
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
||||
using (var s = File.Create(filePath))
|
||||
@@ -96,7 +94,7 @@ namespace OpenRA.FileSystem
|
||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||
// full parent path too. We need to be careful to not add a second path
|
||||
// prefix to these hacked packages.
|
||||
var filePath = filename.StartsWith(path) ? filename : Path.Combine(path, filename);
|
||||
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
||||
if (Directory.Exists(filePath))
|
||||
Directory.Delete(filePath, true);
|
||||
else if (File.Exists(filePath))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -19,7 +19,7 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
public class ZipFileLoader : IPackageLoader
|
||||
{
|
||||
static readonly string[] Extensions = { ".zip", ".oramap" };
|
||||
const uint ZipSignature = 0x04034b50;
|
||||
|
||||
class ReadOnlyZipFile : IReadOnlyPackage
|
||||
{
|
||||
@@ -55,7 +55,8 @@ namespace OpenRA.FileSystem
|
||||
get
|
||||
{
|
||||
foreach (ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
if (entry.IsFile)
|
||||
yield return entry.Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
||||
{
|
||||
readonly MemoryStream pkgStream = new MemoryStream();
|
||||
readonly MemoryStream pkgStream = new();
|
||||
|
||||
public ReadWriteZipFile(string filename, bool create = false)
|
||||
{
|
||||
@@ -141,23 +142,22 @@ namespace OpenRA.FileSystem
|
||||
|
||||
sealed class ZipFolder : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get { return path; } }
|
||||
public ReadOnlyZipFile Parent { get; private set; }
|
||||
readonly string path;
|
||||
public string Name { get; }
|
||||
public ReadOnlyZipFile Parent { get; }
|
||||
|
||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||
{
|
||||
if (path.EndsWith("/", StringComparison.Ordinal))
|
||||
path = path.Substring(0, path.Length - 1);
|
||||
path = path[..^1];
|
||||
|
||||
Name = path;
|
||||
Parent = parent;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
// Zip files use '/' as a path separator
|
||||
return Parent.GetStream(path + '/' + filename);
|
||||
return Parent.GetStream(Name + '/' + filename);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
@@ -166,9 +166,9 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
foreach (var entry in Parent.Contents)
|
||||
{
|
||||
if (entry.StartsWith(path, StringComparison.Ordinal) && entry != path)
|
||||
if (entry.StartsWith(Name, StringComparison.Ordinal) && entry != Name)
|
||||
{
|
||||
var filename = entry.Substring(path.Length + 1);
|
||||
var filename = entry[(Name.Length + 1)..];
|
||||
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
|
||||
if (dirLevels == 1)
|
||||
yield return filename;
|
||||
@@ -179,18 +179,18 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return Parent.Contains(path + '/' + filename);
|
||||
return Parent.Contains(Name + '/' + filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
return Parent.OpenPackage(path + '/' + filename, context);
|
||||
return Parent.OpenPackage(Name + '/' + filename, context);
|
||||
}
|
||||
|
||||
public void Dispose() { /* nothing to do */ }
|
||||
}
|
||||
|
||||
class StaticStreamDataSource : IStaticDataSource
|
||||
sealed class StaticStreamDataSource : IStaticDataSource
|
||||
{
|
||||
readonly Stream s;
|
||||
public StaticStreamDataSource(Stream s)
|
||||
@@ -206,7 +206,10 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
{
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
var readSignature = s.ReadUInt32();
|
||||
s.Position -= 4;
|
||||
|
||||
if (readSignature != ZipSignature)
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
@@ -218,10 +221,13 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package)
|
||||
{
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
using (var s = File.OpenRead(filename))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
if (s.ReadUInt32() != ZipSignature)
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
package = new ReadWriteZipFile(filename);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA
|
||||
|
||||
public class Fonts : IGlobalModData
|
||||
{
|
||||
[FieldLoader.LoadUsing("LoadFonts")]
|
||||
[FieldLoader.LoadUsing(nameof(LoadFonts))]
|
||||
public readonly Dictionary<string, FontData> FontList;
|
||||
|
||||
static object LoadFonts(MiniYaml y)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -16,10 +16,8 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
@@ -31,8 +29,9 @@ namespace OpenRA
|
||||
{
|
||||
public static class Game
|
||||
{
|
||||
public const int NetTickScale = 3; // 120 ms net tick for 40 ms local tick
|
||||
public const int Timestep = 40;
|
||||
[TranslationReference("filename")]
|
||||
const string SavedScreenshot = "notification-saved-screenshot";
|
||||
|
||||
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
|
||||
|
||||
public static InstalledMods Mods { get; private set; }
|
||||
@@ -42,13 +41,14 @@ namespace OpenRA
|
||||
public static Settings Settings;
|
||||
public static CursorManager Cursor;
|
||||
public static bool HideCursor;
|
||||
|
||||
static WorldRenderer worldRenderer;
|
||||
static string modLaunchWrapper;
|
||||
|
||||
internal static OrderManager OrderManager;
|
||||
static Server.Server server;
|
||||
|
||||
public static MersenneTwister CosmeticRandom = new MersenneTwister(); // not synced
|
||||
public static MersenneTwister CosmeticRandom = new(); // not synced
|
||||
|
||||
public static Renderer Renderer;
|
||||
public static Sound Sound;
|
||||
@@ -56,7 +56,6 @@ namespace OpenRA
|
||||
public static string EngineVersion { get; private set; }
|
||||
public static LocalPlayerProfile LocalPlayerProfile;
|
||||
|
||||
static Task discoverNat;
|
||||
static bool takeScreenshot = false;
|
||||
static Benchmark benchmark = null;
|
||||
|
||||
@@ -64,12 +63,18 @@ namespace OpenRA
|
||||
|
||||
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
|
||||
{
|
||||
var connection = new NetworkConnection(endpoint);
|
||||
var newConnection = new NetworkConnection(endpoint);
|
||||
if (recordReplay)
|
||||
connection.StartRecording(() => { return TimestampedFilename(); });
|
||||
newConnection.StartRecording(() => TimestampedFilename());
|
||||
|
||||
var om = new OrderManager(endpoint, password, connection);
|
||||
var om = new OrderManager(newConnection);
|
||||
JoinInner(om);
|
||||
CurrentServerSettings.Password = password;
|
||||
CurrentServerSettings.Target = endpoint;
|
||||
|
||||
lastConnectionState = ConnectionState.PreConnecting;
|
||||
ConnectionStateChanged(OrderManager, password, newConnection);
|
||||
|
||||
return om;
|
||||
}
|
||||
|
||||
@@ -81,34 +86,56 @@ namespace OpenRA
|
||||
|
||||
static void JoinInner(OrderManager om)
|
||||
{
|
||||
OrderManager?.Dispose();
|
||||
// Refresh TextNotificationsManager before the game starts.
|
||||
TextNotificationsManager.Clear();
|
||||
|
||||
// HACK: The shellmap World and OrderManager are owned by the main menu's WorldRenderer instead of Game.
|
||||
// This allows us to switch Game.OrderManager from the shellmap to the new network connection when joining
|
||||
// a lobby, while keeping the OrderManager that runs the shellmap intact.
|
||||
// A matching check in World.Dispose (which is called by WorldRenderer.Dispose) makes sure that we dispose
|
||||
// the shellmap's OM when a lobby game actually starts.
|
||||
if (OrderManager?.World == null || OrderManager.World.Type != WorldType.Shellmap)
|
||||
OrderManager?.Dispose();
|
||||
|
||||
OrderManager = om;
|
||||
lastConnectionState = ConnectionState.PreConnecting;
|
||||
ConnectionStateChanged(OrderManager);
|
||||
}
|
||||
|
||||
public static void JoinReplay(string replayFile)
|
||||
{
|
||||
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
|
||||
JoinInner(new OrderManager(new ReplayConnection(replayFile)));
|
||||
}
|
||||
|
||||
static void JoinLocal()
|
||||
{
|
||||
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
|
||||
JoinInner(new OrderManager(new EchoConnection()));
|
||||
|
||||
// Add a spectator client for the local player
|
||||
// On the shellmap this player is controlling the map via scripted orders
|
||||
OrderManager.LobbyInfo.Clients.Add(new Session.Client
|
||||
{
|
||||
Index = OrderManager.Connection.LocalClientId,
|
||||
Name = Settings.Player.Name,
|
||||
PreferredColor = Settings.Player.Color,
|
||||
Color = Settings.Player.Color,
|
||||
Faction = "Random",
|
||||
SpawnPoint = 0,
|
||||
Team = 0,
|
||||
State = Session.ClientState.Ready
|
||||
});
|
||||
}
|
||||
|
||||
// More accurate replacement for Environment.TickCount
|
||||
static Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
public static long RunTime { get { return stopwatch.ElapsedMilliseconds; } }
|
||||
static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
|
||||
public static long RunTime => Stopwatch.ElapsedMilliseconds;
|
||||
|
||||
public static int RenderFrame = 0;
|
||||
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
|
||||
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
|
||||
public static int NetFrameNumber => OrderManager.NetFrameNumber;
|
||||
public static int LocalTick => OrderManager.LocalFrameNumber;
|
||||
|
||||
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
|
||||
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
|
||||
public static event Action<OrderManager, string, NetworkConnection> ConnectionStateChanged = (om, pass, conn) => { };
|
||||
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
|
||||
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
|
||||
public static int LocalClientId => OrderManager.Connection.LocalClientId;
|
||||
|
||||
public static void RemoteDirectConnect(ConnectionTarget endpoint)
|
||||
{
|
||||
@@ -160,12 +187,8 @@ namespace OpenRA
|
||||
Cursor.SetCursor(null);
|
||||
BeforeGameStart();
|
||||
|
||||
Map map;
|
||||
|
||||
using (new PerfTimer("PrepareMap"))
|
||||
map = ModData.PrepareMap(mapUID);
|
||||
using (new PerfTimer("NewWorld"))
|
||||
OrderManager.World = new World(ModData, map, OrderManager, type);
|
||||
OrderManager.World = new World(mapUID, ModData, OrderManager, type);
|
||||
|
||||
OrderManager.World.GameOver += FinishBenchmark;
|
||||
|
||||
@@ -186,11 +209,9 @@ namespace OpenRA
|
||||
Ui.MouseFocusWidget = null;
|
||||
Ui.KeyboardFocusWidget = null;
|
||||
|
||||
OrderManager.LocalFrameNumber = 0;
|
||||
OrderManager.LastTickTime = RunTime;
|
||||
OrderManager.StartGame();
|
||||
worldRenderer.RefreshPalette();
|
||||
Cursor.SetCursor("default");
|
||||
Cursor.SetCursor(ChromeMetrics.Get<string>("DefaultCursor"));
|
||||
|
||||
// Now loading is completed, now is the ideal time to run a GC and compact the LOH.
|
||||
// - All the temporary garbage created during loading can be collected.
|
||||
@@ -206,15 +227,26 @@ namespace OpenRA
|
||||
public static void RestartGame()
|
||||
{
|
||||
var replay = OrderManager.Connection as ReplayConnection;
|
||||
var replayName = replay != null ? replay.Filename : null;
|
||||
var replayName = replay?.Filename;
|
||||
var lobbyInfo = OrderManager.LobbyInfo;
|
||||
|
||||
// Reseed the RNG so this isn't an exact repeat of the last game
|
||||
lobbyInfo.GlobalSettings.RandomSeed = CosmeticRandom.Next();
|
||||
|
||||
// Note: the map may have been changed on disk outside the game, changing its UID.
|
||||
// Use the updated UID if we have tracked the update instead of failing.
|
||||
lobbyInfo.GlobalSettings.Map = ModData.MapCache.GetUpdatedMap(lobbyInfo.GlobalSettings.Map);
|
||||
if (lobbyInfo.GlobalSettings.Map == null)
|
||||
{
|
||||
Disconnect();
|
||||
Ui.ResetAll();
|
||||
LoadShellMap();
|
||||
return;
|
||||
}
|
||||
|
||||
var orders = new[]
|
||||
{
|
||||
Order.Command("sync_lobby {0}".F(lobbyInfo.Serialize())),
|
||||
Order.Command($"sync_lobby {lobbyInfo.Serialize()}"),
|
||||
Order.Command("startgame")
|
||||
};
|
||||
|
||||
@@ -233,15 +265,14 @@ namespace OpenRA
|
||||
{
|
||||
OrderManager om = null;
|
||||
|
||||
Action lobbyReady = null;
|
||||
lobbyReady = () =>
|
||||
void LobbyReady()
|
||||
{
|
||||
LobbyInfoChanged -= lobbyReady;
|
||||
LobbyInfoChanged -= LobbyReady;
|
||||
foreach (var o in setupOrders)
|
||||
om.IssueOrder(o);
|
||||
};
|
||||
}
|
||||
|
||||
LobbyInfoChanged += lobbyReady;
|
||||
LobbyInfoChanged += LobbyReady;
|
||||
|
||||
om = JoinServer(CreateLocalServer(mapUID), "");
|
||||
}
|
||||
@@ -284,7 +315,7 @@ namespace OpenRA
|
||||
if (!string.IsNullOrEmpty(supportDirArg))
|
||||
Platform.OverrideSupportDir(supportDirArg);
|
||||
|
||||
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
|
||||
Console.WriteLine($"Platform is {Platform.CurrentPlatform} ({Platform.CurrentArchitecture})");
|
||||
|
||||
// Load the engine version as early as possible so it can be written to exception logs
|
||||
try
|
||||
@@ -296,12 +327,13 @@ namespace OpenRA
|
||||
if (string.IsNullOrEmpty(EngineVersion))
|
||||
EngineVersion = "Unknown";
|
||||
|
||||
Console.WriteLine("Engine version is {0}", EngineVersion);
|
||||
Console.WriteLine($"Engine version is {EngineVersion}");
|
||||
Console.WriteLine($"Runtime: {Platform.RuntimeVersion}");
|
||||
|
||||
// Special case handling of Game.Mod argument: if it matches a real filesystem path
|
||||
// then we use this to override the mod search path, and replace it with the mod id
|
||||
var modID = args.GetValue("Game.Mod", null);
|
||||
var explicitModPaths = new string[0];
|
||||
var explicitModPaths = Array.Empty<string>();
|
||||
if (modID != null && (File.Exists(modID) || Directory.Exists(modID)))
|
||||
{
|
||||
explicitModPaths = new[] { modID };
|
||||
@@ -329,9 +361,17 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
|
||||
var assembly = Assembly.LoadFile(rendererPath);
|
||||
|
||||
#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.");
|
||||
|
||||
@@ -343,7 +383,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("graphics", "{0}", e);
|
||||
Log.Write("graphics", $"{e}");
|
||||
Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
|
||||
|
||||
Renderer?.Dispose();
|
||||
@@ -352,8 +392,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.Server.DiscoverNatDevices)
|
||||
discoverNat = UPnP.DiscoverNatDevices(Settings.Server.NatDiscoveryTimeout);
|
||||
Nat.Initialize();
|
||||
|
||||
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
|
||||
var modSearchPaths = modSearchArg != null ?
|
||||
@@ -363,7 +402,7 @@ namespace OpenRA
|
||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||
Console.WriteLine("Internal mods:");
|
||||
foreach (var mod in Mods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
|
||||
Console.WriteLine($"\t{mod.Key}: {mod.Value.Metadata.Title} ({mod.Value.Metadata.Version})");
|
||||
|
||||
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
||||
|
||||
@@ -377,26 +416,18 @@ namespace OpenRA
|
||||
// Sanitize input from platform-specific launchers
|
||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath.Substring(1, launchPath.Length - 2);
|
||||
launchPath = launchPath[1..^1];
|
||||
|
||||
if (launchPath == null)
|
||||
{
|
||||
// When launching the assembly directly we must propagate the Engine.EngineDir argument if defined
|
||||
// Platform-specific launchers are expected to manage this internally.
|
||||
launchPath = Assembly.GetEntryAssembly().Location;
|
||||
if (!string.IsNullOrEmpty(engineDirArg))
|
||||
launchArgs.Add("Engine.EngineDir=\"" + engineDirArg + "\"");
|
||||
}
|
||||
// Metadata registration requires an explicit launch path
|
||||
if (launchPath != null)
|
||||
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
|
||||
|
||||
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
|
||||
|
||||
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
|
||||
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
|
||||
ExternalMods.ClearInvalidRegistrations(ModRegistration.User);
|
||||
}
|
||||
|
||||
Console.WriteLine("External mods:");
|
||||
foreach (var mod in ExternalMods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
||||
Console.WriteLine($"\t{mod.Key}: {mod.Value.Title} ({mod.Value.Version})");
|
||||
|
||||
InitializeMod(modID, args);
|
||||
}
|
||||
@@ -405,7 +436,7 @@ namespace OpenRA
|
||||
{
|
||||
// Clear static state if we have switched mods
|
||||
LobbyInfoChanged = () => { };
|
||||
ConnectionStateChanged = om => { };
|
||||
ConnectionStateChanged = (om, p, conn) => { };
|
||||
BeforeGameStart = () => { };
|
||||
OnRemoteDirectConnect = endpoint => { };
|
||||
delayedActions = new ActionQueue();
|
||||
@@ -429,9 +460,9 @@ namespace OpenRA
|
||||
throw new InvalidOperationException("Game.Mod argument missing.");
|
||||
|
||||
if (!Mods.ContainsKey(mod))
|
||||
throw new InvalidOperationException("Unknown or invalid mod '{0}'.".F(mod));
|
||||
throw new InvalidOperationException($"Unknown or invalid mod '{mod}'.");
|
||||
|
||||
Console.WriteLine("Loading mod: {0}", mod);
|
||||
Console.WriteLine($"Loading mod: {mod}");
|
||||
|
||||
Sound.StopVideo();
|
||||
|
||||
@@ -442,19 +473,22 @@ namespace OpenRA
|
||||
if (!ModData.LoadScreen.BeforeLoad())
|
||||
return;
|
||||
|
||||
using (new PerfTimer("LoadMaps"))
|
||||
ModData.MapCache.LoadMaps();
|
||||
|
||||
ModData.InitializeLoaders(ModData.DefaultFileSystem);
|
||||
Renderer.InitializeFonts(ModData);
|
||||
|
||||
using (new PerfTimer("LoadMaps"))
|
||||
ModData.MapCache.LoadMaps();
|
||||
|
||||
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
|
||||
Renderer.InitializeDepthBuffer(grid);
|
||||
|
||||
Cursor?.Dispose();
|
||||
|
||||
Cursor = new CursorManager(ModData.CursorProvider);
|
||||
|
||||
var metadata = ModData.Manifest.Metadata;
|
||||
if (!string.IsNullOrEmpty(metadata.WindowTitle))
|
||||
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
|
||||
|
||||
PerfHistory.Items["render"].HasNormalTick = false;
|
||||
PerfHistory.Items["batches"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_world"].HasNormalTick = false;
|
||||
@@ -464,31 +498,18 @@ namespace OpenRA
|
||||
|
||||
JoinLocal();
|
||||
|
||||
try
|
||||
{
|
||||
discoverNat?.Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("NAT discovery failed: {0}", e.Message);
|
||||
Log.Write("nat", e.ToString());
|
||||
}
|
||||
|
||||
ChromeMetrics.TryGet("ChatMessageColor", out chatMessageColor);
|
||||
ChromeMetrics.TryGet("SystemMessageColor", out systemMessageColor);
|
||||
|
||||
ModData.LoadScreen.StartGame(args);
|
||||
}
|
||||
|
||||
public static void LoadEditor(string mapUid)
|
||||
{
|
||||
JoinLocal();
|
||||
StartGame(mapUid, WorldType.Editor);
|
||||
}
|
||||
|
||||
public static void LoadShellMap()
|
||||
{
|
||||
var shellmap = ChooseShellmap();
|
||||
|
||||
using (new PerfTimer("StartGame"))
|
||||
{
|
||||
StartGame(shellmap, WorldType.Shellmap);
|
||||
@@ -542,9 +563,8 @@ namespace OpenRA
|
||||
|
||||
// Note: These delayed actions should only be used by widgets or disposing objects
|
||||
// - things that depend on a particular world should be queuing them on the world actor.
|
||||
static volatile ActionQueue delayedActions = new ActionQueue();
|
||||
static Color systemMessageColor = Color.White;
|
||||
static Color chatMessageColor = Color.White;
|
||||
static volatile ActionQueue delayedActions = new();
|
||||
|
||||
public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); }
|
||||
public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
|
||||
|
||||
@@ -561,7 +581,7 @@ namespace OpenRA
|
||||
Log.Write("debug", "Taking screenshot " + path);
|
||||
|
||||
Renderer.SaveScreenshot(path);
|
||||
Debug("Saved screenshot " + filename);
|
||||
TextNotificationsManager.Debug(TranslationProvider.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,62 +591,38 @@ namespace OpenRA
|
||||
|
||||
var world = orderManager.World;
|
||||
|
||||
var uiTickDelta = tick - Ui.LastTickTime;
|
||||
if (uiTickDelta >= Timestep)
|
||||
if (Ui.LastTickTime.ShouldAdvance(tick))
|
||||
{
|
||||
// Explained below for the world tick calculation
|
||||
var integralTickTimestep = (uiTickDelta / Timestep) * Timestep;
|
||||
Ui.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : Timestep;
|
||||
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, Ui.Tick);
|
||||
Ui.LastTickTime.AdvanceTickTime(tick);
|
||||
Sync.RunUnsynced(world, Ui.Tick);
|
||||
Cursor.Tick();
|
||||
}
|
||||
|
||||
var worldTimestep = world == null ? Timestep : world.IsLoadingGameSave ? 1 : world.Timestep;
|
||||
var worldTickDelta = tick - orderManager.LastTickTime;
|
||||
if (worldTimestep != 0 && worldTickDelta >= worldTimestep)
|
||||
if (orderManager.LastTickTime.ShouldAdvance(tick))
|
||||
{
|
||||
using (new PerfSample("tick_time"))
|
||||
{
|
||||
// Tick the world to advance the world time to match real time:
|
||||
// If dt < TickJankThreshold then we should try and catch up by repeatedly ticking
|
||||
// If dt >= TickJankThreshold then we should accept the jank and progress at the normal rate
|
||||
// dt is rounded down to an integer tick count in order to preserve fractional tick components.
|
||||
var integralTickTimestep = (worldTickDelta / worldTimestep) * worldTimestep;
|
||||
orderManager.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : worldTimestep;
|
||||
orderManager.LastTickTime.AdvanceTickTime(tick);
|
||||
|
||||
Sound.Tick();
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, orderManager.TickImmediate);
|
||||
|
||||
Sync.RunUnsynced(world, orderManager.TickImmediate);
|
||||
|
||||
if (world == null)
|
||||
return;
|
||||
|
||||
var isNetTick = LocalTick % NetTickScale == 0;
|
||||
|
||||
if (!isNetTick || orderManager.IsReadyForNextFrame)
|
||||
if (orderManager.TryTick())
|
||||
{
|
||||
++orderManager.LocalFrameNumber;
|
||||
|
||||
Log.Write("debug", "--Tick: {0} ({1})", LocalTick, isNetTick ? "net" : "local");
|
||||
|
||||
if (isNetTick)
|
||||
orderManager.Tick();
|
||||
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () =>
|
||||
{
|
||||
world.OrderGenerator.Tick(world);
|
||||
});
|
||||
Sync.RunUnsynced(world, () => world.OrderGenerator.Tick(world));
|
||||
|
||||
world.Tick();
|
||||
|
||||
PerfHistory.Tick();
|
||||
}
|
||||
else if (orderManager.NetFrameNumber == 0)
|
||||
orderManager.LastTickTime = RunTime;
|
||||
|
||||
// Wait until we have done our first world Tick before TickRendering
|
||||
if (orderManager.LocalFrameNumber > 0)
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
|
||||
Sync.RunUnsynced(world, () => world.TickRender(worldRenderer));
|
||||
}
|
||||
|
||||
benchmark?.Tick(LocalTick);
|
||||
@@ -637,10 +633,10 @@ namespace OpenRA
|
||||
{
|
||||
PerformDelayedActions();
|
||||
|
||||
if (OrderManager.Connection.ConnectionState != lastConnectionState)
|
||||
if (OrderManager.Connection is NetworkConnection nc && nc.ConnectionState != lastConnectionState)
|
||||
{
|
||||
lastConnectionState = OrderManager.Connection.ConnectionState;
|
||||
ConnectionStateChanged(OrderManager);
|
||||
lastConnectionState = nc.ConnectionState;
|
||||
ConnectionStateChanged(OrderManager, null, nc);
|
||||
}
|
||||
|
||||
InnerLogicTick(OrderManager);
|
||||
@@ -777,13 +773,20 @@ namespace OpenRA
|
||||
|
||||
while (state == RunStatus.Running)
|
||||
{
|
||||
// Ideal time between logic updates. Timestep = 0 means the game is paused
|
||||
// but we still call LogicTick() because it handles pausing internally.
|
||||
var logicInterval = worldRenderer != null && worldRenderer.World.Timestep != 0 ? worldRenderer.World.Timestep : Timestep;
|
||||
var logicInterval = Ui.Timestep;
|
||||
var logicWorld = worldRenderer?.World;
|
||||
|
||||
// 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))
|
||||
logicInterval = logicWorld == OrderManager.World ? OrderManager.SuggestedTimestep : logicWorld.Timestep;
|
||||
|
||||
// Ideal time between screen updates
|
||||
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
|
||||
var renderInterval = 1000 / maxFramerate;
|
||||
var renderInterval = logicInterval;
|
||||
if (!Settings.Graphics.CapFramerateToGameFps)
|
||||
{
|
||||
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
|
||||
renderInterval = 1000 / maxFramerate;
|
||||
}
|
||||
|
||||
// Tick as fast as possible while restoring game saves, capping rendering at 5 FPS
|
||||
if (OrderManager.World != null && OrderManager.World.IsLoadingGameSave)
|
||||
@@ -817,8 +820,7 @@ namespace OpenRA
|
||||
|
||||
var haveSomeTimeUntilNextLogic = now < nextLogic;
|
||||
var isTimeToRender = now >= nextRender;
|
||||
|
||||
if ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender)
|
||||
if (!Renderer.WindowIsSuspended && ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender))
|
||||
{
|
||||
nextRender = now + renderInterval;
|
||||
|
||||
@@ -833,6 +835,19 @@ namespace OpenRA
|
||||
RenderTick();
|
||||
renderBeforeNextTick = false;
|
||||
}
|
||||
|
||||
// Simulate a render tick if it was time to render but we skip actually rendering
|
||||
if (Renderer.WindowIsSuspended && isTimeToRender)
|
||||
{
|
||||
// Make sure that nextUpdate is set to a proper minimum interval
|
||||
nextRender = now + renderInterval;
|
||||
|
||||
// Still process SDL events to allow a restore to come through
|
||||
Renderer.Window.PumpInput(new NullInputHandler());
|
||||
|
||||
// Ensure that we still logic tick despite not rendering
|
||||
renderBeforeNextTick = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep((int)(nextUpdate - now));
|
||||
@@ -874,26 +889,6 @@ namespace OpenRA
|
||||
state = RunStatus.Success;
|
||||
}
|
||||
|
||||
public static void AddSystemLine(string text)
|
||||
{
|
||||
AddSystemLine("Battlefield Control", text);
|
||||
}
|
||||
|
||||
public static void AddSystemLine(string name, string text)
|
||||
{
|
||||
OrderManager.AddChatLine(name, systemMessageColor, text, systemMessageColor);
|
||||
}
|
||||
|
||||
public static void AddChatLine(string name, Color nameColor, string text)
|
||||
{
|
||||
OrderManager.AddChatLine(name, nameColor, text, chatMessageColor);
|
||||
}
|
||||
|
||||
public static void Debug(string s, params object[] args)
|
||||
{
|
||||
AddSystemLine("Debug", string.Format(s, args));
|
||||
}
|
||||
|
||||
public static void Disconnect()
|
||||
{
|
||||
OrderManager.World?.TraitDict.PrintReport();
|
||||
@@ -965,16 +960,13 @@ namespace OpenRA
|
||||
{
|
||||
var orders = new List<Order>
|
||||
{
|
||||
Order.Command("option gamespeed {0}".F("default")),
|
||||
Order.Command("state {0}".F(Session.ClientState.Ready))
|
||||
Order.Command("option gamespeed default"),
|
||||
Order.Command($"state {Session.ClientState.Ready}")
|
||||
};
|
||||
|
||||
var path = Platform.ResolvePath(launchMap);
|
||||
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap) ??
|
||||
ModData.MapCache.SingleOrDefault(m => m.Package.Name == path);
|
||||
|
||||
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.Package.Name) == launchMap);
|
||||
if (map == null)
|
||||
throw new InvalidOperationException("Could not find map '{0}'.".F(launchMap));
|
||||
throw new ArgumentException($"Could not find map '{launchMap}'.");
|
||||
|
||||
CreateAndStartLocalServer(map.Uid, orders);
|
||||
}
|
||||
@@ -988,4 +980,11 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CurrentServerSettings
|
||||
{
|
||||
public static string Password;
|
||||
public static ConnectionTarget Target;
|
||||
public static ExternalMod ServerExternalMod;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -33,12 +33,13 @@ namespace OpenRA
|
||||
public DateTime EndTimeUtc;
|
||||
|
||||
/// <summary>Gets the game's duration, from the time the game started until the replay recording stopped.</summary>
|
||||
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
|
||||
public IList<Player> Players { get; private set; }
|
||||
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
|
||||
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
|
||||
public TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero;
|
||||
|
||||
public IList<Player> Players { get; }
|
||||
public HashSet<int> DisabledSpawnPoints = new();
|
||||
public MapPreview MapPreview => Game.ModData.MapCache[MapUid];
|
||||
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
|
||||
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
|
||||
public bool IsSinglePlayer => HumanPlayers.Count() == 1;
|
||||
|
||||
readonly Dictionary<OpenRA.Player, Player> playersByRuntime;
|
||||
|
||||
@@ -75,7 +76,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException)
|
||||
{
|
||||
Log.Write("debug", "GameInformation deserialized invalid MiniYaml:\n{0}".F(data));
|
||||
Log.Write("debug", $"GameInformation deserialized invalid MiniYaml:\n{data}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +89,7 @@ namespace OpenRA
|
||||
};
|
||||
|
||||
for (var i = 0; i < Players.Count; i++)
|
||||
nodes.Add(new MiniYamlNode("Player@{0}".F(i), FieldSaver.Save(Players[i])));
|
||||
nodes.Add(new MiniYamlNode($"Player@{i}", FieldSaver.Save(Players[i])));
|
||||
|
||||
return nodes.WriteToString();
|
||||
}
|
||||
@@ -97,10 +98,10 @@ namespace OpenRA
|
||||
public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo)
|
||||
{
|
||||
if (runtimePlayer == null)
|
||||
throw new ArgumentNullException("runtimePlayer");
|
||||
throw new ArgumentNullException(nameof(runtimePlayer));
|
||||
|
||||
if (lobbyInfo == null)
|
||||
throw new ArgumentNullException("lobbyInfo");
|
||||
throw new ArgumentNullException(nameof(lobbyInfo));
|
||||
|
||||
// We don't care about spectators and map players
|
||||
if (runtimePlayer.NonCombatant || !runtimePlayer.Playable)
|
||||
@@ -123,6 +124,7 @@ namespace OpenRA
|
||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||
Color = runtimePlayer.Color,
|
||||
Team = client.Team,
|
||||
Handicap = client.Handicap,
|
||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
|
||||
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
|
||||
@@ -166,6 +168,7 @@ namespace OpenRA
|
||||
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
|
||||
public int Team;
|
||||
public int SpawnPoint;
|
||||
public int Handicap;
|
||||
|
||||
/// <summary>True if the faction was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomFaction;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -32,7 +32,7 @@ namespace OpenRA
|
||||
/// You can remove inherited traits by adding a - in front of them as in -TraitName: to inherit everything, but this trait.
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
readonly TypeDictionary traits = new TypeDictionary();
|
||||
readonly TypeDictionary traits = new();
|
||||
List<TraitInfo> constructOrderCache = null;
|
||||
|
||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||
@@ -61,7 +61,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException("Actor type {0}: {1}".F(name, e.Message));
|
||||
throw new YamlException($"Actor type {name}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@ namespace OpenRA
|
||||
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException("Junk value `{0}` on trait node {1}"
|
||||
.F(my.Value, traitName));
|
||||
throw new YamlException($"Junk value `{my.Value}` on trait node {traitName}");
|
||||
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// ObjectCreator will only return null to signal us to abort here if the linter is running
|
||||
@@ -89,7 +88,7 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
if (traitInstance.Length > 1)
|
||||
info.GetType().GetField("InstanceName").SetValue(info, traitInstance[1]);
|
||||
info.GetType().GetField(nameof(info.InstanceName)).SetValue(info, traitInstance[1]);
|
||||
|
||||
FieldLoader.Load(info, my);
|
||||
}
|
||||
@@ -111,39 +110,51 @@ namespace OpenRA
|
||||
{
|
||||
Trait = i,
|
||||
Type = i.GetType(),
|
||||
Dependencies = PrerequisitesOf(i).ToList()
|
||||
Dependencies = PrerequisitesOf(i).ToList(),
|
||||
OptionalDependencies = OptionalPrerequisitesOf(i).ToList()
|
||||
}).ToList();
|
||||
|
||||
var resolved = source.Where(s => !s.Dependencies.Any()).ToList();
|
||||
var unresolved = source.Except(resolved);
|
||||
var resolved = source.Where(s => s.Dependencies.Count == 0 && s.OptionalDependencies.Count == 0).ToList();
|
||||
var unresolved = source.ToHashSet();
|
||||
unresolved.ExceptWith(resolved);
|
||||
|
||||
var testResolve = new Func<Type, Type, bool>((a, b) => a == b || a.IsAssignableFrom(b));
|
||||
static bool AreResolvable(Type a, Type b) => a.IsAssignableFrom(b);
|
||||
|
||||
// This query detects which unresolved traits can be immediately resolved as all their direct dependencies are met.
|
||||
var more = unresolved.Where(u =>
|
||||
u.Dependencies.All(d => // To be resolvable, all dependencies must be satisfied according to the following conditions:
|
||||
resolved.Exists(r => testResolve(d, r.Type)) && // There must exist a resolved trait that meets the dependency.
|
||||
!unresolved.Any(u1 => testResolve(d, u1.Type)))); // All matching traits that meet this dependency must be resolved first.
|
||||
resolved.Exists(r => AreResolvable(d, r.Type)) && // There must exist a resolved trait that meets the dependency.
|
||||
!unresolved.Any(u1 => AreResolvable(d, u1.Type))) && // All matching traits that meet this dependency must be resolved first.
|
||||
u.OptionalDependencies.All(d => // To be resolvable, all optional dependencies must be satisfied according to the following condition:
|
||||
!unresolved.Any(u1 => AreResolvable(d, u1.Type)))); // All matching traits that meet this optional dependencies must be resolved first.
|
||||
|
||||
// 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.
|
||||
while (more.Any())
|
||||
resolved.AddRange(more);
|
||||
|
||||
if (unresolved.Any())
|
||||
var readyToResolve = more.ToList();
|
||||
while (readyToResolve.Count != 0)
|
||||
{
|
||||
var exceptionString = "ActorInfo(\"" + Name + "\") failed to initialize because of the following:\r\n";
|
||||
var missing = unresolved.SelectMany(u => u.Dependencies.Where(d => !source.Any(s => testResolve(d, s.Type)))).Distinct();
|
||||
resolved.AddRange(readyToResolve);
|
||||
unresolved.ExceptWith(readyToResolve);
|
||||
readyToResolve.Clear();
|
||||
readyToResolve.AddRange(more);
|
||||
}
|
||||
|
||||
exceptionString += "Missing:\r\n";
|
||||
if (unresolved.Count != 0)
|
||||
{
|
||||
var exceptionString = "ActorInfo(\"" + Name + "\") failed to initialize because of the following:\n";
|
||||
var missing = unresolved.SelectMany(u => u.Dependencies.Where(d => !source.Any(s => AreResolvable(d, s.Type)))).Distinct();
|
||||
|
||||
exceptionString += "Missing:\n";
|
||||
foreach (var m in missing)
|
||||
exceptionString += m + " \r\n";
|
||||
exceptionString += m + " \n";
|
||||
|
||||
exceptionString += "Unresolved:\r\n";
|
||||
exceptionString += "Unresolved:\n";
|
||||
foreach (var u in unresolved)
|
||||
{
|
||||
var deps = u.Dependencies.Where(d => !resolved.Exists(r => r.Type == d));
|
||||
exceptionString += u.Type + ": { " + string.Join(", ", deps) + " }\r\n";
|
||||
var optDeps = u.OptionalDependencies.Where(d => !resolved.Exists(r => r.Type == d));
|
||||
var allDeps = string.Join(", ", deps.Select(o => o.ToString()).Concat(optDeps.Select(o => $"[{o}]")));
|
||||
exceptionString += $"{u.Type}: {{ {allDeps} }}\n";
|
||||
}
|
||||
|
||||
throw new YamlException(exceptionString);
|
||||
@@ -162,6 +173,15 @@ namespace OpenRA
|
||||
.Select(t => t.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> OptionalPrerequisitesOf(TraitInfo info)
|
||||
{
|
||||
return info
|
||||
.GetType()
|
||||
.GetInterfaces()
|
||||
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(NotBefore<>))
|
||||
.Select(t => t.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
||||
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
||||
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -29,14 +28,14 @@ namespace OpenRA.GameRules
|
||||
Title = value.Value;
|
||||
|
||||
var nd = value.ToDictionary();
|
||||
if (nd.ContainsKey("Hidden"))
|
||||
bool.TryParse(nd["Hidden"].Value, out Hidden);
|
||||
if (nd.TryGetValue("Hidden", out var yaml))
|
||||
bool.TryParse(yaml.Value, out Hidden);
|
||||
|
||||
if (nd.ContainsKey("VolumeModifier"))
|
||||
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", nd["VolumeModifier"].Value);
|
||||
if (nd.TryGetValue("VolumeModifier", out yaml))
|
||||
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", yaml.Value);
|
||||
|
||||
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
|
||||
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
|
||||
var ext = nd.TryGetValue("Extension", out yaml) ? yaml.Value : "aud";
|
||||
Filename = (nd.TryGetValue("Filename", out yaml) ? yaml.Value : key) + "." + ext;
|
||||
}
|
||||
|
||||
public void Load(IReadOnlyFileSystem fileSystem)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -15,20 +15,18 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class Ruleset
|
||||
{
|
||||
public readonly IReadOnlyDictionary<string, ActorInfo> Actors;
|
||||
public readonly ActorInfoDictionary Actors;
|
||||
public readonly IReadOnlyDictionary<string, WeaponInfo> Weapons;
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Voices;
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
|
||||
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
|
||||
public readonly TileSet TileSet;
|
||||
public readonly SequenceProvider Sequences;
|
||||
public readonly ITerrainInfo TerrainInfo;
|
||||
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
|
||||
|
||||
public Ruleset(
|
||||
@@ -37,17 +35,15 @@ namespace OpenRA
|
||||
IReadOnlyDictionary<string, SoundInfo> voices,
|
||||
IReadOnlyDictionary<string, SoundInfo> notifications,
|
||||
IReadOnlyDictionary<string, MusicInfo> music,
|
||||
TileSet tileSet,
|
||||
SequenceProvider sequences,
|
||||
ITerrainInfo terrainInfo,
|
||||
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
{
|
||||
Actors = actors;
|
||||
Actors = new ActorInfoDictionary(actors);
|
||||
Weapons = weapons;
|
||||
Voices = voices;
|
||||
Notifications = notifications;
|
||||
Music = music;
|
||||
TileSet = tileSet;
|
||||
Sequences = sequences;
|
||||
TerrainInfo = terrainInfo;
|
||||
ModelSequences = modelSequences;
|
||||
|
||||
foreach (var a in Actors.Values)
|
||||
@@ -60,15 +56,14 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException("Actor type {0}: {1}".F(a.Name, e.Message));
|
||||
throw new YamlException($"Actor type {a.Name}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var weapon in Weapons)
|
||||
{
|
||||
var projectileLoaded = weapon.Value.Projectile as IRulesetLoaded<WeaponInfo>;
|
||||
if (projectileLoaded != null)
|
||||
if (weapon.Value.Projectile is IRulesetLoaded<WeaponInfo> projectileLoaded)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -76,14 +71,13 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException("Projectile type {0}: {1}".F(weapon.Key, e.Message));
|
||||
throw new YamlException($"Projectile type {weapon.Key}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var warhead in weapon.Value.Warheads)
|
||||
{
|
||||
var cacher = warhead as IRulesetLoaded<WeaponInfo>;
|
||||
if (cacher != null)
|
||||
if (warhead is IRulesetLoaded<WeaponInfo> cacher)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -91,7 +85,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException("Weapon type {0}: {1}".F(weapon.Key, e.Message));
|
||||
throw new YamlException($"Weapon type {weapon.Key}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,7 +111,7 @@ namespace OpenRA
|
||||
if (filterNode != null)
|
||||
yamlNodes = yamlNodes.Where(k => !filterNode(k));
|
||||
|
||||
return new ReadOnlyDictionary<string, T>(yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">"));
|
||||
return yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">");
|
||||
}
|
||||
|
||||
public static Ruleset LoadDefaults(ModData modData)
|
||||
@@ -126,14 +120,14 @@ namespace OpenRA
|
||||
var fs = modData.DefaultFileSystem;
|
||||
|
||||
Ruleset ruleset = null;
|
||||
Action f = () =>
|
||||
void LoadRuleset()
|
||||
{
|
||||
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
|
||||
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
|
||||
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
k => new WeaponInfo(k.Value));
|
||||
|
||||
var voices = MergeOrDefault("Manifest,Voices", fs, m.Voices, null, null,
|
||||
k => new SoundInfo(k.Value));
|
||||
@@ -147,15 +141,15 @@ namespace OpenRA
|
||||
var modelSequences = MergeOrDefault("Manifest,ModelSequences", fs, m.ModelSequences, null, null,
|
||||
k => k);
|
||||
|
||||
// The default ruleset does not include a preferred tileset or sequence set
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null, modelSequences);
|
||||
};
|
||||
// The default ruleset does not include a preferred tileset
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, modelSequences);
|
||||
}
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
{
|
||||
modData.HandleLoadingProgress();
|
||||
|
||||
var loader = new Task(f);
|
||||
var loader = new Task(LoadRuleset);
|
||||
loader.Start();
|
||||
|
||||
// Animate the loadscreen while we wait
|
||||
@@ -163,7 +157,7 @@ namespace OpenRA
|
||||
modData.HandleLoadingProgress();
|
||||
}
|
||||
else
|
||||
f();
|
||||
LoadRuleset();
|
||||
|
||||
return ruleset;
|
||||
}
|
||||
@@ -171,28 +165,27 @@ namespace OpenRA
|
||||
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
|
||||
{
|
||||
var dr = modData.DefaultRules;
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
var sequences = modData.DefaultSequences[tileSet];
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences, dr.ModelSequences);
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, dr.ModelSequences);
|
||||
}
|
||||
|
||||
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
|
||||
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications,
|
||||
MiniYaml mapMusic, MiniYaml mapSequences, MiniYaml mapModelSequences)
|
||||
MiniYaml mapMusic, MiniYaml mapModelSequences)
|
||||
{
|
||||
var m = modData.Manifest;
|
||||
var dr = modData.DefaultRules;
|
||||
|
||||
Ruleset ruleset = null;
|
||||
Action f = () =>
|
||||
void LoadRuleset()
|
||||
{
|
||||
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
|
||||
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
|
||||
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
k => new WeaponInfo(k.Value));
|
||||
|
||||
var voices = MergeOrDefault("Voices", fileSystem, m.Voices, mapVoices, dr.Voices,
|
||||
k => new SoundInfo(k.Value));
|
||||
@@ -203,26 +196,22 @@ namespace OpenRA
|
||||
var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music,
|
||||
k => new MusicInfo(k.Key, k.Value));
|
||||
|
||||
// TODO: Add support for merging custom tileset modifications
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
|
||||
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
|
||||
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
|
||||
new SequenceProvider(fileSystem, modData, tileSet, mapSequences);
|
||||
// TODO: Add support for merging custom terrain modifications
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
|
||||
var modelSequences = dr.ModelSequences;
|
||||
if (mapModelSequences != null)
|
||||
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
|
||||
k => k);
|
||||
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences, modelSequences);
|
||||
};
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, modelSequences);
|
||||
}
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
{
|
||||
modData.HandleLoadingProgress();
|
||||
|
||||
var loader = new Task(f);
|
||||
var loader = new Task(LoadRuleset);
|
||||
loader.Start();
|
||||
|
||||
// Animate the loadscreen while we wait
|
||||
@@ -230,14 +219,14 @@ namespace OpenRA
|
||||
modData.HandleLoadingProgress();
|
||||
}
|
||||
else
|
||||
f();
|
||||
LoadRuleset();
|
||||
|
||||
return ruleset;
|
||||
}
|
||||
|
||||
static bool AnyCustomYaml(MiniYaml yaml)
|
||||
{
|
||||
return yaml != null && (yaml.Value != null || yaml.Nodes.Any());
|
||||
return yaml != null && (yaml.Value != null || yaml.Nodes.Count > 0);
|
||||
}
|
||||
|
||||
static bool AnyFlaggedTraits(ModData modData, List<MiniYamlNode> actors)
|
||||
@@ -250,12 +239,12 @@ namespace OpenRA
|
||||
{
|
||||
var traitName = traitNode.Key.Split('@')[0];
|
||||
var traitType = modData.ObjectCreator.FindType(traitName + "Info");
|
||||
if (traitType != null && traitType.GetInterface("ILobbyCustomRulesIgnore") == null)
|
||||
if (traitType != null && traitType.GetInterface(nameof(ILobbyCustomRulesIgnore)) == null)
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Write("debug", "Error in AnyFlaggedTraits\r\n" + ex.ToString());
|
||||
Log.Write("debug", "Error in AnyFlaggedTraits\n" + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,14 +17,14 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
public class SoundInfo
|
||||
{
|
||||
public readonly Dictionary<string, string[]> Variants = new Dictionary<string, string[]>();
|
||||
public readonly Dictionary<string, string[]> Prefixes = new Dictionary<string, string[]>();
|
||||
public readonly Dictionary<string, string[]> Voices = new Dictionary<string, string[]>();
|
||||
public readonly Dictionary<string, string[]> Notifications = new Dictionary<string, string[]>();
|
||||
public readonly Dictionary<string, string[]> Variants = new();
|
||||
public readonly Dictionary<string, string[]> Prefixes = new();
|
||||
public readonly Dictionary<string, string[]> Voices = new();
|
||||
public readonly Dictionary<string, string[]> Notifications = new();
|
||||
public readonly string DefaultVariant = ".aud";
|
||||
public readonly string DefaultPrefix = "";
|
||||
public readonly HashSet<string> DisableVariants = new HashSet<string>();
|
||||
public readonly HashSet<string> DisablePrefixes = new HashSet<string>();
|
||||
public readonly HashSet<string> DisableVariants = new();
|
||||
public readonly HashSet<string> DisablePrefixes = new();
|
||||
|
||||
public readonly Lazy<Dictionary<string, SoundPool>> VoicePools;
|
||||
public readonly Lazy<Dictionary<string, SoundPool>> NotificationsPools;
|
||||
@@ -33,23 +33,28 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
FieldLoader.Load(this, y);
|
||||
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, a.Value)));
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, SoundPool.DefaultInterruptType, a.Value)));
|
||||
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
|
||||
}
|
||||
|
||||
Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||
{
|
||||
var ret = new Dictionary<string, SoundPool>();
|
||||
var classifiction = y.Nodes.First(x => x.Key == key);
|
||||
foreach (var t in classifiction.Value.Nodes)
|
||||
{
|
||||
var volumeModifier = 1f;
|
||||
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == "VolumeModifier");
|
||||
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.VolumeModifier));
|
||||
if (volumeModifierNode != null)
|
||||
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
||||
|
||||
var interruptType = SoundPool.DefaultInterruptType;
|
||||
var interruptTypeNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.InterruptType));
|
||||
if (interruptTypeNode != null)
|
||||
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
||||
|
||||
var names = FieldLoader.GetValue<string[]>(t.Key, t.Value.Value);
|
||||
var sp = new SoundPool(volumeModifier, names);
|
||||
var sp = new SoundPool(volumeModifier, interruptType, names);
|
||||
ret.Add(t.Key, sp);
|
||||
}
|
||||
|
||||
@@ -59,13 +64,17 @@ namespace OpenRA.GameRules
|
||||
|
||||
public class SoundPool
|
||||
{
|
||||
public enum InterruptType { DoNotPlay, Interrupt, Overlap }
|
||||
public const InterruptType DefaultInterruptType = InterruptType.DoNotPlay;
|
||||
public readonly float VolumeModifier;
|
||||
public readonly InterruptType Type;
|
||||
readonly string[] clips;
|
||||
readonly List<string> liveclips = new List<string>();
|
||||
readonly List<string> liveclips = new();
|
||||
|
||||
public SoundPool(float volumeModifier, params string[] clips)
|
||||
public SoundPool(float volumeModifier, InterruptType interruptType, params string[] clips)
|
||||
{
|
||||
VolumeModifier = volumeModifier;
|
||||
Type = interruptType;
|
||||
this.clips = clips;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -36,7 +36,7 @@ namespace OpenRA.GameRules
|
||||
public class WarheadArgs
|
||||
{
|
||||
public WeaponInfo Weapon;
|
||||
public int[] DamageModifiers = { };
|
||||
public int[] DamageModifiers = Array.Empty<int>();
|
||||
public WPos? Source;
|
||||
public WRot ImpactOrientation;
|
||||
public WPos ImpactPosition;
|
||||
@@ -99,17 +99,20 @@ namespace OpenRA.GameRules
|
||||
[Desc("Number of shots in a single ammo magazine.")]
|
||||
public readonly int Burst = 1;
|
||||
|
||||
[Desc("Can this weapon target the attacker itself?")]
|
||||
public readonly bool CanTargetSelf = false;
|
||||
|
||||
[Desc("What types of targets are affected.")]
|
||||
public readonly BitSet<TargetableType> ValidTargets = new BitSet<TargetableType>("Ground", "Water");
|
||||
public readonly BitSet<TargetableType> ValidTargets = new("Ground", "Water");
|
||||
|
||||
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
|
||||
public readonly BitSet<TargetableType> InvalidTargets;
|
||||
|
||||
static readonly BitSet<TargetableType> TargetTypeAir = new BitSet<TargetableType>("Air");
|
||||
static readonly BitSet<TargetableType> TargetTypeAir = new("Air");
|
||||
|
||||
[Desc("If weapon is not directly targeting an actor and targeted position is above this altitude,",
|
||||
"the weapon will ignore terrain target types and only check TargetTypeAir for validity.")]
|
||||
public readonly WDist AirThreshold = new WDist(128);
|
||||
public readonly WDist AirThreshold = new(128);
|
||||
|
||||
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
|
||||
"If multiple entries, their number needs to match Burst - 1.")]
|
||||
@@ -121,13 +124,18 @@ namespace OpenRA.GameRules
|
||||
[Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")]
|
||||
public readonly bool TargetActorCenter = false;
|
||||
|
||||
[FieldLoader.LoadUsing("LoadProjectile")]
|
||||
[FieldLoader.LoadUsing(nameof(LoadProjectile))]
|
||||
public readonly IProjectileInfo Projectile;
|
||||
|
||||
[FieldLoader.LoadUsing("LoadWarheads")]
|
||||
public readonly List<IWarhead> Warheads = new List<IWarhead>();
|
||||
[FieldLoader.LoadUsing(nameof(LoadWarheads))]
|
||||
public readonly List<IWarhead> Warheads = new();
|
||||
|
||||
public WeaponInfo(string name, MiniYaml content)
|
||||
/// <summary>
|
||||
/// This constructor is used solely for documentation generation.
|
||||
/// </summary>
|
||||
public WeaponInfo() { }
|
||||
|
||||
public WeaponInfo(MiniYaml content)
|
||||
{
|
||||
// Resolve any weapon-level yaml inheritance or removals
|
||||
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
||||
@@ -139,7 +147,11 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
||||
return null;
|
||||
|
||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
FieldLoader.Load(ret, proj);
|
||||
return ret;
|
||||
}
|
||||
@@ -150,6 +162,9 @@ namespace OpenRA.GameRules
|
||||
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
|
||||
{
|
||||
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
|
||||
if (ret == null)
|
||||
continue;
|
||||
|
||||
FieldLoader.Load(ret, node.Value);
|
||||
retList.Add(ret);
|
||||
}
|
||||
@@ -194,29 +209,24 @@ namespace OpenRA.GameRules
|
||||
/// <summary>Checks if the weapon is valid against (can target) the actor.</summary>
|
||||
public bool IsValidAgainst(Actor victim, Actor firedBy)
|
||||
{
|
||||
var targetTypes = victim.GetEnabledTargetTypes();
|
||||
|
||||
if (!IsValidTarget(targetTypes))
|
||||
if (!CanTargetSelf && victim == firedBy)
|
||||
return false;
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var warhead in Warheads)
|
||||
if (warhead.IsValidAgainst(victim, firedBy))
|
||||
return true;
|
||||
var targetTypes = victim.GetEnabledTargetTypes();
|
||||
|
||||
return false;
|
||||
return IsValidTarget(targetTypes);
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the frozen actor.</summary>
|
||||
public bool IsValidAgainst(FrozenActor victim, Actor firedBy)
|
||||
{
|
||||
if (!IsValidTarget(victim.TargetTypes))
|
||||
if (!victim.IsValid)
|
||||
return false;
|
||||
|
||||
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
|
||||
if (!CanTargetSelf && victim.Actor == firedBy)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return IsValidTarget(victim.TargetTypes);
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target.</summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,27 +10,50 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class GameSpeed
|
||||
{
|
||||
[Translate]
|
||||
public readonly string Name = "Default";
|
||||
public readonly int Timestep = 40;
|
||||
public readonly int OrderLatency = 3;
|
||||
[TranslationReference]
|
||||
[FieldLoader.Require]
|
||||
public readonly string Name;
|
||||
|
||||
[FieldLoader.Require]
|
||||
public readonly int Timestep;
|
||||
|
||||
[FieldLoader.Require]
|
||||
public readonly int OrderLatency;
|
||||
}
|
||||
|
||||
public class GameSpeeds : IGlobalModData
|
||||
{
|
||||
[FieldLoader.LoadUsing("LoadSpeeds")]
|
||||
[FieldLoader.Require]
|
||||
public readonly string DefaultSpeed;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadSpeeds))]
|
||||
public readonly Dictionary<string, GameSpeed> Speeds;
|
||||
|
||||
static object LoadSpeeds(MiniYaml y)
|
||||
{
|
||||
var ret = new Dictionary<string, GameSpeed>();
|
||||
foreach (var node in y.Nodes)
|
||||
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
|
||||
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
|
||||
if (speedsNode == null)
|
||||
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
|
||||
|
||||
foreach (var node in speedsNode.Value.Nodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
{
|
||||
var label = e.Missing.Length > 1 ? "Required properties missing" : "Required property missing";
|
||||
throw new YamlException($"Error parsing GameSpeed {node.Key}: {label}: {e.Missing.JoinWith(", ")}");
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA.Graphics
|
||||
public string Name { get; private set; }
|
||||
public bool IsDecoration { get; set; }
|
||||
|
||||
readonly SequenceProvider sequenceProvider;
|
||||
readonly SequenceSet sequences;
|
||||
readonly Func<WAngle> facingFunc;
|
||||
readonly Func<bool> paused;
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
|
||||
bool backwards;
|
||||
bool tickAlways;
|
||||
int timeUntilNextFrame;
|
||||
Action tickFunc = () => { };
|
||||
Action tickFunc;
|
||||
|
||||
public Animation(World world, string name)
|
||||
: this(world, name, () => WAngle.Zero) { }
|
||||
@@ -43,48 +43,57 @@ namespace OpenRA.Graphics
|
||||
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||
{
|
||||
sequenceProvider = world.Map.Rules.Sequences;
|
||||
sequences = world.Map.Sequences;
|
||||
Name = name.ToLowerInvariant();
|
||||
this.facingFunc = facingFunc;
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
public int CurrentFrame { get { return backwards ? CurrentSequence.Length - frame - 1 : frame; } }
|
||||
public Sprite Image { get { return CurrentSequence.GetSprite(CurrentFrame, facingFunc()); } }
|
||||
public int CurrentFrame => backwards ? CurrentSequence.Length - frame - 1 : frame;
|
||||
|
||||
public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
public Sprite Image => CurrentSequence.GetSprite(CurrentFrame, facingFunc());
|
||||
|
||||
public IRenderable[] Render(WPos pos, in WVec offset, int zOffset, PaletteReference palette)
|
||||
{
|
||||
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration, CurrentSequence.IgnoreWorldTint);
|
||||
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
|
||||
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
||||
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,
|
||||
rotation);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
if (shadow != null)
|
||||
{
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true, CurrentSequence.IgnoreWorldTint);
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
||||
true, rotation);
|
||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||
}
|
||||
|
||||
return new IRenderable[] { imageRenderable };
|
||||
}
|
||||
|
||||
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, in WVec offset, int zOffset, PaletteReference palette, float scale = 1f, float rotation = 0f)
|
||||
{
|
||||
scale *= CurrentSequence.Scale;
|
||||
var screenOffset = (scale * wr.ScreenVectorComponents(offset)).XY.ToInt2();
|
||||
var imagePos = pos + screenOffset - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2));
|
||||
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale);
|
||||
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
||||
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale, alpha, rotation);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
if (shadow != null)
|
||||
{
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2));
|
||||
var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale);
|
||||
var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale, 1f, rotation);
|
||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||
}
|
||||
|
||||
return new IRenderable[] { imageRenderable };
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, WVec offset, float scale)
|
||||
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, in WVec offset)
|
||||
{
|
||||
var scale = CurrentSequence.Scale;
|
||||
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
|
||||
var cb = CurrentSequence.Bounds;
|
||||
return Rectangle.FromLTRB(
|
||||
@@ -96,7 +105,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public IRenderable[] Render(WPos pos, PaletteReference palette)
|
||||
{
|
||||
return Render(pos, WVec.Zero, 0, palette, 1f);
|
||||
return Render(pos, WVec.Zero, 0, palette);
|
||||
}
|
||||
|
||||
public void Play(string sequenceName)
|
||||
@@ -107,7 +116,7 @@ namespace OpenRA.Graphics
|
||||
int CurrentSequenceTickOrDefault()
|
||||
{
|
||||
const int DefaultTick = 40; // 25 fps == 40 ms
|
||||
return CurrentSequence != null ? CurrentSequence.Tick : DefaultTick;
|
||||
return CurrentSequence?.Tick ?? DefaultTick;
|
||||
}
|
||||
|
||||
void PlaySequence(string sequenceName)
|
||||
@@ -155,7 +164,7 @@ namespace OpenRA.Graphics
|
||||
if (frame >= CurrentSequence.Length)
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = () => { };
|
||||
tickFunc = null;
|
||||
after?.Invoke();
|
||||
}
|
||||
};
|
||||
@@ -203,13 +212,13 @@ namespace OpenRA.Graphics
|
||||
public void Tick(int t)
|
||||
{
|
||||
if (tickAlways)
|
||||
tickFunc();
|
||||
tickFunc?.Invoke();
|
||||
else
|
||||
{
|
||||
timeUntilNextFrame -= t;
|
||||
while (timeUntilNextFrame <= 0)
|
||||
{
|
||||
tickFunc();
|
||||
tickFunc?.Invoke();
|
||||
timeUntilNextFrame += CurrentSequenceTickOrDefault();
|
||||
}
|
||||
}
|
||||
@@ -227,11 +236,11 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasSequence(string seq) { return sequenceProvider.HasSequence(Name, seq); }
|
||||
public bool HasSequence(string seq) { return sequences.HasSequence(Name, seq); }
|
||||
|
||||
public ISpriteSequence GetSequence(string sequenceName)
|
||||
{
|
||||
return sequenceProvider.GetSequence(Name, sequenceName);
|
||||
return sequences.GetSequence(Name, sequenceName);
|
||||
}
|
||||
|
||||
public string GetRandomExistingSequence(string[] sequences, MersenneTwister random)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -35,21 +35,21 @@ namespace OpenRA.Graphics
|
||||
ZOffset = zOffset;
|
||||
}
|
||||
|
||||
public IRenderable[] Render(Actor self, WorldRenderer wr, PaletteReference pal, float scale)
|
||||
public IRenderable[] Render(Actor self, PaletteReference pal)
|
||||
{
|
||||
var center = self.CenterPosition;
|
||||
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
|
||||
var offset = OffsetFunc?.Invoke() ?? WVec.Zero;
|
||||
|
||||
var z = (ZOffset != null) ? ZOffset(center + offset) : 0;
|
||||
return Animation.Render(center, offset, z, pal, scale);
|
||||
var z = ZOffset?.Invoke(center + offset) ?? 0;
|
||||
return Animation.Render(center, offset, z, pal);
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(Actor self, WorldRenderer wr, float scale)
|
||||
public Rectangle ScreenBounds(Actor self, WorldRenderer wr)
|
||||
{
|
||||
var center = self.CenterPosition;
|
||||
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
|
||||
var offset = OffsetFunc?.Invoke() ?? WVec.Zero;
|
||||
|
||||
return Animation.ScreenBounds(wr, center, offset, scale);
|
||||
return Animation.ScreenBounds(wr, center, offset);
|
||||
}
|
||||
|
||||
public static implicit operator AnimationWithOffset(Animation a)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -49,10 +49,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
public readonly int[] PanelRegion = null;
|
||||
public readonly PanelSides PanelSides = PanelSides.All;
|
||||
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
|
||||
public readonly Dictionary<string, Rectangle> Regions = new();
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<string, Collection> Collections { get; private set; }
|
||||
public static IReadOnlyDictionary<string, Collection> Collections => collections;
|
||||
static Dictionary<string, Collection> collections;
|
||||
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
|
||||
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
|
||||
@@ -77,8 +77,6 @@ namespace OpenRA.Graphics
|
||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||
|
||||
Collections = new ReadOnlyDictionary<string, Collection>(collections);
|
||||
|
||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
|
||||
@@ -102,9 +100,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
static void LoadCollection(string name, MiniYaml yaml)
|
||||
{
|
||||
if (Game.ModData.LoadScreen != null)
|
||||
Game.ModData.LoadScreen.Display();
|
||||
|
||||
Game.ModData.LoadScreen?.Display();
|
||||
collections.Add(name, FieldLoader.Load<Collection>(yaml));
|
||||
}
|
||||
|
||||
@@ -146,6 +142,15 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
public static Sprite GetImage(string collectionName, string imageName)
|
||||
{
|
||||
var image = TryGetImage(collectionName, imageName);
|
||||
if (image == null)
|
||||
throw new ArgumentException($"Sprite `{collectionName}/{imageName}` was not found.");
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static Sprite TryGetImage(string collectionName, string imageName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return null;
|
||||
@@ -155,10 +160,7 @@ namespace OpenRA.Graphics
|
||||
return sprite;
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!collection.Regions.TryGetValue(imageName, out var mi))
|
||||
return null;
|
||||
@@ -178,6 +180,15 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
public static Sprite[] GetPanelImages(string collectionName)
|
||||
{
|
||||
var panel = TryGetPanelImages(collectionName);
|
||||
if (panel == null)
|
||||
throw new ArgumentException($"Panel `{collectionName}` was not found.");
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
public static Sprite[] TryGetPanelImages(string collectionName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return null;
|
||||
@@ -187,17 +198,14 @@ namespace OpenRA.Graphics
|
||||
return cachedSprites;
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Sprite[] sprites;
|
||||
if (collection.PanelRegion != null)
|
||||
{
|
||||
if (collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
|
||||
Log.Write("debug", $"Collection '{collectionName}' does not define a valid PanelRegion");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -224,18 +232,23 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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.
|
||||
if (!collection.Regions.Any())
|
||||
return Array.Empty<Sprite>();
|
||||
|
||||
// Support manual definitions for unusual dialog layouts
|
||||
sprites = new[]
|
||||
{
|
||||
GetImage(collectionName, "corner-tl"),
|
||||
GetImage(collectionName, "border-t"),
|
||||
GetImage(collectionName, "corner-tr"),
|
||||
GetImage(collectionName, "border-l"),
|
||||
GetImage(collectionName, "background"),
|
||||
GetImage(collectionName, "border-r"),
|
||||
GetImage(collectionName, "corner-bl"),
|
||||
GetImage(collectionName, "border-b"),
|
||||
GetImage(collectionName, "corner-br")
|
||||
TryGetImage(collectionName, "corner-tl"),
|
||||
TryGetImage(collectionName, "border-t"),
|
||||
TryGetImage(collectionName, "corner-tr"),
|
||||
TryGetImage(collectionName, "border-l"),
|
||||
TryGetImage(collectionName, "background"),
|
||||
TryGetImage(collectionName, "border-r"),
|
||||
TryGetImage(collectionName, "corner-bl"),
|
||||
TryGetImage(collectionName, "border-b"),
|
||||
TryGetImage(collectionName, "corner-br")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -250,13 +263,13 @@ namespace OpenRA.Graphics
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
Log.Write("debug", $"Could not find collection '{collectionName}'");
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
if (collection.PanelRegion == null || collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
|
||||
Log.Write("debug", $"Collection '{collectionName}' does not define a valid PanelRegion");
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,7 +17,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class CursorManager
|
||||
{
|
||||
class Cursor
|
||||
sealed class Cursor
|
||||
{
|
||||
public string Name;
|
||||
public int2 PaddedSize;
|
||||
@@ -28,14 +28,14 @@ namespace OpenRA.Graphics
|
||||
public IHardwareCursor[] Cursors;
|
||||
}
|
||||
|
||||
readonly Dictionary<string, Cursor> cursors = new Dictionary<string, Cursor>();
|
||||
readonly Dictionary<string, Cursor> cursors = new();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
readonly GraphicSettings graphicSettings;
|
||||
|
||||
Cursor cursor;
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
bool hardwareCursorsDisabled = false;
|
||||
readonly bool hardwareCursorsDisabled = false;
|
||||
bool hardwareCursorsDoubled = false;
|
||||
|
||||
public CursorManager(CursorProvider cursorProvider)
|
||||
@@ -69,9 +69,16 @@ namespace OpenRA.Graphics
|
||||
// Hotspot is specified relative to the center of the frame
|
||||
var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2;
|
||||
|
||||
// SheetBuilder expects data in BGRA
|
||||
var data = FrameToBGRA(kv.Key, f, palette);
|
||||
c.Sprites[c.Length++] = sheetBuilder.Add(data, f.Size, 0, hotspot);
|
||||
// Resolve indexed data to real colours
|
||||
var data = f.Data;
|
||||
var type = f.Type;
|
||||
if (type == SpriteFrameType.Indexed8)
|
||||
{
|
||||
data = ConvertIndexedToBgra(kv.Key, f, palette);
|
||||
type = SpriteFrameType.Bgra32;
|
||||
}
|
||||
|
||||
c.Sprites[c.Length++] = sheetBuilder.Add(data, type, f.Size, 0, hotspot);
|
||||
|
||||
// Bounds relative to the hotspot
|
||||
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
|
||||
@@ -99,34 +106,27 @@ namespace OpenRA.Graphics
|
||||
// Dispose any existing cursors to avoid leaking native resources
|
||||
ClearHardwareCursors();
|
||||
|
||||
try
|
||||
foreach (var kv in cursors)
|
||||
{
|
||||
foreach (var kv in cursors)
|
||||
var template = kv.Value;
|
||||
for (var i = 0; i < template.Sprites.Length; i++)
|
||||
{
|
||||
var template = kv.Value;
|
||||
for (var i = 0; i < template.Sprites.Length; i++)
|
||||
template.Cursors[i]?.Dispose();
|
||||
|
||||
// Calculate the padding to position the frame within sequenceBounds
|
||||
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
|
||||
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
|
||||
|
||||
var hardwareCursor = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
|
||||
if (hardwareCursor != null)
|
||||
template.Cursors[i] = hardwareCursor;
|
||||
else
|
||||
{
|
||||
if (template.Cursors[i] != null)
|
||||
template.Cursors[i].Dispose();
|
||||
|
||||
// Calculate the padding to position the frame within sequenceBounds
|
||||
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
|
||||
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
|
||||
|
||||
template.Cursors[i] = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
|
||||
Log.Write("debug", $"Failed to initialize hardware cursor for {template.Name}.");
|
||||
Console.WriteLine($"Failed to initialize hardware cursor for {template.Name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Log.Write("debug", "Error was: " + e.Message);
|
||||
|
||||
Console.WriteLine("Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Console.WriteLine("Error was: " + e.Message);
|
||||
|
||||
ClearHardwareCursors();
|
||||
}
|
||||
|
||||
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
||||
}
|
||||
@@ -170,10 +170,11 @@ namespace OpenRA.Graphics
|
||||
if (cursor != null && frame >= cursor.Cursors.Length)
|
||||
frame %= cursor.Cursors.Length;
|
||||
|
||||
if (cursor == null || isLocked)
|
||||
var hardwareCursor = cursor?.Cursors[frame];
|
||||
if (hardwareCursor == null || isLocked)
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
else
|
||||
Game.Renderer.Window.SetHardwareCursor(cursor.Cursors[frame]);
|
||||
Game.Renderer.Window.SetHardwareCursor(hardwareCursor);
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
@@ -189,17 +190,17 @@ namespace OpenRA.Graphics
|
||||
// Render cursor in software
|
||||
var doubleCursor = graphicSettings.CursorDouble;
|
||||
var cursorSprite = cursor.Sprites[frame % cursor.Length];
|
||||
var cursorSize = doubleCursor ? 2.0f * cursorSprite.Size : cursorSprite.Size;
|
||||
var cursorScale = doubleCursor ? 2 : 1;
|
||||
|
||||
// Cursor is rendered in native window coordinates
|
||||
// Apply same scaling rules as hardware cursors
|
||||
if (Game.Renderer.NativeWindowScale > 1.5f)
|
||||
cursorSize = 2 * cursorSize;
|
||||
cursorScale *= 2;
|
||||
|
||||
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
|
||||
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
|
||||
mousePos,
|
||||
cursorSize / Game.Renderer.WindowScale);
|
||||
cursorScale / Game.Renderer.WindowScale);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
@@ -217,33 +218,27 @@ namespace OpenRA.Graphics
|
||||
Update();
|
||||
}
|
||||
|
||||
public static byte[] FrameToBGRA(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
public static byte[] ConvertIndexedToBgra(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
{
|
||||
// Data is already in BGRA format
|
||||
if (frame.Type == SpriteFrameType.BGRA)
|
||||
return frame.Data;
|
||||
if (frame.Type != SpriteFrameType.Indexed8)
|
||||
throw new ArgumentException("ConvertIndexedToBgra requires input frames to be indexed.", nameof(frame));
|
||||
|
||||
// Cursors may be either native BGRA or Indexed.
|
||||
// Indexed sprites are converted to BGRA using the referenced palette.
|
||||
// All palettes must be explicitly referenced, even if they are embedded in the sprite.
|
||||
if (frame.Type == SpriteFrameType.Indexed && palette == null)
|
||||
throw new InvalidOperationException("Cursor sequence `{0}` attempted to load an indexed sprite but does not define Palette".F(name));
|
||||
if (palette == null)
|
||||
throw new InvalidOperationException($"Cursor sequence `{name}` attempted to load an indexed sprite but does not define Palette");
|
||||
|
||||
var width = frame.Size.Width;
|
||||
var height = frame.Size.Height;
|
||||
var data = new byte[4 * width * height];
|
||||
for (var j = 0; j < height; j++)
|
||||
unsafe
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
// Cast the data to an int array so we can copy the src data directly
|
||||
fixed (byte* bd = &data[0])
|
||||
{
|
||||
var rgba = palette[frame.Data[j * width + i]];
|
||||
var k = 4 * (j * width + i);
|
||||
|
||||
// Convert RGBA to BGRA
|
||||
data[k] = (byte)(rgba >> 16);
|
||||
data[k + 1] = (byte)(rgba >> 8);
|
||||
data[k + 2] = (byte)(rgba >> 0);
|
||||
data[k + 3] = (byte)(rgba >> 24);
|
||||
var rgba = (uint*)bd;
|
||||
for (var j = 0; j < height; j++)
|
||||
for (var i = 0; i < width; i++)
|
||||
rgba[j * width + i] = palette[frame.Data[j * width + i]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -31,15 +31,14 @@ namespace OpenRA.Graphics
|
||||
|
||||
// Overwrite previous definitions if there are duplicates
|
||||
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
||||
foreach (var p in modData.DefaultRules.Actors["world"].TraitInfos<IProvidesCursorPaletteInfo>())
|
||||
foreach (var p in modData.DefaultRules.Actors[SystemActors.World].TraitInfos<IProvidesCursorPaletteInfo>())
|
||||
if (p.Palette != null)
|
||||
pals[p.Palette] = p;
|
||||
|
||||
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
|
||||
.Where(p => p != null)
|
||||
.Distinct()
|
||||
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem))
|
||||
.AsReadOnly();
|
||||
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
|
||||
|
||||
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
||||
var cursors = new Dictionary<string, CursorSequence>();
|
||||
@@ -47,7 +46,7 @@ namespace OpenRA.Graphics
|
||||
foreach (var sequence in s.Value.Nodes)
|
||||
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
||||
|
||||
Cursors = cursors.AsReadOnly();
|
||||
Cursors = cursors;
|
||||
}
|
||||
|
||||
public bool HasCursorSequence(string cursor)
|
||||
@@ -60,7 +59,7 @@ namespace OpenRA.Graphics
|
||||
try { return Cursors[cursor]; }
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
throw new InvalidOperationException("Cursor does not have a sequence `{0}`".F(cursor));
|
||||
throw new InvalidOperationException($"Cursor does not have a sequence `{cursor}`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -31,28 +31,36 @@ namespace OpenRA.Graphics
|
||||
Palette = palette;
|
||||
Name = name;
|
||||
|
||||
Frames = cache[cursorSrc].Skip(Start).ToArray();
|
||||
var cursorSprites = cache[cursorSrc];
|
||||
Frames = cursorSprites.Skip(Start).ToArray();
|
||||
|
||||
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
|
||||
if ((d.TryGetValue("Length", out var yaml) && yaml.Value == "*") ||
|
||||
(d.TryGetValue("End", out yaml) && yaml.Value == "*"))
|
||||
Length = Frames.Length;
|
||||
else if (d.ContainsKey("Length"))
|
||||
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
|
||||
else if (d.ContainsKey("End"))
|
||||
Length = Exts.ParseIntegerInvariant(d["End"].Value) - Start;
|
||||
else if (d.TryGetValue("Length", out yaml))
|
||||
Length = Exts.ParseIntegerInvariant(yaml.Value);
|
||||
else if (d.TryGetValue("End", out yaml))
|
||||
Length = Exts.ParseIntegerInvariant(yaml.Value) - Start;
|
||||
else
|
||||
Length = 1;
|
||||
|
||||
Frames = Frames.Take(Length).ToArray();
|
||||
|
||||
if (d.ContainsKey("X"))
|
||||
if (Start > cursorSprites.Length)
|
||||
throw new YamlException($"Cursor {name}: {nameof(Start)} is greater than the length of the sprite sequence.");
|
||||
|
||||
if (Length > cursorSprites.Length)
|
||||
throw new YamlException($"Cursor {name}: {nameof(Length)} is greater than the length of the sprite sequence.");
|
||||
|
||||
if (d.TryGetValue("X", out yaml))
|
||||
{
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
|
||||
Exts.TryParseIntegerInvariant(yaml.Value, out var x);
|
||||
Hotspot = Hotspot.WithX(x);
|
||||
}
|
||||
|
||||
if (d.ContainsKey("Y"))
|
||||
if (d.TryGetValue("Y", out yaml))
|
||||
{
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
|
||||
Exts.TryParseIntegerInvariant(yaml.Value, out var y);
|
||||
Hotspot = Hotspot.WithY(y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -17,73 +17,98 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class HardwarePalette : IDisposable
|
||||
{
|
||||
public ITexture Texture { get; private set; }
|
||||
public ITexture Texture { get; }
|
||||
public ITexture ColorShifts { get; }
|
||||
|
||||
public int Height { get; private set; }
|
||||
readonly Dictionary<string, ImmutablePalette> palettes = new Dictionary<string, ImmutablePalette>();
|
||||
readonly Dictionary<string, MutablePalette> modifiablePalettes = new Dictionary<string, MutablePalette>();
|
||||
readonly IReadOnlyDictionary<string, MutablePalette> readOnlyModifiablePalettes;
|
||||
readonly Dictionary<string, int> indices = new Dictionary<string, int>();
|
||||
byte[] buffer = new byte[0];
|
||||
readonly Dictionary<string, ImmutablePalette> palettes = new();
|
||||
readonly Dictionary<string, MutablePalette> mutablePalettes = new();
|
||||
readonly Dictionary<string, int> indices = new();
|
||||
byte[] buffer = Array.Empty<byte>();
|
||||
float[] colorShiftBuffer = Array.Empty<float>();
|
||||
|
||||
public HardwarePalette()
|
||||
{
|
||||
Texture = Game.Renderer.Context.CreateTexture();
|
||||
readOnlyModifiablePalettes = modifiablePalettes.AsReadOnly();
|
||||
ColorShifts = Game.Renderer.Context.CreateTexture();
|
||||
}
|
||||
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return modifiablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
|
||||
return mutablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
|
||||
}
|
||||
|
||||
public IPalette GetPalette(string name)
|
||||
{
|
||||
if (modifiablePalettes.TryGetValue(name, out var mutable))
|
||||
if (mutablePalettes.TryGetValue(name, out var mutable))
|
||||
return mutable.AsReadOnly();
|
||||
if (palettes.TryGetValue(name, out var immutable))
|
||||
return immutable;
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
}
|
||||
|
||||
public int GetPaletteIndex(string name)
|
||||
{
|
||||
if (!indices.TryGetValue(name, out var ret))
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void AddPalette(string name, ImmutablePalette p, bool allowModifiers)
|
||||
{
|
||||
if (palettes.ContainsKey(name))
|
||||
throw new InvalidOperationException("Palette {0} has already been defined".F(name));
|
||||
throw new InvalidOperationException($"Palette {name} has already been defined");
|
||||
|
||||
int index = palettes.Count;
|
||||
// PERF: the first row in the palette textures is reserved as a placeholder for non-indexed sprites
|
||||
// that do not have a color-shift applied. This provides a quick shortcut to avoid querying the
|
||||
// color-shift texture for every pixel only to find that most are not shifted.
|
||||
var index = palettes.Count + 1;
|
||||
indices.Add(name, index);
|
||||
palettes.Add(name, p);
|
||||
|
||||
if (palettes.Count > Height)
|
||||
if (index >= Height)
|
||||
{
|
||||
Height = Exts.NextPowerOf2(palettes.Count);
|
||||
Height = Exts.NextPowerOf2(index + 1);
|
||||
Array.Resize(ref buffer, Height * Palette.Size * 4);
|
||||
Array.Resize(ref colorShiftBuffer, Height * 8);
|
||||
}
|
||||
|
||||
if (allowModifiers)
|
||||
modifiablePalettes.Add(name, new MutablePalette(p));
|
||||
mutablePalettes.Add(name, new MutablePalette(p));
|
||||
else
|
||||
CopyPaletteToBuffer(index, p);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Performance", "CA1854:Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method",
|
||||
Justification = "False positive - indexer is a set not a get.")]
|
||||
public void ReplacePalette(string name, IPalette p)
|
||||
{
|
||||
if (modifiablePalettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], modifiablePalettes[name] = new MutablePalette(p));
|
||||
if (mutablePalettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p));
|
||||
else if (palettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
|
||||
else
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
CopyBufferToTexture();
|
||||
}
|
||||
|
||||
public void SetColorShift(string name, float hueOffset, float satOffset, float valueMultiplier, float minHue, float maxHue)
|
||||
{
|
||||
var index = GetPaletteIndex(name);
|
||||
colorShiftBuffer[8 * index + 0] = minHue;
|
||||
colorShiftBuffer[8 * index + 1] = maxHue;
|
||||
colorShiftBuffer[8 * index + 4] = hueOffset;
|
||||
colorShiftBuffer[8 * index + 5] = satOffset;
|
||||
colorShiftBuffer[8 * index + 6] = valueMultiplier;
|
||||
}
|
||||
|
||||
public bool HasColorShift(string name)
|
||||
{
|
||||
var index = GetPaletteIndex(name);
|
||||
return colorShiftBuffer[8 * index] != 0 || colorShiftBuffer[8 * index + 1] != 0;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
CopyModifiablePalettesToBuffer();
|
||||
@@ -97,26 +122,27 @@ namespace OpenRA.Graphics
|
||||
|
||||
void CopyModifiablePalettesToBuffer()
|
||||
{
|
||||
foreach (var kvp in modifiablePalettes)
|
||||
foreach (var kvp in mutablePalettes)
|
||||
CopyPaletteToBuffer(indices[kvp.Key], kvp.Value);
|
||||
}
|
||||
|
||||
void CopyBufferToTexture()
|
||||
{
|
||||
Texture.SetData(buffer, Palette.Size, Height);
|
||||
ColorShifts.SetFloatData(colorShiftBuffer, 2, Height);
|
||||
}
|
||||
|
||||
public void ApplyModifiers(IEnumerable<IPaletteModifier> paletteMods)
|
||||
{
|
||||
foreach (var mod in paletteMods)
|
||||
mod.AdjustPalette(readOnlyModifiablePalettes);
|
||||
mod.AdjustPalette(mutablePalettes);
|
||||
|
||||
// Update our texture with the changes.
|
||||
CopyModifiablePalettesToBuffer();
|
||||
CopyBufferToTexture();
|
||||
|
||||
// Reset modified palettes back to their original colors, ready for next time.
|
||||
foreach (var kvp in modifiablePalettes)
|
||||
foreach (var kvp in mutablePalettes)
|
||||
{
|
||||
var originalPalette = palettes[kvp.Key];
|
||||
var modifiedPalette = kvp.Value;
|
||||
@@ -127,6 +153,7 @@ namespace OpenRA.Graphics
|
||||
public void Dispose()
|
||||
{
|
||||
Texture.Dispose();
|
||||
ColorShifts.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -25,11 +26,11 @@ namespace OpenRA.Graphics
|
||||
float[] Bounds(uint frame);
|
||||
ModelRenderData RenderData(uint section);
|
||||
|
||||
/// <summary>Returns the smallest rectangle that covers all rotations of all frames in a model</summary>
|
||||
/// <summary>Returns the smallest rectangle that covers all rotations of all frames in a model.</summary>
|
||||
Rectangle AggregateBounds { get; }
|
||||
}
|
||||
|
||||
public struct ModelRenderData
|
||||
public readonly struct ModelRenderData
|
||||
{
|
||||
public readonly int Start;
|
||||
public readonly int Count;
|
||||
@@ -45,6 +46,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public interface IModelCache : IDisposable
|
||||
{
|
||||
IModel GetModel(string model);
|
||||
IModel GetModelSequence(string model, string sequence);
|
||||
bool HasModelSequence(string model, string sequence);
|
||||
IVertexBuffer<Vertex> VertexBuffer { get; }
|
||||
@@ -60,12 +62,17 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public Action<string> OnMissingModelError { get; set; }
|
||||
|
||||
class PlaceholderModelCache : IModelCache
|
||||
sealed class PlaceholderModelCache : IModelCache
|
||||
{
|
||||
public IVertexBuffer<Vertex> VertexBuffer { get { throw new NotImplementedException(); } }
|
||||
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();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -14,7 +14,7 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct ModelAnimation
|
||||
public readonly struct ModelAnimation
|
||||
{
|
||||
public readonly IModel Model;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
@@ -46,12 +46,6 @@ namespace OpenRA.Graphics
|
||||
xy.Y + (int)(r.Bottom * scale));
|
||||
}
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisableFunc == null || !DisableFunc();
|
||||
}
|
||||
}
|
||||
public bool IsVisible => DisableFunc == null || !DisableFunc();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -37,18 +37,19 @@ namespace OpenRA.Graphics
|
||||
// Static constants
|
||||
static readonly float[] ShadowDiffuse = new float[] { 0, 0, 0 };
|
||||
static readonly float[] ShadowAmbient = new float[] { 1, 1, 1 };
|
||||
static readonly float2 SpritePadding = new float2(2, 2);
|
||||
static readonly float2 SpritePadding = new(2, 2);
|
||||
static readonly float[] ZeroVector = new float[] { 0, 0, 0, 1 };
|
||||
static readonly float[] ZVector = new float[] { 0, 0, 1, 1 };
|
||||
static readonly float[] FlipMtx = Util.ScaleMatrix(1, -1, 1);
|
||||
static readonly float[] ShadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2);
|
||||
static readonly float[] GroundNormal = { 0, 0, 1, 1 };
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
|
||||
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
|
||||
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
|
||||
readonly List<(Sheet Sheet, Action Func)> doRender = new List<(Sheet, Action)>();
|
||||
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new();
|
||||
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new();
|
||||
readonly List<(Sheet Sheet, Action Func)> doRender = new();
|
||||
|
||||
SheetBuilder sheetBuilderForFrame;
|
||||
bool isInFrame;
|
||||
@@ -64,7 +65,7 @@ namespace OpenRA.Graphics
|
||||
shader.SetTexture("Palette", palette);
|
||||
}
|
||||
|
||||
public void SetViewportParams(Size screen, int2 scroll)
|
||||
public void SetViewportParams()
|
||||
{
|
||||
var a = 2f / renderer.SheetSize;
|
||||
var view = new[]
|
||||
@@ -80,7 +81,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public ModelRenderProxy RenderAsync(
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
|
||||
float[] groundNormal, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
in WRot groundOrientation, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
|
||||
{
|
||||
if (!isInFrame)
|
||||
@@ -92,7 +93,10 @@ namespace OpenRA.Graphics
|
||||
// Correct for bogus light source definition
|
||||
var lightYaw = Util.MakeFloatMatrix(new WRot(WAngle.Zero, WAngle.Zero, -lightSource.Yaw).AsMatrix());
|
||||
var lightPitch = Util.MakeFloatMatrix(new WRot(WAngle.Zero, -lightSource.Pitch, WAngle.Zero).AsMatrix());
|
||||
var shadowTransform = Util.MatrixMultiply(lightPitch, lightYaw);
|
||||
var ground = Util.MakeFloatMatrix(groundOrientation.AsMatrix());
|
||||
var shadowTransform = Util.MatrixMultiply(Util.MatrixMultiply(lightPitch, lightYaw), Util.MatrixInverse(ground));
|
||||
|
||||
var groundNormal = Util.MatrixVectorMultiply(ground, GroundNormal);
|
||||
|
||||
var invShadowTransform = Util.MatrixInverse(shadowTransform);
|
||||
var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
|
||||
@@ -163,8 +167,7 @@ namespace OpenRA.Graphics
|
||||
CalculateSpriteGeometry(tl, br, 1, out var spriteSize, out var spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out var shadowSpriteSize, out var shadowSpriteOffset);
|
||||
|
||||
if (sheetBuilderForFrame == null)
|
||||
sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
|
||||
sheetBuilderForFrame ??= new SheetBuilder(SheetType.BGRA, AllocateSheet);
|
||||
|
||||
var sprite = sheetBuilderForFrame.Allocate(spriteSize, 0, spriteOffset);
|
||||
var shadowSprite = sheetBuilderForFrame.Allocate(shadowSpriteSize, 0, shadowSpriteOffset);
|
||||
@@ -205,7 +208,7 @@ namespace OpenRA.Graphics
|
||||
var t = m.Model.TransformationMatrix(i, frame);
|
||||
var it = Util.MatrixInverse(t);
|
||||
if (it == null)
|
||||
throw new InvalidOperationException("Failed to invert the transformed matrix of frame {0} during RenderAsync.".F(i));
|
||||
throw new InvalidOperationException($"Failed to invert the transformed matrix of frame {i} during RenderAsync.");
|
||||
|
||||
// Transform light vector from shadow -> world -> limb coords
|
||||
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
|
||||
@@ -299,7 +302,7 @@ namespace OpenRA.Graphics
|
||||
return fbo;
|
||||
}
|
||||
|
||||
void DisableFrameBuffer(IFrameBuffer fbo)
|
||||
static void DisableFrameBuffer(IFrameBuffer fbo)
|
||||
{
|
||||
Game.Renderer.Flush();
|
||||
Game.Renderer.Context.DisableDepthBuffer();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -40,11 +40,12 @@ namespace OpenRA.Graphics
|
||||
return new ReadOnlyPalette(palette);
|
||||
}
|
||||
|
||||
class ReadOnlyPalette : IPalette
|
||||
sealed class ReadOnlyPalette : IPalette
|
||||
{
|
||||
IPalette palette;
|
||||
readonly IPalette palette;
|
||||
public ReadOnlyPalette(IPalette palette) { this.palette = palette; }
|
||||
public uint this[int index] { get { return palette[index]; } }
|
||||
public uint this[int index] => palette[index];
|
||||
|
||||
public void CopyToArray(Array destination, int destinationOffset)
|
||||
{
|
||||
palette.CopyToArray(destination, destinationOffset);
|
||||
@@ -56,28 +57,25 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
readonly uint[] colors = new uint[Palette.Size];
|
||||
|
||||
public uint this[int index]
|
||||
{
|
||||
get { return colors[index]; }
|
||||
}
|
||||
public uint this[int index] => colors[index];
|
||||
|
||||
public void CopyToArray(Array destination, int destinationOffset)
|
||||
{
|
||||
Buffer.BlockCopy(colors, 0, destination, destinationOffset * 4, Palette.Size * 4);
|
||||
}
|
||||
|
||||
public ImmutablePalette(string filename, int[] remap)
|
||||
public ImmutablePalette(string filename, int[] remapTransparent, int[] remap)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
LoadFromStream(s, remap);
|
||||
LoadFromStream(s, remapTransparent, remap);
|
||||
}
|
||||
|
||||
public ImmutablePalette(Stream s, int[] remapShadow)
|
||||
public ImmutablePalette(Stream s, int[] remapTransparent, int[] remapShadow)
|
||||
{
|
||||
LoadFromStream(s, remapShadow);
|
||||
LoadFromStream(s, remapTransparent, remapShadow);
|
||||
}
|
||||
|
||||
void LoadFromStream(Stream s, int[] remapShadow)
|
||||
void LoadFromStream(Stream s, int[] remapTransparent, int[] remapShadow)
|
||||
{
|
||||
using (var reader = new BinaryReader(s))
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
@@ -94,7 +92,9 @@ namespace OpenRA.Graphics
|
||||
colors[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
colors[0] = 0; // Convert black background to transparency.
|
||||
foreach (var i in remapTransparent)
|
||||
colors[i] = 0;
|
||||
|
||||
foreach (var i in remapShadow)
|
||||
colors[i] = 140u << 24;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public ImmutablePalette(IPalette p)
|
||||
{
|
||||
for (int i = 0; i < Palette.Size; i++)
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
colors[i] = p[i];
|
||||
}
|
||||
|
||||
@@ -126,8 +126,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public uint this[int index]
|
||||
{
|
||||
get { return colors[index]; }
|
||||
set { colors[index] = value; }
|
||||
get => colors[index];
|
||||
set => colors[index] = value;
|
||||
}
|
||||
|
||||
public void CopyToArray(Array destination, int destinationOffset)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -18,8 +18,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public readonly string Name;
|
||||
public IPalette Palette { get; internal set; }
|
||||
public float TextureIndex { get { return index / hardwarePalette.Height; } }
|
||||
public float TextureMidIndex { get { return (index + 0.5f) / hardwarePalette.Height; } }
|
||||
public float TextureIndex => index / hardwarePalette.Height;
|
||||
public float TextureMidIndex => (index + 0.5f) / hardwarePalette.Height;
|
||||
|
||||
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
|
||||
{
|
||||
@@ -28,5 +28,7 @@ namespace OpenRA.Graphics
|
||||
this.index = index;
|
||||
this.hardwarePalette = hardwarePalette;
|
||||
}
|
||||
|
||||
public bool HasColorShift => hardwarePalette.HasColorShift(Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -59,6 +59,7 @@ namespace OpenRA
|
||||
int DisplayCount { get; }
|
||||
int CurrentDisplay { get; }
|
||||
bool HasInputFocus { get; }
|
||||
bool IsSuspended { get; }
|
||||
|
||||
event Action<float, float, float, float> OnWindowScaleChanged;
|
||||
|
||||
@@ -71,6 +72,7 @@ namespace OpenRA
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot, bool pixelDouble);
|
||||
void SetHardwareCursor(IHardwareCursor cursor);
|
||||
void SetWindowTitle(string title);
|
||||
void SetRelativeMouseMode(bool mode);
|
||||
void SetScaleModifier(float scale);
|
||||
|
||||
@@ -82,6 +84,7 @@ namespace OpenRA
|
||||
public interface IGraphicsContext : IDisposable
|
||||
{
|
||||
IVertexBuffer<Vertex> CreateVertexBuffer(int size);
|
||||
Vertex[] CreateVertices(int size);
|
||||
ITexture CreateTexture();
|
||||
IFrameBuffer CreateFrameBuffer(Size s);
|
||||
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
|
||||
@@ -103,6 +106,11 @@ namespace OpenRA
|
||||
{
|
||||
void Bind();
|
||||
void SetData(T[] vertices, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Upon return `vertices` may reference another array object of at least the same size - containing random values.
|
||||
/// </summary>
|
||||
void SetData(ref T[] vertices, int length);
|
||||
void SetData(T[] vertices, int offset, int start, int length);
|
||||
}
|
||||
|
||||
@@ -122,8 +130,8 @@ namespace OpenRA
|
||||
|
||||
public interface ITexture : IDisposable
|
||||
{
|
||||
void SetData(uint[,] colors);
|
||||
void SetData(byte[] colors, int width, int height);
|
||||
void SetFloatData(float[] data, int width, int height);
|
||||
byte[] GetData();
|
||||
Size Size { get; }
|
||||
TextureScaleFilter ScaleFilter { get; set; }
|
||||
@@ -145,7 +153,7 @@ namespace OpenRA
|
||||
TriangleList,
|
||||
}
|
||||
|
||||
public struct Range<T>
|
||||
public readonly struct Range<T>
|
||||
{
|
||||
public readonly T Start, End;
|
||||
public Range(T start, T end) { Start = start; End = end; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -18,43 +17,37 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public class PlayerColorRemap : IPaletteRemap
|
||||
{
|
||||
Dictionary<int, Color> remapColors;
|
||||
readonly int[] remapIndices;
|
||||
readonly float hue;
|
||||
readonly float saturation;
|
||||
readonly float value;
|
||||
|
||||
public static int GetRemapIndex(int[] ramp, int i)
|
||||
public PlayerColorRemap(int[] remapIndices, Color color)
|
||||
{
|
||||
return ramp[i];
|
||||
}
|
||||
this.remapIndices = remapIndices;
|
||||
|
||||
public PlayerColorRemap(int[] ramp, Color c, float rampFraction)
|
||||
{
|
||||
var h = c.GetHue() / 360.0f;
|
||||
var s = c.GetSaturation();
|
||||
var l = c.GetBrightness();
|
||||
|
||||
// Increase luminosity if required to represent the full ramp
|
||||
var rampRange = (byte)((1 - rampFraction) * l);
|
||||
var c1 = Color.FromAhsl(h, s, Math.Max(rampRange, l));
|
||||
var c2 = Color.FromAhsl(h, s, (byte)Math.Max(0, l - rampRange));
|
||||
var baseIndex = ramp[0];
|
||||
var remapRamp = ramp.Select(r => r - ramp[0]);
|
||||
var rampMaxIndex = ramp.Length - 1;
|
||||
|
||||
// reversed remapping
|
||||
if (ramp[0] > ramp[rampMaxIndex])
|
||||
{
|
||||
baseIndex = ramp[rampMaxIndex];
|
||||
for (var i = rampMaxIndex; i > 0; i--)
|
||||
remapRamp = ramp.Select(r => r - ramp[rampMaxIndex]);
|
||||
}
|
||||
|
||||
remapColors = remapRamp.Select((x, i) => (baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
|
||||
.ToDictionary(u => u.Item1, u => u.Item2);
|
||||
var (r, g, b) = color.ToLinear();
|
||||
(hue, saturation, value) = Color.RgbToHsv(r, g, b);
|
||||
}
|
||||
|
||||
public Color GetRemappedColor(Color original, int index)
|
||||
{
|
||||
return remapColors.TryGetValue(index, out var c)
|
||||
? c : original;
|
||||
if (!remapIndices.Contains(index))
|
||||
return original;
|
||||
|
||||
// Color remapping is applied in a linear color space, so start
|
||||
// by undoing the pre-multiplied alpha and gamma corrections
|
||||
var (r, g, b) = original.ToLinear();
|
||||
|
||||
// Calculate the brightness (i.e HSV value) of the original colour
|
||||
// This inlines the single line of Color.RgbToHsv() that we need
|
||||
var value = Math.Max(Math.Max(r, g), b);
|
||||
|
||||
// Construct the new RGB color
|
||||
(r, g, b) = Color.HsvToRgb(hue, saturation, value * this.value);
|
||||
|
||||
// Convert linear back to SRGB and pre-multiply by the alpha
|
||||
return Color.FromLinear(original.A, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -16,21 +17,38 @@ namespace OpenRA.Graphics
|
||||
public interface IRenderable
|
||||
{
|
||||
WPos Pos { get; }
|
||||
PaletteReference Palette { get; }
|
||||
int ZOffset { get; }
|
||||
bool IsDecoration { get; }
|
||||
|
||||
IRenderable WithPalette(PaletteReference newPalette);
|
||||
IRenderable WithZOffset(int newOffset);
|
||||
IRenderable OffsetBy(WVec offset);
|
||||
IRenderable OffsetBy(in WVec offset);
|
||||
IRenderable AsDecoration();
|
||||
|
||||
IFinalizedRenderable PrepareRender(WorldRenderer wr);
|
||||
}
|
||||
|
||||
public interface ITintableRenderable
|
||||
public interface IPalettedRenderable : IRenderable
|
||||
{
|
||||
IRenderable WithTint(in float3 newTint);
|
||||
PaletteReference Palette { get; }
|
||||
IPalettedRenderable WithPalette(PaletteReference newPalette);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum TintModifiers
|
||||
{
|
||||
None = 0,
|
||||
IgnoreWorldTint = 1,
|
||||
ReplaceColor = 2
|
||||
}
|
||||
|
||||
public interface IModifyableRenderable : IRenderable
|
||||
{
|
||||
float Alpha { get; }
|
||||
float3 Tint { get; }
|
||||
TintModifiers TintModifiers { get; }
|
||||
|
||||
IModifyableRenderable WithAlpha(float newAlpha);
|
||||
IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers);
|
||||
}
|
||||
|
||||
public interface IFinalizedRenderable
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -18,7 +18,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public class RgbaColorRenderer
|
||||
{
|
||||
static readonly float3 Offset = new float3(0.5f, 0.5f, 0f);
|
||||
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
|
||||
|
||||
readonly SpriteRenderer parent;
|
||||
readonly Vertex[] vertices = new Vertex[6];
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor)
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
var delta = (end - start) / (end - start).XY.Length;
|
||||
var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z);
|
||||
@@ -52,10 +52,10 @@ namespace OpenRA.Graphics
|
||||
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);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color color)
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
var delta = (end - start) / (end - start).XY.Length;
|
||||
var corner = width / 2 * new float2(-delta.Y, delta.X);
|
||||
@@ -72,15 +72,15 @@ namespace OpenRA.Graphics
|
||||
vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[4] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0);
|
||||
vertices[5] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the 2D intersection of two lines.
|
||||
/// Will behave badly if the lines are parallel.
|
||||
/// Z position is the average of a and b (ignores actual intersection point if it exists)
|
||||
/// Z position is the average of a and b (ignores actual intersection point if it exists).
|
||||
/// </summary>
|
||||
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
|
||||
static float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
|
||||
{
|
||||
var crossA = a.X * (a.Y + da.Y) - a.Y * (a.X + da.X);
|
||||
var crossB = b.X * (b.Y + db.Y) - b.Y * (b.X + db.X);
|
||||
@@ -90,7 +90,7 @@ namespace OpenRA.Graphics
|
||||
return new float3(x / d, y / d, 0.5f * (a.Z + b.Z));
|
||||
}
|
||||
|
||||
void DrawDisconnectedLine(IEnumerable<float3> points, float width, Color color)
|
||||
void DrawDisconnectedLine(IEnumerable<float3> points, float width, Color color, BlendMode blendMode)
|
||||
{
|
||||
using (var e = points.GetEnumerator())
|
||||
{
|
||||
@@ -101,13 +101,13 @@ namespace OpenRA.Graphics
|
||||
while (e.MoveNext())
|
||||
{
|
||||
var point = e.Current;
|
||||
DrawLine(lastPoint, point, width, color);
|
||||
DrawLine(lastPoint, point, width, color, blendMode);
|
||||
lastPoint = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawConnectedLine(float3[] points, float width, Color color, bool closed)
|
||||
void DrawConnectedLine(float3[] points, float width, Color color, bool closed, BlendMode blendMode)
|
||||
{
|
||||
// Not a line
|
||||
if (points.Length < 2)
|
||||
@@ -116,7 +116,7 @@ namespace OpenRA.Graphics
|
||||
// Single segment
|
||||
if (points.Length == 2)
|
||||
{
|
||||
DrawLine(points[0], points[1], width, color);
|
||||
DrawLine(points[0], points[1], width, color, blendMode);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace OpenRA.Graphics
|
||||
// Segment is part of closed loop
|
||||
if (closed)
|
||||
{
|
||||
var prev = points[points.Length - 1];
|
||||
var prev = points[^1];
|
||||
var prevDir = (start - prev) / (start - prev).XY.Length;
|
||||
var prevCorner = width / 2 * new float3(-prevDir.Y, prevDir.X, prevDir.Z);
|
||||
ca = IntersectionOf(start - prevCorner, prevDir, start - corner, dir);
|
||||
@@ -163,7 +163,7 @@ namespace OpenRA.Graphics
|
||||
vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||
vertices[4] = new Vertex(cd + Offset, r, g, b, a, 0, 0);
|
||||
vertices[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
|
||||
// Advance line segment
|
||||
end = next;
|
||||
@@ -175,32 +175,32 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false)
|
||||
public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
if (!connectSegments)
|
||||
DrawDisconnectedLine(points, width, color);
|
||||
DrawDisconnectedLine(points, width, color, blendMode);
|
||||
else
|
||||
DrawConnectedLine(points as float3[] ?? points.ToArray(), width, color, false);
|
||||
DrawConnectedLine(points as float3[] ?? points.ToArray(), width, color, false, blendMode);
|
||||
}
|
||||
|
||||
public void DrawPolygon(float3[] vertices, float width, Color color)
|
||||
public void DrawPolygon(float3[] vertices, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
DrawConnectedLine(vertices, width, color, true);
|
||||
DrawConnectedLine(vertices, width, color, true, blendMode);
|
||||
}
|
||||
|
||||
public void DrawPolygon(float2[] vertices, float width, Color color)
|
||||
public void DrawPolygon(float2[] vertices, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true);
|
||||
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true, blendMode);
|
||||
}
|
||||
|
||||
public void DrawRect(in float3 tl, in float3 br, float width, Color color)
|
||||
public void DrawRect(in float3 tl, in float3 br, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||
var bl = new float3(tl.X, br.Y, br.Z);
|
||||
DrawPolygon(new[] { tl, tr, br, bl }, width, color);
|
||||
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
|
||||
}
|
||||
|
||||
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color)
|
||||
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;
|
||||
@@ -211,17 +211,17 @@ namespace OpenRA.Graphics
|
||||
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);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
public void FillRect(in float3 tl, in float3 br, Color color)
|
||||
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 bl = new float3(tl.X, br.Y, br.Z);
|
||||
FillRect(tl, tr, br, bl, color);
|
||||
FillRect(tl, tr, br, bl, color, blendMode);
|
||||
}
|
||||
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color)
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
@@ -235,10 +235,10 @@ namespace OpenRA.Graphics
|
||||
vertices[3] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[4] = new Vertex(d + Offset, cr, cg, cb, ca, 0, 0);
|
||||
vertices[5] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
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)
|
||||
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[1] = VertexWithColor(b + Offset, topRightColor);
|
||||
@@ -247,7 +247,7 @@ namespace OpenRA.Graphics
|
||||
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
|
||||
vertices[5] = VertexWithColor(a + Offset, topLeftColor);
|
||||
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
}
|
||||
|
||||
static Vertex VertexWithColor(in float3 xyz, Color color)
|
||||
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
|
||||
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
|
||||
}
|
||||
|
||||
public void FillEllipse(in float3 tl, in float3 br, Color color, int vertices = 32)
|
||||
public void FillEllipse(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
// TODO: Create an ellipse polygon instead
|
||||
var a = (br.X - tl.X) / 2;
|
||||
@@ -272,7 +272,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
var z = float2.Lerp(tl.Z, br.Z, (y - tl.Y) / (br.Y - tl.Y));
|
||||
var dx = a * (float)Math.Sqrt(1 - (y - yc) * (y - yc) / b / b);
|
||||
DrawLine(new float3(xc - dx, y, z), new float3(xc + dx, y, z), 1, color);
|
||||
DrawLine(new float3(xc - dx, y, z), new float3(xc + dx, y, z), 1, color, blendMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -22,44 +22,36 @@ namespace OpenRA.Graphics
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 location, in float3 size)
|
||||
public void DrawSprite(Sprite s, in float3 location, in float3 scale, float rotation = 0f)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, location, 0, size);
|
||||
parent.DrawSprite(s, 0, location, scale, rotation);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 location)
|
||||
public void DrawSprite(Sprite s, in float3 location, float scale = 1f, float rotation = 0f)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, location, 0, s.Size);
|
||||
parent.DrawSprite(s, 0, location, scale, rotation);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
|
||||
public void DrawSprite(Sprite s, in float3 location, float scale, in float3 tint, float alpha, float rotation = 0f)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, a, b, c, d);
|
||||
parent.DrawSprite(s, 0, location, scale, tint, alpha, rotation);
|
||||
}
|
||||
|
||||
public void DrawSpriteWithTint(Sprite s, in float3 location, in float3 size, in float3 tint)
|
||||
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSpriteWithTint(s, location, 0, size, tint);
|
||||
}
|
||||
|
||||
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSpriteWithTint(s, a, b, c, d, tint);
|
||||
parent.DrawSprite(s, 0, a, b, c, d, tint, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
using Sequences = IReadOnlyDictionary<string, Lazy<IReadOnlyDictionary<string, ISpriteSequence>>>;
|
||||
using UnitSequences = Lazy<IReadOnlyDictionary<string, ISpriteSequence>>;
|
||||
|
||||
public interface ISpriteSequence
|
||||
{
|
||||
string Name { get; }
|
||||
int Start { get; }
|
||||
int Length { get; }
|
||||
int Stride { get; }
|
||||
int Facings { get; }
|
||||
int Tick { get; }
|
||||
int ZOffset { get; }
|
||||
int ShadowStart { get; }
|
||||
int ShadowZOffset { get; }
|
||||
int[] Frames { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public class SequenceProvider : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly string tileSet;
|
||||
readonly Lazy<Sequences> sequences;
|
||||
readonly Lazy<SpriteCache> spriteCache;
|
||||
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
|
||||
|
||||
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
|
||||
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
sequences = Exts.Lazy(() =>
|
||||
{
|
||||
using (new Support.PerfTimer("LoadSequences"))
|
||||
return Load(fileSystem, additionalSequences);
|
||||
});
|
||||
|
||||
spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders));
|
||||
}
|
||||
|
||||
public ISpriteSequence GetSequence(string unitName, string sequenceName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images { get { return sequences.Value.Keys; } }
|
||||
|
||||
public bool HasSequence(string unitName)
|
||||
{
|
||||
return sequences.Value.ContainsKey(unitName);
|
||||
}
|
||||
|
||||
public bool HasSequence(string unitName, string sequenceName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
return unitSeq.Value.ContainsKey(sequenceName);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Sequences(string unitName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
return unitSeq.Value.Keys;
|
||||
}
|
||||
|
||||
Sequences Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences)
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var items = new Dictionary<string, UnitSequences>();
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var key = node.Value.ToLines(node.Key).JoinWith("|");
|
||||
|
||||
if (sequenceCache.TryGetValue(key, out var t))
|
||||
items.Add(node.Key, t);
|
||||
else
|
||||
{
|
||||
t = Exts.Lazy(() => modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node));
|
||||
sequenceCache.Add(key, t);
|
||||
items.Add(node.Key, t);
|
||||
}
|
||||
}
|
||||
|
||||
return new ReadOnlyDictionary<string, UnitSequences>(items);
|
||||
}
|
||||
|
||||
public void Preload()
|
||||
{
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Current.CreateBuffer();
|
||||
|
||||
foreach (var unitSeq in sequences.Value.Values)
|
||||
foreach (var seq in unitSeq.Value.Values) { }
|
||||
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Current.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (spriteCache.IsValueCreated)
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
119
OpenRA.Game/Graphics/SequenceSet.cs
Normal file
119
OpenRA.Game/Graphics/SequenceSet.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
#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 OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ISpriteSequence
|
||||
{
|
||||
string Name { get; }
|
||||
int Length { get; }
|
||||
int Facings { get; }
|
||||
int Tick { get; }
|
||||
int ZOffset { get; }
|
||||
int ShadowZOffset { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
float Scale { get; }
|
||||
void ResolveSprites(SpriteCache cache);
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
(Sprite Sprite, WAngle Rotation) GetSpriteWithRotation(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
float GetAlpha(int frame);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
int BgraSheetSize { get; }
|
||||
int IndexedSheetSize { get; }
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public sealed class SequenceSet : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly string tileSet;
|
||||
readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> images;
|
||||
public SpriteCache SpriteCache { get; }
|
||||
|
||||
public SequenceSet(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
SpriteCache = new SpriteCache(fileSystem, modData.SpriteLoaders, modData.SpriteSequenceLoader.BgraSheetSize, modData.SpriteSequenceLoader.IndexedSheetSize);
|
||||
using (new Support.PerfTimer("LoadSequences"))
|
||||
images = Load(fileSystem, additionalSequences);
|
||||
}
|
||||
|
||||
public ISpriteSequence GetSequence(string image, string sequence)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
if (!sequences.TryGetValue(sequence, out var seq))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have a sequence named `{sequence}`.");
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images => images.Keys;
|
||||
|
||||
public bool HasSequence(string image, string sequence)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
return sequences.ContainsKey(sequence);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Sequences(string image)
|
||||
{
|
||||
if (!images.TryGetValue(image, out var sequences))
|
||||
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
|
||||
|
||||
return sequences.Keys;
|
||||
}
|
||||
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences)
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var images = new Dictionary<string, IReadOnlyDictionary<string, ISpriteSequence>>();
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
public void LoadSprites()
|
||||
{
|
||||
SpriteCache.LoadReservations(modData);
|
||||
foreach (var sequences in images.Values)
|
||||
foreach (var sequence in sequences)
|
||||
sequence.Value.ResolveSprites(SpriteCache);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SpriteCache.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -32,7 +32,7 @@ namespace OpenRA.Graphics
|
||||
return data;
|
||||
}
|
||||
|
||||
public bool Buffered { get { return data != null || texture == null; } }
|
||||
public bool Buffered => data != null || texture == null;
|
||||
|
||||
public Sheet(SheetType type, Size size)
|
||||
{
|
||||
@@ -79,21 +79,17 @@ namespace OpenRA.Graphics
|
||||
|
||||
public Png AsPng()
|
||||
{
|
||||
var data = GetData();
|
||||
if (Type == SheetType.Indexed)
|
||||
throw new InvalidOperationException("AsPng() cannot be called on Indexed sheets.");
|
||||
|
||||
// Convert BGRA to RGBA
|
||||
for (var i = 0; i < Size.Width * Size.Height; i++)
|
||||
{
|
||||
var temp = data[i * 4];
|
||||
data[i * 4] = data[i * 4 + 2];
|
||||
data[i * 4 + 2] = temp;
|
||||
}
|
||||
|
||||
return new Png(data, Size.Width, Size.Height);
|
||||
return new Png(GetData(), SpriteFrameType.Bgra32, Size.Width, Size.Height);
|
||||
}
|
||||
|
||||
public Png AsPng(TextureChannel channel, IPalette pal)
|
||||
{
|
||||
if (Type != SheetType.Indexed)
|
||||
throw new InvalidOperationException("AsPng(TextureChannel, IPalette) can only be called on Indexed sheets.");
|
||||
|
||||
var d = GetData();
|
||||
var plane = new byte[Size.Width * Size.Height];
|
||||
var dataStride = 4 * Size.Width;
|
||||
@@ -107,7 +103,7 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
palColors[i] = pal.GetColor(i);
|
||||
|
||||
return new Png(plane, Size.Width, Size.Height, palColors);
|
||||
return new Png(plane, SpriteFrameType.Indexed8, Size.Width, Size.Height, palColors);
|
||||
}
|
||||
|
||||
public void CreateBuffer()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -34,15 +34,16 @@ namespace OpenRA.Graphics
|
||||
public sealed class SheetBuilder : IDisposable
|
||||
{
|
||||
public readonly SheetType Type;
|
||||
readonly List<Sheet> sheets = new List<Sheet>();
|
||||
readonly List<Sheet> sheets = new();
|
||||
readonly Func<Sheet> allocateSheet;
|
||||
readonly int margin;
|
||||
|
||||
Sheet current;
|
||||
TextureChannel channel;
|
||||
int rowHeight = 0;
|
||||
int2 p;
|
||||
|
||||
public Sheet Current { get; private set; }
|
||||
public TextureChannel CurrentChannel { get; private set; }
|
||||
public IEnumerable<Sheet> AllSheets => sheets;
|
||||
|
||||
public static Sheet AllocateSheet(SheetType type, int sheetSize)
|
||||
{
|
||||
return new Sheet(type, new Size(sheetSize, sheetSize));
|
||||
@@ -52,9 +53,16 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case SpriteFrameType.Indexed: return SheetType.Indexed;
|
||||
case SpriteFrameType.BGRA: return SheetType.BGRA;
|
||||
default: throw new NotImplementedException("Unknown SpriteFrameType {0}".F(t));
|
||||
case SpriteFrameType.Indexed8:
|
||||
return SheetType.Indexed;
|
||||
|
||||
// Util.FastCopyIntoChannel will automatically convert these to BGRA
|
||||
case SpriteFrameType.Bgra32:
|
||||
case SpriteFrameType.Bgr24:
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
return SheetType.BGRA;
|
||||
default: throw new NotImplementedException($"Unknown SpriteFrameType {t}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,45 +74,36 @@ namespace OpenRA.Graphics
|
||||
|
||||
public SheetBuilder(SheetType t, Func<Sheet> allocateSheet, int margin = 1)
|
||||
{
|
||||
channel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||
CurrentChannel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||
Type = t;
|
||||
current = allocateSheet();
|
||||
sheets.Add(current);
|
||||
Current = allocateSheet();
|
||||
sheets.Add(Current);
|
||||
this.allocateSheet = allocateSheet;
|
||||
this.margin = margin;
|
||||
}
|
||||
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Size, 0, frame.Offset); }
|
||||
public Sprite Add(byte[] src, Size size) { return Add(src, size, 0, float3.Zero); }
|
||||
public Sprite Add(byte[] src, Size size, float zRamp, in float3 spriteOffset)
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); }
|
||||
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, float zRamp, in float3 spriteOffset)
|
||||
{
|
||||
// Don't bother allocating empty sprites
|
||||
if (size.Width == 0 || size.Height == 0)
|
||||
return new Sprite(current, Rectangle.Empty, 0, spriteOffset, channel, BlendMode.Alpha);
|
||||
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
||||
|
||||
var rect = Allocate(size, zRamp, spriteOffset);
|
||||
Util.FastCopyIntoChannel(rect, src);
|
||||
current.CommitBufferedData();
|
||||
Util.FastCopyIntoChannel(rect, src, type);
|
||||
Current.CommitBufferedData();
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sprite Add(Png src, float scale = 1f)
|
||||
{
|
||||
var rect = Allocate(new Size(src.Width, src.Height), scale);
|
||||
var rect = Allocate(new Size(src.Width, src.Height), scale);
|
||||
Util.FastCopyIntoSprite(rect, src);
|
||||
current.CommitBufferedData();
|
||||
Current.CommitBufferedData();
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sprite Add(Size size, byte paletteIndex)
|
||||
{
|
||||
var data = new byte[size.Width * size.Height];
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
data[i] = paletteIndex;
|
||||
|
||||
return Add(data, size);
|
||||
}
|
||||
|
||||
TextureChannel? NextChannel(TextureChannel t)
|
||||
{
|
||||
var nextChannel = (int)t + (int)Type;
|
||||
@@ -117,7 +116,7 @@ namespace OpenRA.Graphics
|
||||
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
|
||||
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
|
||||
{
|
||||
if (imageSize.Width + p.X + margin > current.Size.Width)
|
||||
if (imageSize.Width + p.X + margin > Current.Size.Width)
|
||||
{
|
||||
p = new int2(0, p.Y + rowHeight + margin);
|
||||
rowHeight = imageSize.Height;
|
||||
@@ -126,33 +125,29 @@ namespace OpenRA.Graphics
|
||||
if (imageSize.Height > rowHeight)
|
||||
rowHeight = imageSize.Height;
|
||||
|
||||
if (p.Y + imageSize.Height + margin > current.Size.Height)
|
||||
if (p.Y + imageSize.Height + margin > Current.Size.Height)
|
||||
{
|
||||
var next = NextChannel(channel);
|
||||
var next = NextChannel(CurrentChannel);
|
||||
if (next == null)
|
||||
{
|
||||
current.ReleaseBuffer();
|
||||
current = allocateSheet();
|
||||
sheets.Add(current);
|
||||
channel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||
Current.ReleaseBuffer();
|
||||
Current = allocateSheet();
|
||||
sheets.Add(Current);
|
||||
CurrentChannel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||
}
|
||||
else
|
||||
channel = next.Value;
|
||||
CurrentChannel = next.Value;
|
||||
|
||||
rowHeight = imageSize.Height;
|
||||
p = int2.Zero;
|
||||
}
|
||||
|
||||
var rect = new Sprite(current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height), zRamp, spriteOffset, channel, 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);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sheet Current { get { return current; } }
|
||||
public TextureChannel CurrentChannel { get { return channel; } }
|
||||
public IEnumerable<Sheet> AllSheets { get { return sheets; } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var sheet in sheets)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -23,7 +23,6 @@ namespace OpenRA.Graphics
|
||||
public readonly float ZRamp;
|
||||
public readonly float3 Size;
|
||||
public readonly float3 Offset;
|
||||
public readonly float3 FractionalOffset;
|
||||
public readonly float Top, Left, Bottom, Right;
|
||||
|
||||
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1)
|
||||
@@ -38,13 +37,16 @@ namespace OpenRA.Graphics
|
||||
Channel = channel;
|
||||
Size = scale * new float3(bounds.Size.Width, bounds.Size.Height, bounds.Size.Height * zRamp);
|
||||
BlendMode = blendMode;
|
||||
FractionalOffset = Size.Z != 0 ? offset / Size :
|
||||
new float3(offset.X / Size.X, offset.Y / Size.Y, 0);
|
||||
|
||||
Left = (float)Math.Min(bounds.Left, bounds.Right) / sheet.Size.Width;
|
||||
Top = (float)Math.Min(bounds.Top, bounds.Bottom) / sheet.Size.Height;
|
||||
Right = (float)Math.Max(bounds.Left, bounds.Right) / sheet.Size.Width;
|
||||
Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) / sheet.Size.Height;
|
||||
// Some GPUs suffer from precision issues when rendering into non 1:1 framebuffers that result
|
||||
// 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
|
||||
// with negligible impact on the 1:1 rendering case.
|
||||
var inset = 1 / 128f;
|
||||
Left = (Math.Min(bounds.Left, bounds.Right) + inset) / sheet.Size.Width;
|
||||
Top = (Math.Min(bounds.Top, bounds.Bottom) + inset) / sheet.Size.Height;
|
||||
Right = (Math.Max(bounds.Left, bounds.Right) - inset) / sheet.Size.Width;
|
||||
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - inset) / sheet.Size.Height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
174
OpenRA.Game/Graphics/SpriteCache.cs
Normal file
174
OpenRA.Game/Graphics/SpriteCache.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
#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.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class SpriteCache : IDisposable
|
||||
{
|
||||
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly IReadOnlyFileSystem fileSystem;
|
||||
|
||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> spriteReservations = new();
|
||||
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> frameReservations = new();
|
||||
readonly Dictionary<string, List<int>> reservationsByFilename = new();
|
||||
|
||||
readonly Dictionary<int, ISpriteFrame[]> resolvedFrames = new();
|
||||
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
|
||||
|
||||
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
|
||||
|
||||
int nextReservationToken = 1;
|
||||
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int bgraSheetSize, int indexedSheetSize, int bgraSheetMargin = 1, int indexedSheetMargin = 1)
|
||||
{
|
||||
SheetBuilders = new Dictionary<SheetType, SheetBuilder>
|
||||
{
|
||||
{ SheetType.Indexed, new SheetBuilder(SheetType.Indexed, indexedSheetSize, indexedSheetMargin) },
|
||||
{ SheetType.BGRA, new SheetBuilder(SheetType.BGRA, bgraSheetSize, bgraSheetMargin) }
|
||||
};
|
||||
|
||||
this.fileSystem = fileSystem;
|
||||
this.loaders = loaders;
|
||||
}
|
||||
|
||||
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
||||
{
|
||||
var token = nextReservationToken++;
|
||||
spriteReservations[token] = (frames?.ToArray(), location);
|
||||
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
||||
return token;
|
||||
}
|
||||
|
||||
public int ReserveFrames(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
||||
{
|
||||
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))
|
||||
return null;
|
||||
|
||||
using (stream)
|
||||
{
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
|
||||
return frames;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadReservations(ModData modData)
|
||||
{
|
||||
foreach (var sb in SheetBuilders.Values)
|
||||
sb.Current.CreateBuffer();
|
||||
|
||||
var spriteCache = new Dictionary<int, Sprite>();
|
||||
foreach (var (filename, tokens) in reservationsByFilename)
|
||||
{
|
||||
modData.LoadScreen?.Display();
|
||||
var loadedFrames = GetFrames(fileSystem, filename, loaders, out _);
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
if (frameReservations.TryGetValue(token, out var r))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedSprites[token] = null;
|
||||
missingFiles[token] = (filename, r.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spriteCache.Clear();
|
||||
}
|
||||
|
||||
spriteReservations.Clear();
|
||||
frameReservations.Clear();
|
||||
reservationsByFilename.Clear();
|
||||
|
||||
foreach (var sb in SheetBuilders.Values)
|
||||
sb.Current.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public Sprite[] ResolveSprites(int token)
|
||||
{
|
||||
var resolved = resolvedSprites[token];
|
||||
resolvedSprites.Remove(token);
|
||||
if (missingFiles.TryGetValue(token, out var r))
|
||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public ISpriteFrame[] ResolveFrames(int token)
|
||||
{
|
||||
var resolved = resolvedFrames[token];
|
||||
resolvedFrames.Remove(token);
|
||||
if (missingFiles.TryGetValue(token, out var r))
|
||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public IEnumerable<(string Filename, MiniYamlNode.SourceLocation Location)> MissingFiles => missingFiles.Values.ToHashSet();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var sb in SheetBuilders.Values)
|
||||
sb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
@@ -18,10 +17,9 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class SpriteFont : IDisposable
|
||||
{
|
||||
public int TopOffset { get; private set; }
|
||||
public int TopOffset { get; }
|
||||
readonly int size;
|
||||
readonly SheetBuilder builder;
|
||||
readonly Func<string, float> lineWidth;
|
||||
readonly IFont font;
|
||||
readonly Cache<char, GlyphInfo> glyphs;
|
||||
readonly Cache<(char C, int Radius), Sprite> contrastGlyphs;
|
||||
@@ -32,7 +30,7 @@ namespace OpenRA.Graphics
|
||||
public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
|
||||
{
|
||||
if (builder.Type != SheetType.BGRA)
|
||||
throw new ArgumentException("The sheet builder must create BGRA sheets.", "builder");
|
||||
throw new ArgumentException("The sheet builder must create BGRA sheets.", nameof(builder));
|
||||
|
||||
deviceScale = scale;
|
||||
this.size = size;
|
||||
@@ -43,13 +41,9 @@ namespace OpenRA.Graphics
|
||||
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
|
||||
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
|
||||
|
||||
// PERF: Cache these delegates for Measure calls.
|
||||
Func<char, float> characterWidth = character => glyphs[character].Advance;
|
||||
lineWidth = line => line.Sum(characterWidth) / deviceScale;
|
||||
|
||||
// Pre-cache small font sizes so glyphs are immediately available when we need them
|
||||
if (size <= 24)
|
||||
using (new PerfTimer("Precache {0} {1}px".F(name, size)))
|
||||
using (new PerfTimer($"Precache {name} {size}px"))
|
||||
for (var n = (char)0x20; n < (char)0x7f; n++)
|
||||
if (glyphs[n] == null)
|
||||
throw new InvalidOperationException();
|
||||
@@ -89,10 +83,10 @@ namespace OpenRA.Graphics
|
||||
if (g.Sprite != null)
|
||||
{
|
||||
var contrastSprite = contrastGlyphs[(s, screenContrast)];
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(contrastSprite,
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(contrastSprite,
|
||||
(screen + g.Offset - contrastVector) / deviceScale,
|
||||
contrastSprite.Size / deviceScale,
|
||||
tint);
|
||||
1f / deviceScale,
|
||||
tint, 1f);
|
||||
}
|
||||
|
||||
screen += new int2((int)(g.Advance + 0.5f), 0);
|
||||
@@ -120,16 +114,16 @@ namespace OpenRA.Graphics
|
||||
|
||||
// Convert screen coordinates back to UI coordinates for drawing
|
||||
if (g.Sprite != null)
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
|
||||
(screen + g.Offset).ToFloat2() / deviceScale,
|
||||
g.Sprite.Size / deviceScale,
|
||||
tint);
|
||||
1f / deviceScale,
|
||||
tint, 1f);
|
||||
|
||||
screen += new int2((int)(g.Advance + 0.5f), 0);
|
||||
}
|
||||
}
|
||||
|
||||
float2 Rotate(float2 v, float sina, float cosa, float2 offset)
|
||||
static float2 Rotate(float2 v, float sina, float cosa, float2 offset)
|
||||
{
|
||||
return new float2(
|
||||
v.X * cosa - v.Y * sina + offset.X,
|
||||
@@ -172,12 +166,12 @@ namespace OpenRA.Graphics
|
||||
|
||||
// Offset rotated glyph to align the top-left corner with the screen pixel grid
|
||||
var screenOffset = new float2((int)(ra.X * deviceScale + 0.5f), (int)(ra.Y * deviceScale + 0.5f)) / deviceScale - ra;
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
|
||||
ra + screenOffset,
|
||||
rb + screenOffset,
|
||||
rc + screenOffset,
|
||||
rd + screenOffset,
|
||||
tint);
|
||||
tint, 1f);
|
||||
}
|
||||
|
||||
p += new float2(g.Advance / deviceScale, 0);
|
||||
@@ -238,16 +232,26 @@ namespace OpenRA.Graphics
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return new int2(0, size);
|
||||
|
||||
var lines = text.Split('\n');
|
||||
return new int2((int)Math.Ceiling(MaxLineWidth(lines, lineWidth)), lines.Length * size);
|
||||
var lines = text.SplitLines('\n');
|
||||
|
||||
var maxWidth = 0f;
|
||||
var rows = 0;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
rows++;
|
||||
maxWidth = Math.Max(maxWidth, LineWidth(line));
|
||||
}
|
||||
|
||||
return new int2((int)Math.Ceiling(maxWidth), rows * size);
|
||||
}
|
||||
|
||||
static float MaxLineWidth(string[] lines, Func<string, float> lineWidth)
|
||||
float LineWidth(ReadOnlySpan<char> line)
|
||||
{
|
||||
var maxWidth = 0f;
|
||||
foreach (var line in lines)
|
||||
maxWidth = Math.Max(maxWidth, lineWidth(line));
|
||||
return maxWidth;
|
||||
var result = 0f;
|
||||
foreach (var c in line)
|
||||
result += glyphs[c].Advance;
|
||||
|
||||
return result / deviceScale;
|
||||
}
|
||||
|
||||
GlyphInfo CreateGlyph(char c)
|
||||
@@ -423,7 +427,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
class GlyphInfo
|
||||
sealed class GlyphInfo
|
||||
{
|
||||
public float Advance;
|
||||
public int2 Offset;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,20 +9,39 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public enum SpriteFrameType { Indexed, BGRA }
|
||||
/// <summary>
|
||||
/// Describes the format of the pixel data in a ISpriteFrame.
|
||||
/// Note that the channel order is defined for little-endian bytes, so BGRA corresponds
|
||||
/// to a 32bit ARGB value, such as that returned by Color.ToArgb().
|
||||
/// </summary>
|
||||
public enum SpriteFrameType
|
||||
{
|
||||
// 8 bit index into an external palette
|
||||
Indexed8,
|
||||
|
||||
// 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!)
|
||||
Bgra32,
|
||||
|
||||
// Like BGRA, but without an alpha channel
|
||||
Bgr24,
|
||||
|
||||
// 32 bit color in big-endian format, like png
|
||||
Rgba32,
|
||||
|
||||
// Like RGBA, but without an alpha channel
|
||||
Rgb24
|
||||
}
|
||||
|
||||
public interface ISpriteLoader
|
||||
{
|
||||
bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata);
|
||||
bool TryParseSprite(Stream s, string filename, out ISpriteFrame[] frames, out TypeDictionary metadata);
|
||||
}
|
||||
|
||||
public interface ISpriteFrame
|
||||
@@ -45,94 +64,6 @@ namespace OpenRA.Graphics
|
||||
bool DisableExportPadding { get; }
|
||||
}
|
||||
|
||||
public class SpriteCache
|
||||
{
|
||||
public readonly Cache<SpriteFrameType, SheetBuilder> SheetBuilders;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly IReadOnlyFileSystem fileSystem;
|
||||
|
||||
readonly Dictionary<string, List<Sprite[]>> sprites = new Dictionary<string, List<Sprite[]>>();
|
||||
readonly Dictionary<string, ISpriteFrame[]> unloadedFrames = new Dictionary<string, ISpriteFrame[]>();
|
||||
readonly Dictionary<string, TypeDictionary> metadata = new Dictionary<string, TypeDictionary>();
|
||||
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
{
|
||||
SheetBuilders = new Cache<SpriteFrameType, SheetBuilder>(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t)));
|
||||
|
||||
this.fileSystem = fileSystem;
|
||||
this.loaders = loaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first set of sprites with the given filename.
|
||||
/// If getUsedFrames is defined then the indices returned by the function call
|
||||
/// are guaranteed to be loaded. The value of other indices in the returned
|
||||
/// array are undefined and should never be accessed.
|
||||
/// </summary>
|
||||
public Sprite[] this[string filename, Func<int, IEnumerable<int>> getUsedFrames = null]
|
||||
{
|
||||
get
|
||||
{
|
||||
var allSprites = sprites.GetOrAdd(filename);
|
||||
var sprite = allSprites.FirstOrDefault();
|
||||
|
||||
if (!unloadedFrames.TryGetValue(filename, out var unloaded))
|
||||
unloaded = null;
|
||||
|
||||
// This is the first time that the file has been requested
|
||||
// Load all of the frames into the unused buffer and initialize
|
||||
// the loaded cache (initially empty)
|
||||
if (sprite == null)
|
||||
{
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata);
|
||||
unloadedFrames[filename] = unloaded;
|
||||
metadata[filename] = fileMetadata;
|
||||
|
||||
sprite = new Sprite[unloaded.Length];
|
||||
allSprites.Add(sprite);
|
||||
}
|
||||
|
||||
// HACK: The sequence code relies on side-effects from getUsedFrames
|
||||
var indices = getUsedFrames != null ? getUsedFrames(sprite.Length) :
|
||||
Enumerable.Range(0, sprite.Length);
|
||||
|
||||
// Load any unused frames into the SheetBuilder
|
||||
if (unloaded != null)
|
||||
{
|
||||
foreach (var i in indices)
|
||||
{
|
||||
if (unloaded[i] != null)
|
||||
{
|
||||
sprite[i] = SheetBuilders[unloaded[i].Type].Add(unloaded[i]);
|
||||
unloaded[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// All frames have been loaded
|
||||
if (unloaded.All(f => f == null))
|
||||
unloadedFrames.Remove(filename);
|
||||
}
|
||||
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a TypeDictionary containing any metadata defined by the frame
|
||||
/// or null if the frame does not define metadata.
|
||||
/// </summary>
|
||||
public TypeDictionary FrameMetadata(string filename)
|
||||
{
|
||||
if (!metadata.TryGetValue(filename, out var fileMetadata))
|
||||
{
|
||||
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
metadata[filename] = fileMetadata;
|
||||
}
|
||||
|
||||
return fileMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
public class FrameCache
|
||||
{
|
||||
readonly Cache<string, ISpriteFrame[]> frames;
|
||||
@@ -142,7 +73,7 @@ namespace OpenRA.Graphics
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _));
|
||||
}
|
||||
|
||||
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
|
||||
public ISpriteFrame[] this[string filename] => frames[filename];
|
||||
}
|
||||
|
||||
public static class FrameLoader
|
||||
@@ -151,7 +82,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
using (var stream = fileSystem.Open(filename))
|
||||
{
|
||||
var spriteFrames = GetFrames(stream, loaders, out metadata);
|
||||
var spriteFrames = GetFrames(stream, loaders, filename, out metadata);
|
||||
if (spriteFrames == null)
|
||||
throw new InvalidDataException(filename + " is not a valid sprite file!");
|
||||
|
||||
@@ -159,12 +90,12 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, out TypeDictionary metadata)
|
||||
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, string filename, out TypeDictionary metadata)
|
||||
{
|
||||
metadata = null;
|
||||
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSprite(stream, out var frames, out metadata))
|
||||
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
|
||||
return frames;
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,79 +9,107 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct SpriteRenderable : IRenderable, ITintableRenderable, IFinalizedRenderable
|
||||
public class SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable
|
||||
{
|
||||
public static readonly IEnumerable<IRenderable> None = new IRenderable[0];
|
||||
public static readonly IEnumerable<IRenderable> None = Array.Empty<IRenderable>();
|
||||
|
||||
readonly Sprite sprite;
|
||||
readonly WPos pos;
|
||||
readonly WVec offset;
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
readonly float scale;
|
||||
readonly float3 tint;
|
||||
readonly bool isDecoration;
|
||||
readonly bool ignoreWorldTint;
|
||||
readonly WAngle rotation = WAngle.Zero;
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
|
||||
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, false) { }
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, bool ignoreWorldTint)
|
||||
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, ignoreWorldTint) { }
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float3 tint, bool isDecoration, bool ignoreWorldTint)
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha,
|
||||
float3 tint, TintModifiers tintModifiers, bool isDecoration, WAngle rotation)
|
||||
{
|
||||
this.sprite = sprite;
|
||||
this.pos = pos;
|
||||
this.offset = offset;
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
Offset = offset;
|
||||
ZOffset = zOffset;
|
||||
Palette = palette;
|
||||
this.scale = scale;
|
||||
this.tint = tint;
|
||||
this.isDecoration = isDecoration;
|
||||
this.ignoreWorldTint = ignoreWorldTint;
|
||||
this.rotation = rotation;
|
||||
Tint = tint;
|
||||
IsDecoration = isDecoration;
|
||||
TintModifiers = tintModifiers;
|
||||
Alpha = alpha;
|
||||
|
||||
// PERF: Remove useless palette assignments for RGBA sprites
|
||||
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
|
||||
// and can be removed once this has been fixed
|
||||
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
|
||||
Palette = null;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos + offset; } }
|
||||
public WVec Offset { get { return offset; } }
|
||||
public PaletteReference Palette { get { return palette; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
public bool IsDecoration { get { return isDecoration; } }
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha,
|
||||
float3 tint, TintModifiers tintModifiers, bool isDecoration)
|
||||
: this(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, WAngle.Zero) { }
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, tint, isDecoration, ignoreWorldTint); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
|
||||
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
|
||||
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, tint, true, ignoreWorldTint); }
|
||||
public WPos Pos => pos + Offset;
|
||||
public WVec Offset { get; }
|
||||
public PaletteReference Palette { get; }
|
||||
public int ZOffset { get; }
|
||||
public bool IsDecoration { get; }
|
||||
|
||||
public IRenderable WithTint(in float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
|
||||
public float Alpha { get; }
|
||||
public float3 Tint { get; }
|
||||
public TintModifiers TintModifiers { get; }
|
||||
|
||||
public IPalettedRenderable WithPalette(PaletteReference newPalette)
|
||||
{
|
||||
return new SpriteRenderable(sprite, pos, Offset, ZOffset, newPalette, scale, Alpha, Tint, TintModifiers, IsDecoration, rotation);
|
||||
}
|
||||
|
||||
public IRenderable WithZOffset(int newOffset)
|
||||
{
|
||||
return new SpriteRenderable(sprite, pos, Offset, newOffset, Palette, scale, Alpha, Tint, TintModifiers, IsDecoration, rotation);
|
||||
}
|
||||
|
||||
public IRenderable OffsetBy(in WVec vec)
|
||||
{
|
||||
return new SpriteRenderable(sprite, pos + vec, Offset, ZOffset, Palette, scale, Alpha, Tint, TintModifiers, IsDecoration, rotation);
|
||||
}
|
||||
|
||||
public IRenderable AsDecoration()
|
||||
{
|
||||
return new SpriteRenderable(sprite, pos, Offset, ZOffset, Palette, scale, Alpha, Tint, TintModifiers, true, rotation);
|
||||
}
|
||||
|
||||
public IModifyableRenderable WithAlpha(float newAlpha)
|
||||
{
|
||||
return new SpriteRenderable(sprite, pos, Offset, ZOffset, Palette, scale, newAlpha, Tint, TintModifiers, IsDecoration, rotation);
|
||||
}
|
||||
|
||||
public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers)
|
||||
{
|
||||
return new SpriteRenderable(sprite, pos, Offset, ZOffset, Palette, scale, Alpha, newTint, newTintModifiers, IsDecoration, rotation);
|
||||
}
|
||||
|
||||
float3 ScreenPosition(WorldRenderer wr)
|
||||
{
|
||||
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset) - (0.5f * scale * sprite.Size.XY).ToInt2();
|
||||
|
||||
// HACK: The z offset needs to be applied somewhere, but this probably is the wrong place.
|
||||
return new float3(xy, sprite.Offset.Z + wr.ScreenZPosition(pos, 0) - 0.5f * scale * sprite.Size.Z);
|
||||
var s = 0.5f * scale * sprite.Size;
|
||||
return wr.Screen3DPxPosition(pos) + wr.ScreenPxOffset(Offset) - new float3((int)s.X, (int)s.Y, s.Z);
|
||||
}
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
var wsr = Game.Renderer.WorldSpriteRenderer;
|
||||
if (ignoreWorldTint)
|
||||
wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
|
||||
else
|
||||
{
|
||||
var t = tint;
|
||||
if (wr.TerrainLighting != null)
|
||||
t *= wr.TerrainLighting.TintAt(pos);
|
||||
var t = Alpha * Tint;
|
||||
if (wr.TerrainLighting != null && (TintModifiers & TintModifiers.IgnoreWorldTint) == 0)
|
||||
t *= wr.TerrainLighting.TintAt(pos);
|
||||
|
||||
wsr.DrawSpriteWithTint(sprite, ScreenPosition(wr), palette, scale * sprite.Size, t);
|
||||
}
|
||||
// Shader interprets negative alpha as a flag to use the tint colour directly instead of multiplying the sprite colour
|
||||
var a = Alpha;
|
||||
if ((TintModifiers & TintModifiers.ReplaceColor) != 0)
|
||||
a *= -1;
|
||||
|
||||
wsr.DrawSprite(sprite, Palette, ScreenPosition(wr), scale, t, a, rotation.RendererRadians());
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
@@ -89,13 +117,16 @@ namespace OpenRA.Graphics
|
||||
var pos = ScreenPosition(wr) + sprite.Offset;
|
||||
var tl = wr.Viewport.WorldToViewPx(pos);
|
||||
var br = wr.Viewport.WorldToViewPx(pos + sprite.Size);
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red);
|
||||
if (rotation == WAngle.Zero)
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red);
|
||||
else
|
||||
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(tl, br - tl, rotation.RendererRadians()), 1, Color.Red);
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr)
|
||||
{
|
||||
var screenOffset = ScreenPosition(wr) + sprite.Offset;
|
||||
return new Rectangle((int)screenOffset.X, (int)screenOffset.Y, (int)sprite.Size.X, (int)sprite.Size.Y);
|
||||
return Util.BoundingRectangle(screenOffset, sprite.Size, rotation.RendererRadians());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,20 +10,20 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class SpriteRenderer : Renderer.IBatchRenderer
|
||||
{
|
||||
const int SheetCount = 7;
|
||||
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => "Texture{0}".F(i));
|
||||
public const int SheetCount = 8;
|
||||
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
|
||||
readonly Vertex[] vertices;
|
||||
Vertex[] vertices;
|
||||
readonly Sheet[] sheets = new Sheet[SheetCount];
|
||||
|
||||
BlendMode currentBlend = BlendMode.Alpha;
|
||||
@@ -34,7 +34,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
vertices = new Vertex[renderer.TempBufferSize];
|
||||
vertices = renderer.Context.CreateVertices(renderer.TempBufferSize);
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
@@ -49,7 +49,9 @@ namespace OpenRA.Graphics
|
||||
|
||||
renderer.Context.SetBlendMode(currentBlend);
|
||||
shader.PrepareRender();
|
||||
renderer.DrawBatch(vertices, nv, PrimitiveType.TriangleList);
|
||||
|
||||
// PERF: The renderer may choose to replace vertices with a different temporary buffer.
|
||||
renderer.DrawBatch(ref vertices, nv, PrimitiveType.TriangleList);
|
||||
renderer.Context.SetBlendMode(BlendMode.None);
|
||||
|
||||
nv = 0;
|
||||
@@ -81,119 +83,170 @@ namespace OpenRA.Graphics
|
||||
for (; secondarySheetIndex < ns; secondarySheetIndex++)
|
||||
if (sheets[secondarySheetIndex] == secondarySheet)
|
||||
break;
|
||||
|
||||
// If neither sheet has been mapped both index values will be set to ns.
|
||||
// This is fine if they both reference the same texture, but if they don't
|
||||
// we must increment the secondary sheet index to the next free sampler.
|
||||
if (secondarySheetIndex == sheetIndex && secondarySheet != sheet)
|
||||
secondarySheetIndex++;
|
||||
}
|
||||
|
||||
// Make sure that we have enough free samplers to map both if needed, otherwise flush
|
||||
var needSamplers = (sheetIndex == ns ? 1 : 0) + (secondarySheetIndex == ns ? 1 : 0);
|
||||
if (ns + needSamplers >= sheets.Length)
|
||||
if (Math.Max(sheetIndex, secondarySheetIndex) >= sheets.Length)
|
||||
{
|
||||
Flush();
|
||||
sheetIndex = 0;
|
||||
if (ss != null)
|
||||
secondarySheetIndex = 1;
|
||||
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
|
||||
}
|
||||
|
||||
if (sheetIndex >= ns)
|
||||
{
|
||||
sheets[sheetIndex] = sheet;
|
||||
ns += 1;
|
||||
ns++;
|
||||
}
|
||||
|
||||
if (secondarySheetIndex >= ns && ss != null)
|
||||
{
|
||||
sheets[secondarySheetIndex] = ss.SecondarySheet;
|
||||
ns += 1;
|
||||
ns++;
|
||||
}
|
||||
|
||||
return new int2(sheetIndex, secondarySheetIndex);
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, in float3 location, float paletteTextureIndex, in float3 size)
|
||||
static float ResolveTextureIndex(Sprite s, PaletteReference pal)
|
||||
{
|
||||
if (pal == null)
|
||||
return 0;
|
||||
|
||||
// PERF: Remove useless palette assignments for RGBA sprites
|
||||
// HACK: This is working around the limitation that palettes are defined on traits rather than on sequences,
|
||||
// and can be removed once this has been fixed
|
||||
if (s.Channel == TextureChannel.RGBA && !pal.HasColorShift)
|
||||
return 0;
|
||||
|
||||
return pal.TextureIndex;
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, float3.Ones);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
|
||||
1f, rotation);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal)
|
||||
{
|
||||
DrawSprite(s, location, pal.TextureIndex, s.Size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal, float3 size)
|
||||
{
|
||||
DrawSprite(s, location, pal.TextureIndex, size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, float3.Ones, nv);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
|
||||
1f, rotation);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
internal void DrawSpriteWithTint(Sprite s, in float3 location, float paletteTextureIndex, in float3 size, in float3 tint)
|
||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
|
||||
{
|
||||
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,
|
||||
float rotation = 0f)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, tint);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha,
|
||||
rotation);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSpriteWithTint(Sprite s, in float3 location, PaletteReference pal, in float3 size, in float3 tint)
|
||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
|
||||
float rotation = 0f)
|
||||
{
|
||||
DrawSpriteWithTint(s, location, pal.TextureIndex, size, tint);
|
||||
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation);
|
||||
}
|
||||
|
||||
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, tint, nv);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode)
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, IEnumerable<Sheet> sheets, BlendMode blendMode)
|
||||
{
|
||||
shader.SetTexture("Texture0", sheet.GetTexture());
|
||||
var i = 0;
|
||||
foreach (var s in sheets)
|
||||
{
|
||||
if (i >= SheetCount)
|
||||
ThrowSheetOverflow(nameof(sheets));
|
||||
|
||||
if (s != null)
|
||||
shader.SetTexture(SheetIndexToTextureName[i++], s.GetTexture());
|
||||
}
|
||||
|
||||
renderer.Context.SetBlendMode(blendMode);
|
||||
shader.PrepareRender();
|
||||
renderer.DrawBatch(buffer, start, length, type);
|
||||
renderer.Context.SetBlendMode(BlendMode.None);
|
||||
}
|
||||
|
||||
// PERF: methods that throw won't be inlined by the JIT, so extract a static helper for use on hot paths
|
||||
static void ThrowSheetOverflow(string paramName)
|
||||
{
|
||||
throw new ArgumentException($"SpriteRenderer only supports {SheetCount} simultaneous textures", paramName);
|
||||
}
|
||||
|
||||
// For RGBAColorRenderer
|
||||
internal void DrawRGBAVertices(Vertex[] v)
|
||||
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (currentBlend != BlendMode.Alpha || nv + v.Length > renderer.TempBufferSize)
|
||||
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
currentBlend = BlendMode.Alpha;
|
||||
currentBlend = blendMode;
|
||||
Array.Copy(v, 0, vertices, nv, v.Length);
|
||||
nv += v.Length;
|
||||
}
|
||||
|
||||
public void SetPalette(ITexture palette)
|
||||
public void SetPalette(ITexture palette, ITexture colorShifts)
|
||||
{
|
||||
shader.SetTexture("Palette", palette);
|
||||
shader.SetTexture("ColorShifts", colorShifts);
|
||||
}
|
||||
|
||||
public void SetViewportParams(Size screen, float depthScale, float depthOffset, int2 scroll)
|
||||
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
|
||||
{
|
||||
shader.SetVec("Scroll", scroll.X, scroll.Y, scroll.Y);
|
||||
shader.SetVec("r1",
|
||||
2f / screen.Width,
|
||||
2f / screen.Height,
|
||||
-depthScale / screen.Height);
|
||||
shader.SetVec("r2", -1, -1, 1 - depthOffset);
|
||||
// Calculate the scale (r1) and offset (r2) that convert from OpenRA viewport pixels
|
||||
// to OpenGL normalized device coordinates (NDC). OpenGL expects coordinates to vary from [-1, 1],
|
||||
// so we rescale viewport pixels to the range [0, 2] using r1 then subtract 1 using r2.
|
||||
var width = 2f / (downscale * sheetSize.Width);
|
||||
var height = 2f / (downscale * sheetSize.Height);
|
||||
|
||||
// Texture index is sampled as a float, so convert to pixels then scale
|
||||
shader.SetVec("DepthTextureScale", 128 * depthScale / screen.Height);
|
||||
// Depth is more complicated:
|
||||
// * The OpenGL z axis is inverted (negative is closer) relative to OpenRA (positive is closer).
|
||||
// * We want to avoid clipping pixels that are behind the nominal z == y plane at the
|
||||
// top of the map, or above the nominal z == y plane at the bottom of the map.
|
||||
// We therefore expand the depth range by an extra margin that is calculated based on
|
||||
// the maximum expected world height (see Renderer.InitializeDepthBuffer).
|
||||
// * Sprites can specify an additional per-pixel depth offset map, which is applied in the
|
||||
// fragment shader. The fragment shader operates in OpenGL window coordinates, not NDC,
|
||||
// with a depth range [0, 1] corresponding to the NDC [-1, 1]. We must therefore multiply the
|
||||
// sprite channel value [0, 1] by 255 to find the pixel depth offset, then by our depth scale
|
||||
// to find the equivalent NDC offset, then divide by 2 to find the window coordinate offset.
|
||||
// * If depthMargin == 0 (which indicates per-pixel depth testing is disabled) sprites that
|
||||
// extend beyond the top of bottom edges of the screen may be pushed outside [-1, 1] and
|
||||
// culled by the GPU. We avoid this by forcing everything into the z = 0 plane.
|
||||
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
|
||||
shader.SetVec("DepthTextureScale", 128 * depth);
|
||||
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
|
||||
shader.SetVec("r1", width, height, -depth);
|
||||
shader.SetVec("r2", -1, -1, depthMargin != 0f ? 1 : 0);
|
||||
}
|
||||
|
||||
public void SetDepthPreviewEnabled(bool enabled)
|
||||
public void SetDepthPreview(bool enabled, float contrast, float offset)
|
||||
{
|
||||
shader.SetBool("EnableDepthPreview", enabled);
|
||||
shader.SetVec("DepthPreviewParams", contrast, offset);
|
||||
}
|
||||
|
||||
public void SetAntialiasingPixelsPerTexel(float pxPerTx)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -15,14 +15,14 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct TargetLineRenderable : IRenderable, IFinalizedRenderable
|
||||
public class TargetLineRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly IEnumerable<WPos> waypoints;
|
||||
readonly Color color;
|
||||
readonly int width;
|
||||
readonly int markerSize;
|
||||
|
||||
public TargetLineRenderable(IEnumerable<WPos> waypoints, Color color, int width = 1, int markerSize = 1)
|
||||
public TargetLineRenderable(IEnumerable<WPos> waypoints, Color color, int width, int markerSize)
|
||||
{
|
||||
this.waypoints = waypoints;
|
||||
this.color = color;
|
||||
@@ -30,14 +30,19 @@ namespace OpenRA.Graphics
|
||||
this.markerSize = markerSize;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return waypoints.First(); } }
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return 0; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
public WPos Pos => waypoints.First();
|
||||
public int ZOffset => 0;
|
||||
public bool IsDecoration => true;
|
||||
|
||||
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||
|
||||
public IRenderable OffsetBy(in WVec vec)
|
||||
{
|
||||
// Lambdas can't use 'in' variables, so capture a copy for later
|
||||
var offset = vec;
|
||||
return new TargetLineRenderable(waypoints.Select(w => w + offset), color, width, markerSize);
|
||||
}
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new TargetLineRenderable(waypoints, color); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new TargetLineRenderable(waypoints, color); }
|
||||
public IRenderable OffsetBy(WVec vec) { return new TargetLineRenderable(waypoints.Select(w => w + vec), color); }
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
@@ -51,14 +56,14 @@ namespace OpenRA.Graphics
|
||||
foreach (var b in waypoints.Skip(1).Select(pos => wr.Viewport.WorldToViewPx(wr.Screen3DPosition(pos))))
|
||||
{
|
||||
Game.Renderer.RgbaColorRenderer.DrawLine(a, b, width, color);
|
||||
DrawTargetMarker(wr, color, b, markerSize);
|
||||
DrawTargetMarker(color, b, markerSize);
|
||||
a = b;
|
||||
}
|
||||
|
||||
DrawTargetMarker(wr, color, first);
|
||||
DrawTargetMarker(color, first);
|
||||
}
|
||||
|
||||
public static void DrawTargetMarker(WorldRenderer wr, Color color, int2 screenPos, int size = 1)
|
||||
public static void DrawTargetMarker(Color color, int2 screenPos, int size = 1)
|
||||
{
|
||||
var offset = new int2(size, size);
|
||||
var tl = screenPos - offset;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -12,7 +12,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -20,37 +19,37 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
public readonly Sheet Sheet;
|
||||
public readonly BlendMode BlendMode;
|
||||
|
||||
readonly Sheet[] sheets;
|
||||
readonly Sprite emptySprite;
|
||||
|
||||
readonly IVertexBuffer<Vertex> vertexBuffer;
|
||||
readonly Vertex[] vertices;
|
||||
readonly bool[] ignoreTint;
|
||||
readonly HashSet<int> dirtyRows = new HashSet<int>();
|
||||
readonly HashSet<int> dirtyRows = new();
|
||||
readonly int rowStride;
|
||||
readonly bool restrictToBounds;
|
||||
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly Map map;
|
||||
|
||||
readonly PaletteReference palette;
|
||||
readonly PaletteReference[] palettes;
|
||||
|
||||
public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, PaletteReference palette, bool restrictToBounds)
|
||||
public TerrainSpriteLayer(World world, WorldRenderer wr, Sprite emptySprite, BlendMode blendMode, bool restrictToBounds)
|
||||
{
|
||||
worldRenderer = wr;
|
||||
this.restrictToBounds = restrictToBounds;
|
||||
Sheet = sheet;
|
||||
this.emptySprite = emptySprite;
|
||||
sheets = new Sheet[SpriteRenderer.SheetCount];
|
||||
BlendMode = blendMode;
|
||||
this.palette = palette;
|
||||
|
||||
map = world.Map;
|
||||
rowStride = 6 * map.MapSize.X;
|
||||
|
||||
vertices = new Vertex[rowStride * map.MapSize.Y];
|
||||
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
|
||||
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
|
||||
emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha);
|
||||
|
||||
wr.PaletteInvalidated += UpdatePaletteIndices;
|
||||
|
||||
@@ -63,12 +62,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
void UpdatePaletteIndices()
|
||||
{
|
||||
// Everything in the layer uses the same palette,
|
||||
// so we can fix the indices in one pass
|
||||
for (var i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
var v = vertices[i];
|
||||
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, v.R, v.G, v.B);
|
||||
var p = palettes[i / 6]?.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);
|
||||
}
|
||||
|
||||
for (var row = 0; row < map.MapSize.Y; row++)
|
||||
@@ -77,24 +75,24 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void Clear(CPos cell)
|
||||
{
|
||||
Update(cell, null, true);
|
||||
Update(cell, null, null, 1f, 1f, true);
|
||||
}
|
||||
|
||||
public void Update(CPos cell, ISpriteSequence sequence, int frame)
|
||||
public void Update(CPos cell, ISpriteSequence sequence, PaletteReference palette, int frame)
|
||||
{
|
||||
Update(cell, sequence.GetSprite(frame), sequence.IgnoreWorldTint);
|
||||
Update(cell, sequence.GetSprite(frame), palette, sequence.Scale, sequence.GetAlpha(frame), sequence.IgnoreWorldTint);
|
||||
}
|
||||
|
||||
public void Update(CPos cell, Sprite sprite, bool ignoreTint)
|
||||
public void Update(CPos cell, Sprite sprite, PaletteReference palette, float scale = 1f, float alpha = 1f, bool ignoreTint = false)
|
||||
{
|
||||
var xyz = float3.Zero;
|
||||
if (sprite != null)
|
||||
{
|
||||
var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset);
|
||||
xyz = worldRenderer.Screen3DPosition(cellOrigin) + sprite.Offset - 0.5f * sprite.Size;
|
||||
xyz = worldRenderer.Screen3DPosition(cellOrigin) + scale * (sprite.Offset - 0.5f * sprite.Size);
|
||||
}
|
||||
|
||||
Update(cell.ToMPos(map.Grid.Type), sprite, xyz, ignoreTint);
|
||||
Update(cell.ToMPos(map.Grid.Type), sprite, palette, xyz, scale, alpha, ignoreTint);
|
||||
}
|
||||
|
||||
void UpdateTint(MPos uv)
|
||||
@@ -102,11 +100,10 @@ namespace OpenRA.Graphics
|
||||
var offset = rowStride * uv.V + 6 * uv.U;
|
||||
if (ignoreTint[offset])
|
||||
{
|
||||
var noTint = float3.Ones;
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var v = vertices[offset + i];
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, noTint);
|
||||
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);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -117,7 +114,7 @@ namespace OpenRA.Graphics
|
||||
// transparent for isometric tiles
|
||||
var tl = worldRenderer.TerrainLighting;
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map));
|
||||
var step = map.Grid.Type == MapGridType.RectangularIsometric ? 724 : 512;
|
||||
var step = map.Grid.TileScale / 2;
|
||||
var weights = new[]
|
||||
{
|
||||
tl.TintAt(pos + new WVec(-step, -step, 0)),
|
||||
@@ -131,31 +128,61 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var v = vertices[offset + i];
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, weights[CornerVertexMap[i]]);
|
||||
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);
|
||||
}
|
||||
|
||||
dirtyRows.Add(uv.V);
|
||||
}
|
||||
|
||||
public void Update(MPos uv, Sprite sprite, in float3 pos, bool ignoreTint)
|
||||
int GetOrAddSheetIndex(Sheet sheet)
|
||||
{
|
||||
if (sheet == null)
|
||||
return 0;
|
||||
|
||||
for (var i = 0; i < sheets.Length; i++)
|
||||
{
|
||||
if (sheets[i] == sheet)
|
||||
return i;
|
||||
|
||||
if (sheets[i] == null)
|
||||
{
|
||||
sheets[i] = sheet;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidDataException("Sheet overflow");
|
||||
}
|
||||
|
||||
public void Update(MPos uv, Sprite sprite, PaletteReference palette, in float3 pos, float scale, float alpha, bool ignoreTint)
|
||||
{
|
||||
int2 samplers;
|
||||
if (sprite != null)
|
||||
{
|
||||
if (sprite.Sheet != Sheet)
|
||||
throw new InvalidDataException("Attempted to add sprite from a different sheet");
|
||||
|
||||
if (sprite.BlendMode != BlendMode)
|
||||
throw new InvalidDataException("Attempted to add sprite with a different blend mode");
|
||||
|
||||
samplers = new int2(GetOrAddSheetIndex(sprite.Sheet), GetOrAddSheetIndex((sprite as SpriteWithSecondaryData)?.SecondarySheet));
|
||||
|
||||
// PERF: Remove useless palette assignments for RGBA sprites
|
||||
// HACK: This is working around the limitation that palettes are defined on traits rather than on sequences,
|
||||
// and can be removed once this has been fixed
|
||||
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
|
||||
palette = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite = emptySprite;
|
||||
samplers = int2.Zero;
|
||||
}
|
||||
|
||||
// The vertex buffer does not have geometry for cells outside the map
|
||||
if (!map.Tiles.Contains(uv))
|
||||
return;
|
||||
|
||||
var offset = rowStride * uv.V + 6 * uv.U;
|
||||
Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size, float3.Ones);
|
||||
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;
|
||||
|
||||
if (worldRenderer.TerrainLighting != null)
|
||||
{
|
||||
@@ -188,7 +215,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
|
||||
PrimitiveType.TriangleList, Sheet, BlendMode);
|
||||
PrimitiveType.TriangleList, sheets, BlendMode);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
}
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
class TheaterTemplate
|
||||
{
|
||||
public readonly Sprite[] Sprites;
|
||||
public readonly int Stride;
|
||||
public readonly int Variants;
|
||||
|
||||
public TheaterTemplate(Sprite[] sprites, int stride, int variants)
|
||||
{
|
||||
Sprites = sprites;
|
||||
Stride = stride;
|
||||
Variants = variants;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Theater : IDisposable
|
||||
{
|
||||
readonly Dictionary<ushort, TheaterTemplate> templates = new Dictionary<ushort, TheaterTemplate>();
|
||||
SheetBuilder sheetBuilder;
|
||||
readonly Sprite missingTile;
|
||||
readonly MersenneTwister random;
|
||||
TileSet tileset;
|
||||
|
||||
public Theater(TileSet tileset, Action<uint, string> onMissingImage = null)
|
||||
{
|
||||
this.tileset = tileset;
|
||||
var allocated = false;
|
||||
|
||||
Func<Sheet> allocate = () =>
|
||||
{
|
||||
if (allocated)
|
||||
throw new SheetOverflowException("Terrain sheet overflow. Try increasing the tileset SheetSize parameter.");
|
||||
allocated = true;
|
||||
|
||||
return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize));
|
||||
};
|
||||
|
||||
random = new MersenneTwister();
|
||||
|
||||
var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders);
|
||||
foreach (var t in tileset.Templates)
|
||||
{
|
||||
var variants = new List<Sprite[]>();
|
||||
|
||||
foreach (var i in t.Value.Images)
|
||||
{
|
||||
ISpriteFrame[] allFrames;
|
||||
if (onMissingImage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
allFrames = frameCache[i];
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
onMissingImage(t.Key, i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
allFrames = frameCache[i];
|
||||
|
||||
var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length;
|
||||
var indices = t.Value.Frames != null ? t.Value.Frames : Exts.MakeArray(t.Value.TilesCount, j => j);
|
||||
|
||||
var start = indices.Min();
|
||||
var end = indices.Max();
|
||||
if (start < 0 || end >= frameCount)
|
||||
throw new YamlException("Template `{0}` uses frames [{1}..{2}] of {3}, but only [0..{4}] actually exist"
|
||||
.F(t.Key, start, end, i, frameCount - 1));
|
||||
|
||||
variants.Add(indices.Select(j =>
|
||||
{
|
||||
var f = allFrames[j];
|
||||
var tile = t.Value.Contains(j) ? t.Value[j] : null;
|
||||
|
||||
// The internal z axis is inverted from expectation (negative is closer)
|
||||
var zOffset = tile != null ? -tile.ZOffset : 0;
|
||||
var zRamp = tile != null ? tile.ZRamp : 1f;
|
||||
var offset = new float3(f.Offset, zOffset);
|
||||
var type = SheetBuilder.FrameTypeToSheetType(f.Type);
|
||||
|
||||
// Defer SheetBuilder creation until we know what type of frames we are loading!
|
||||
// TODO: Support mixed indexed and BGRA frames
|
||||
if (sheetBuilder == null)
|
||||
sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate);
|
||||
else if (type != sheetBuilder.Type)
|
||||
throw new YamlException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
|
||||
|
||||
var s = sheetBuilder.Allocate(f.Size, zRamp, offset);
|
||||
Util.FastCopyIntoChannel(s, f.Data);
|
||||
|
||||
if (tileset.EnableDepth)
|
||||
{
|
||||
var ss = sheetBuilder.Allocate(f.Size, zRamp, offset);
|
||||
Util.FastCopyIntoChannel(ss, allFrames[j + frameCount].Data);
|
||||
|
||||
// s and ss are guaranteed to use the same sheet
|
||||
// because of the custom terrain sheet allocation
|
||||
s = new SpriteWithSecondaryData(s, s.Sheet, ss.Bounds, ss.Channel);
|
||||
}
|
||||
|
||||
return s;
|
||||
}).ToArray());
|
||||
}
|
||||
|
||||
var allSprites = variants.SelectMany(s => s);
|
||||
|
||||
// Ignore the offsets baked into R8 sprites
|
||||
if (tileset.IgnoreTileSpriteOffsets)
|
||||
allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode));
|
||||
|
||||
if (onMissingImage != null && !variants.Any())
|
||||
continue;
|
||||
|
||||
templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length));
|
||||
}
|
||||
|
||||
// 1x1px transparent tile
|
||||
missingTile = sheetBuilder.Add(new byte[sheetBuilder.Type == SheetType.BGRA ? 4 : 1], new Size(1, 1));
|
||||
|
||||
Sheet.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public bool HasTileSprite(TerrainTile r, int? variant = null)
|
||||
{
|
||||
return TileSprite(r, variant) != missingTile;
|
||||
}
|
||||
|
||||
public Sprite TileSprite(TerrainTile r, int? variant = null)
|
||||
{
|
||||
if (!templates.TryGetValue(r.Type, out var template))
|
||||
return missingTile;
|
||||
|
||||
if (r.Index >= template.Stride)
|
||||
return missingTile;
|
||||
|
||||
var start = template.Variants > 1 ? variant.HasValue ? variant.Value : random.Next(template.Variants) : 0;
|
||||
return template.Sprites[start * template.Stride + r.Index];
|
||||
}
|
||||
|
||||
public Rectangle TemplateBounds(TerrainTemplateInfo template, Size tileSize, MapGridType mapGrid)
|
||||
{
|
||||
Rectangle? templateRect = null;
|
||||
|
||||
var i = 0;
|
||||
for (var y = 0; y < template.Size.Y; y++)
|
||||
{
|
||||
for (var x = 0; x < template.Size.X; x++)
|
||||
{
|
||||
var tile = new TerrainTile(template.Id, (byte)(i++));
|
||||
var tileInfo = tileset.GetTileInfo(tile);
|
||||
|
||||
// Empty tile
|
||||
if (tileInfo == null)
|
||||
continue;
|
||||
|
||||
var sprite = TileSprite(tile);
|
||||
var u = mapGrid == MapGridType.Rectangular ? x : (x - y) / 2f;
|
||||
var v = mapGrid == MapGridType.Rectangular ? y : (x + y) / 2f;
|
||||
|
||||
var tl = new float2(u * tileSize.Width, (v - 0.5f * tileInfo.Height) * tileSize.Height) - 0.5f * sprite.Size;
|
||||
var rect = new Rectangle((int)(tl.X + sprite.Offset.X), (int)(tl.Y + sprite.Offset.Y), (int)sprite.Size.X, (int)sprite.Size.Y);
|
||||
templateRect = templateRect.HasValue ? Rectangle.Union(templateRect.Value, rect) : rect;
|
||||
}
|
||||
}
|
||||
|
||||
return templateRect.HasValue ? templateRect.Value : Rectangle.Empty;
|
||||
}
|
||||
|
||||
public Sheet Sheet { get { return sheetBuilder.Current; } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -13,54 +13,64 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct UISpriteRenderable : IRenderable, IFinalizedRenderable
|
||||
public class UISpriteRenderable : IRenderable, IPalettedRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly Sprite sprite;
|
||||
readonly WPos effectiveWorldPos;
|
||||
readonly int2 screenPos;
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
readonly float scale;
|
||||
readonly float alpha;
|
||||
readonly float rotation = 0f;
|
||||
|
||||
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale)
|
||||
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.effectiveWorldPos = effectiveWorldPos;
|
||||
Pos = effectiveWorldPos;
|
||||
this.screenPos = screenPos;
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
ZOffset = zOffset;
|
||||
Palette = palette;
|
||||
this.scale = scale;
|
||||
this.alpha = alpha;
|
||||
this.rotation = rotation;
|
||||
|
||||
// PERF: Remove useless palette assignments for RGBA sprites
|
||||
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
|
||||
// and can be removed once this has been fixed
|
||||
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
|
||||
Palette = null;
|
||||
}
|
||||
|
||||
// Does not exist in the world, so a world positions don't make sense
|
||||
public WPos Pos { get { return effectiveWorldPos; } }
|
||||
public WVec Offset { get { return WVec.Zero; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
public WPos Pos { get; }
|
||||
public WVec Offset => WVec.Zero;
|
||||
public bool IsDecoration => true;
|
||||
|
||||
public PaletteReference Palette { get { return palette; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
public PaletteReference Palette { get; }
|
||||
public int ZOffset { get; }
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale); }
|
||||
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, Pos, screenPos, ZOffset, newPalette, scale, alpha, rotation); }
|
||||
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||
public IRenderable OffsetBy(WVec vec) { return this; }
|
||||
public IRenderable OffsetBy(in WVec vec) { return this; }
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
Game.Renderer.SpriteRenderer.DrawSprite(sprite, screenPos, palette, scale * sprite.Size);
|
||||
Game.Renderer.SpriteRenderer.DrawSprite(sprite, Palette, screenPos, scale, float3.Ones, alpha, rotation);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
{
|
||||
var offset = screenPos + sprite.Offset.XY;
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red);
|
||||
if (rotation == 0f)
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red);
|
||||
else
|
||||
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(offset, sprite.Size, rotation), 1, Color.Red);
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr)
|
||||
{
|
||||
var offset = screenPos + sprite.Offset;
|
||||
return new Rectangle((int)offset.X, (int)offset.Y, (int)sprite.Size.X, (int)sprite.Size.Y);
|
||||
return Util.BoundingRectangle(offset, sprite.Size, rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -20,29 +20,60 @@ namespace OpenRA.Graphics
|
||||
// yes, our channel order is nuts.
|
||||
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, in float3 size, in float3 tint)
|
||||
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv,
|
||||
in float3 size, in float3 tint, float alpha, float rotation = 0f)
|
||||
{
|
||||
var b = new float3(o.X + size.X, o.Y, o.Z);
|
||||
var c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
|
||||
var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
|
||||
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, nv);
|
||||
float3 a, b, c, d;
|
||||
|
||||
// Rotate sprite if rotation angle is not equal to 0
|
||||
if (rotation != 0f)
|
||||
{
|
||||
var center = o + 0.5f * size;
|
||||
var angleSin = (float)Math.Sin(-rotation);
|
||||
var angleCos = (float)Math.Cos(-rotation);
|
||||
|
||||
// Rotated offset for +/- x with +/- y
|
||||
var ra = 0.5f * new float3(
|
||||
size.X * angleCos - size.Y * angleSin,
|
||||
size.X * angleSin + size.Y * angleCos,
|
||||
(size.X * angleSin + size.Y * angleCos) * size.Z / size.Y);
|
||||
|
||||
// Rotated offset for +/- x with -/+ y
|
||||
var rb = 0.5f * new float3(
|
||||
size.X * angleCos + size.Y * angleSin,
|
||||
size.X * angleSin - size.Y * angleCos,
|
||||
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
|
||||
|
||||
a = center - ra;
|
||||
b = center + rb;
|
||||
c = center + ra;
|
||||
d = center - rb;
|
||||
}
|
||||
else
|
||||
{
|
||||
a = o;
|
||||
b = new float3(o.X + size.X, o.Y, o.Z);
|
||||
c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
|
||||
d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
|
||||
}
|
||||
|
||||
FastCreateQuad(vertices, a, b, c, d, r, samplers, paletteTextureIndex, tint, alpha, nv);
|
||||
}
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices,
|
||||
in float3 a, in float3 b, in float3 c, in float3 d,
|
||||
Sprite r, int2 samplers, float paletteTextureIndex,
|
||||
in float3 tint, int nv)
|
||||
in float3 tint, float alpha, int nv)
|
||||
{
|
||||
float sl = 0;
|
||||
float st = 0;
|
||||
float sr = 0;
|
||||
float sb = 0;
|
||||
|
||||
// See shp.vert for documentation on the channel attribute format
|
||||
// See combined.vert for documentation on the channel attribute format
|
||||
var attribC = r.Channel == TextureChannel.RGBA ? 0x02 : ((byte)r.Channel) << 1 | 0x01;
|
||||
attribC |= samplers.X << 6;
|
||||
var ss = r as SpriteWithSecondaryData;
|
||||
if (ss != null)
|
||||
if (r is SpriteWithSecondaryData ss)
|
||||
{
|
||||
sl = ss.SecondaryLeft;
|
||||
st = ss.SecondaryTop;
|
||||
@@ -54,15 +85,15 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
var fAttribC = (float)attribC;
|
||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
|
||||
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);
|
||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
}
|
||||
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src)
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType)
|
||||
{
|
||||
var destData = dest.Sheet.GetData();
|
||||
var width = dest.Bounds.Width;
|
||||
@@ -85,12 +116,34 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
var r = src[k++];
|
||||
var g = src[k++];
|
||||
var b = src[k++];
|
||||
var a = src[k++];
|
||||
var cc = Color.FromArgb(a, r, g, b);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -139,16 +192,29 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
Color cc;
|
||||
if (src.Palette == null)
|
||||
switch (src.Type)
|
||||
{
|
||||
var r = src.Data[k++];
|
||||
var g = src.Data[k++];
|
||||
var b = src.Data[k++];
|
||||
var a = src.Data[k++];
|
||||
cc = Color.FromArgb(a, r, g, b);
|
||||
case SpriteFrameType.Indexed8:
|
||||
{
|
||||
cc = src.Palette[src.Data[k++]];
|
||||
break;
|
||||
}
|
||||
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
{
|
||||
var r = src.Data[k++];
|
||||
var g = src.Data[k++];
|
||||
var b = src.Data[k++];
|
||||
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255;
|
||||
cc = Color.FromArgb(a, r, g, b);
|
||||
break;
|
||||
}
|
||||
|
||||
// Pngs don't support BGR[A], so no need to include them here
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
|
||||
}
|
||||
else
|
||||
cc = src.Palette[src.Data[k++]];
|
||||
|
||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
||||
}
|
||||
@@ -157,6 +223,69 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Rotates a quad about its center in the x-y plane.</summary>
|
||||
/// <param name="tl">The top left vertex of the quad.</param>
|
||||
/// <param name="size">A float3 containing the X, Y, and Z lengths of the quad.</param>
|
||||
/// <param name="rotation">The number of radians to rotate by.</param>
|
||||
/// <returns>An array of four vertices representing the rotated quad (top-left, top-right, bottom-right, bottom-left).</returns>
|
||||
public static float3[] RotateQuad(float3 tl, float3 size, float rotation)
|
||||
{
|
||||
var center = tl + 0.5f * size;
|
||||
var angleSin = (float)Math.Sin(-rotation);
|
||||
var angleCos = (float)Math.Cos(-rotation);
|
||||
|
||||
// Rotated offset for +/- x with +/- y
|
||||
var ra = 0.5f * new float3(
|
||||
size.X * angleCos - size.Y * angleSin,
|
||||
size.X * angleSin + size.Y * angleCos,
|
||||
(size.X * angleSin + size.Y * angleCos) * size.Z / size.Y);
|
||||
|
||||
// Rotated offset for +/- x with -/+ y
|
||||
var rb = 0.5f * new float3(
|
||||
size.X * angleCos + size.Y * angleSin,
|
||||
size.X * angleSin - size.Y * angleCos,
|
||||
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
|
||||
|
||||
return new float3[]
|
||||
{
|
||||
center - ra,
|
||||
center + rb,
|
||||
center + ra,
|
||||
center - rb
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the bounds of an object. Used for determining which objects need to be rendered on screen, and which do not.
|
||||
/// </summary>
|
||||
/// <param name="offset">The top left vertex of the object.</param>
|
||||
/// <param name="size">A float 3 containing the X, Y, and Z lengths of the object.</param>
|
||||
/// <param name="rotation">The angle to rotate the object by (use 0f if there is no rotation).</param>
|
||||
public static Rectangle BoundingRectangle(float3 offset, float3 size, float rotation)
|
||||
{
|
||||
if (rotation == 0f)
|
||||
return new Rectangle((int)offset.X, (int)offset.Y, (int)size.X, (int)size.Y);
|
||||
|
||||
var rotatedQuad = RotateQuad(offset, size, rotation);
|
||||
var minX = rotatedQuad[0].X;
|
||||
var maxX = rotatedQuad[0].X;
|
||||
var minY = rotatedQuad[0].Y;
|
||||
var maxY = rotatedQuad[0].Y;
|
||||
for (var i = 1; i < rotatedQuad.Length; i++)
|
||||
{
|
||||
minX = Math.Min(rotatedQuad[i].X, minX);
|
||||
maxX = Math.Max(rotatedQuad[i].X, maxX);
|
||||
minY = Math.Min(rotatedQuad[i].Y, minY);
|
||||
maxY = Math.Max(rotatedQuad[i].Y, maxY);
|
||||
}
|
||||
|
||||
return new Rectangle(
|
||||
(int)minX,
|
||||
(int)minY,
|
||||
(int)Math.Ceiling(maxX) - (int)minX,
|
||||
(int)Math.Ceiling(maxY) - (int)minY);
|
||||
}
|
||||
|
||||
public static Color PremultiplyAlpha(Color c)
|
||||
{
|
||||
if (c.A == byte.MaxValue)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -14,7 +14,7 @@ using System.Runtime.InteropServices;
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex
|
||||
public readonly struct Vertex
|
||||
{
|
||||
// 3d position
|
||||
public readonly float X, Y, Z;
|
||||
@@ -26,24 +26,24 @@ namespace OpenRA.Graphics
|
||||
public readonly float P, C;
|
||||
|
||||
// Color tint
|
||||
public readonly float R, G, B;
|
||||
public readonly float R, G, B, A;
|
||||
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones) { }
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones, 1f) { }
|
||||
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float 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) { }
|
||||
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint)
|
||||
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
|
||||
: this(x, y, z, s, t, u, v, p, 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)
|
||||
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)
|
||||
{
|
||||
X = x; Y = y; Z = z;
|
||||
S = s; T = t;
|
||||
U = u; V = v;
|
||||
P = p; C = c;
|
||||
R = r; G = g; B = b;
|
||||
R = r; G = g; B = b; A = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
OpenRA.Game/Graphics/Video.cs
Normal file
36
OpenRA.Game/Graphics/Video.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
#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
|
||||
|
||||
namespace OpenRA.Video
|
||||
{
|
||||
public interface IVideo
|
||||
{
|
||||
ushort FrameCount { get; }
|
||||
byte Framerate { get; }
|
||||
ushort Width { get; }
|
||||
ushort Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current frame color data in 32-bit BGRA.
|
||||
/// </summary>
|
||||
byte[] CurrentFrameData { get; }
|
||||
int CurrentFrameIndex { get; }
|
||||
void AdvanceFrame();
|
||||
|
||||
bool HasAudio { get; }
|
||||
byte[] AudioData { get; }
|
||||
int AudioChannels { get; }
|
||||
int SampleBits { get; }
|
||||
int SampleRate { get; }
|
||||
|
||||
void Reset();
|
||||
}
|
||||
}
|
||||
32
OpenRA.Game/Graphics/VideoLoader.cs
Normal file
32
OpenRA.Game/Graphics/VideoLoader.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#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;
|
||||
|
||||
namespace OpenRA.Video
|
||||
{
|
||||
public interface IVideoLoader
|
||||
{
|
||||
bool TryParseVideo(Stream s, bool useFramePadding, out IVideo video);
|
||||
}
|
||||
|
||||
public static class VideoLoader
|
||||
{
|
||||
public static IVideo GetVideo(Stream stream, bool useFramePadding, IVideoLoader[] loaders)
|
||||
{
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseVideo(stream, useFramePadding, out var video))
|
||||
return video;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -51,11 +51,11 @@ namespace OpenRA.Graphics
|
||||
// Viewport geometry (world-px)
|
||||
public int2 CenterLocation { get; private set; }
|
||||
|
||||
public WPos CenterPosition { get { return worldRenderer.ProjectedPosition(CenterLocation); } }
|
||||
public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation);
|
||||
|
||||
public Rectangle Rectangle { get { return new Rectangle(TopLeft, new Size(viewportSize.X, viewportSize.Y)); } }
|
||||
public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } }
|
||||
public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } }
|
||||
public Rectangle Rectangle => new(TopLeft, new Size(viewportSize.X, viewportSize.Y));
|
||||
public int2 TopLeft => CenterLocation - viewportSize / 2;
|
||||
public int2 BottomRight => CenterLocation + viewportSize / 2;
|
||||
int2 viewportSize;
|
||||
ProjectedCellRegion cells;
|
||||
bool cellsDirty = true;
|
||||
@@ -66,19 +66,13 @@ namespace OpenRA.Graphics
|
||||
WorldViewport lastViewportDistance;
|
||||
|
||||
float zoom = 1f;
|
||||
float minZoom = 1f;
|
||||
float maxZoom = 2f;
|
||||
|
||||
bool unlockMinZoom;
|
||||
float unlockedMinZoomScale;
|
||||
float unlockedMinZoom = 1f;
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get
|
||||
{
|
||||
return zoom;
|
||||
}
|
||||
get => zoom;
|
||||
|
||||
private set
|
||||
{
|
||||
@@ -89,12 +83,13 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public float MinZoom { get { return minZoom; } }
|
||||
public float MinZoom { get; private set; } = 1f;
|
||||
public float MaxZoom { get; private set; } = 2f;
|
||||
|
||||
public void AdjustZoom(float dz)
|
||||
{
|
||||
// Exponential ensures that equal positive and negative steps have the same effect
|
||||
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : minZoom, maxZoom);
|
||||
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : MinZoom, MaxZoom);
|
||||
}
|
||||
|
||||
public void AdjustZoom(float dz, int2 center)
|
||||
@@ -108,10 +103,10 @@ namespace OpenRA.Graphics
|
||||
public void ToggleZoom()
|
||||
{
|
||||
// Unlocked zooms always reset to the default zoom
|
||||
if (zoom < minZoom)
|
||||
Zoom = minZoom;
|
||||
if (zoom < MinZoom)
|
||||
Zoom = MinZoom;
|
||||
else
|
||||
Zoom = zoom > minZoom ? minZoom : maxZoom;
|
||||
Zoom = zoom > MinZoom ? MinZoom : MaxZoom;
|
||||
}
|
||||
|
||||
public void UnlockMinimumZoom(float scale)
|
||||
@@ -124,23 +119,6 @@ namespace OpenRA.Graphics
|
||||
public static long LastMoveRunTime = 0;
|
||||
public static int2 LastMousePos;
|
||||
|
||||
float ClosestTo(float[] collection, float target)
|
||||
{
|
||||
var closestValue = collection.First();
|
||||
var subtractResult = Math.Abs(closestValue - target);
|
||||
|
||||
foreach (var element in collection)
|
||||
{
|
||||
if (Math.Abs(element - target) < subtractResult)
|
||||
{
|
||||
subtractResult = Math.Abs(element - target);
|
||||
closestValue = element;
|
||||
}
|
||||
}
|
||||
|
||||
return closestValue;
|
||||
}
|
||||
|
||||
public ScrollDirection GetBlockedDirections()
|
||||
{
|
||||
var ret = ScrollDirection.None;
|
||||
@@ -194,7 +172,7 @@ namespace OpenRA.Graphics
|
||||
UpdateViewportZooms();
|
||||
}
|
||||
|
||||
float CalculateMinimumZoom(float minHeight, float maxHeight)
|
||||
static float CalculateMinimumZoom(float minHeight, float maxHeight)
|
||||
{
|
||||
var h = Game.Renderer.NativeResolution.Height;
|
||||
|
||||
@@ -230,31 +208,34 @@ namespace OpenRA.Graphics
|
||||
|
||||
var vd = graphicSettings.ViewportDistance;
|
||||
if (viewportSizes.AllowNativeZoom && vd == WorldViewport.Native)
|
||||
minZoom = 1;
|
||||
MinZoom = viewportSizes.DefaultScale;
|
||||
else
|
||||
{
|
||||
var range = viewportSizes.GetSizeRange(vd);
|
||||
minZoom = CalculateMinimumZoom(range.X, range.Y);
|
||||
MinZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
|
||||
}
|
||||
|
||||
maxZoom = Math.Min(minZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * 1f / viewportSizes.MaxZoomWindowHeight);
|
||||
MaxZoom = Math.Min(MinZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
|
||||
|
||||
if (unlockMinZoom)
|
||||
{
|
||||
// Specators and the map editor support zooming out by an extra factor of two.
|
||||
// Spectators and the map editor support zooming out by an extra factor of two.
|
||||
// TODO: Allow zooming out until the full map is visible
|
||||
// We need to improve our viewport scroll handling to center the map as we zoom out
|
||||
// before this will work well enough to enable
|
||||
unlockedMinZoom = minZoom * unlockedMinZoomScale;
|
||||
unlockedMinZoom = MinZoom * unlockedMinZoomScale;
|
||||
}
|
||||
|
||||
if (resetCurrentZoom)
|
||||
Zoom = minZoom;
|
||||
Zoom = MinZoom;
|
||||
else
|
||||
Zoom = Zoom.Clamp(minZoom, maxZoom);
|
||||
Zoom = Zoom.Clamp(MinZoom, MaxZoom);
|
||||
|
||||
var maxSize = 1f / (unlockMinZoom ? unlockedMinZoom : MinZoom) * new float2(Game.Renderer.NativeResolution);
|
||||
Game.Renderer.SetMaximumViewportSize(new Size((int)maxSize.X, (int)maxSize.Y));
|
||||
|
||||
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
|
||||
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
|
||||
t.ViewportZoomExtentsChanged(MinZoom, MaxZoom);
|
||||
}
|
||||
|
||||
public CPos ViewToWorld(int2 view)
|
||||
@@ -297,7 +278,7 @@ namespace OpenRA.Graphics
|
||||
return worldRenderer.World.Map.CellContaining(worldRenderer.ProjectedPosition(ViewToWorldPx(view)));
|
||||
}
|
||||
|
||||
/// <summary> Returns an unfiltered list of all cells that could potentially contain the mouse cursor</summary>
|
||||
/// <summary>Returns an unfiltered list of all cells that could potentially contain the mouse cursor.</summary>
|
||||
IEnumerable<MPos> CandidateMouseoverCells(int2 world)
|
||||
{
|
||||
var map = worldRenderer.World.Map;
|
||||
@@ -313,8 +294,8 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
public int2 ViewToWorldPx(int2 view) { return (graphicSettings.UIScale / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
|
||||
public int2 WorldToViewPx(int2 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).ToFloat2()).ToInt2(); }
|
||||
public int2 WorldToViewPx(in float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
|
||||
public int2 WorldToViewPx(int2 world) { return (Zoom / graphicSettings.UIScale * (world - TopLeft).ToFloat2()).ToInt2(); }
|
||||
public int2 WorldToViewPx(in float3 world) { return (Zoom / graphicSettings.UIScale * (world - TopLeft).XY).ToInt2(); }
|
||||
|
||||
public void Center(IEnumerable<Actor> actors)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -21,38 +21,35 @@ namespace OpenRA.Graphics
|
||||
public sealed class WorldRenderer : IDisposable
|
||||
{
|
||||
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
|
||||
r => ZPosition(r.Pos, r.ZOffset);
|
||||
r => r.Pos.Y + r.Pos.Z + r.ZOffset;
|
||||
|
||||
public readonly Size TileSize;
|
||||
public readonly int TileScale;
|
||||
public readonly World World;
|
||||
public readonly Theater Theater;
|
||||
public Viewport Viewport { get; private set; }
|
||||
public Viewport Viewport { get; }
|
||||
public readonly ITerrainLighting TerrainLighting;
|
||||
|
||||
public event Action PaletteInvalidated = null;
|
||||
|
||||
readonly HashSet<Actor> onScreenActors = new HashSet<Actor>();
|
||||
readonly HardwarePalette palette = new HardwarePalette();
|
||||
readonly Dictionary<string, PaletteReference> palettes = new Dictionary<string, PaletteReference>();
|
||||
readonly HashSet<Actor> onScreenActors = new();
|
||||
readonly HardwarePalette palette = new();
|
||||
readonly Dictionary<string, PaletteReference> palettes = new();
|
||||
readonly IRenderTerrain terrainRenderer;
|
||||
readonly Lazy<DebugVisualizations> debugVis;
|
||||
readonly Func<string, PaletteReference> createPaletteReference;
|
||||
readonly bool enableDepthBuffer;
|
||||
|
||||
readonly List<IFinalizedRenderable> preparedRenderables = new List<IFinalizedRenderable>();
|
||||
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new List<IFinalizedRenderable>();
|
||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new List<IFinalizedRenderable>();
|
||||
readonly List<IFinalizedRenderable> preparedRenderables = new();
|
||||
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new();
|
||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
|
||||
|
||||
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
|
||||
|
||||
bool lastDepthPreviewEnabled;
|
||||
readonly List<IRenderable> renderablesBuffer = new();
|
||||
|
||||
internal WorldRenderer(ModData modData, World world)
|
||||
{
|
||||
World = world;
|
||||
TileSize = World.Map.Grid.TileSize;
|
||||
TileScale = World.Map.Grid.Type == MapGridType.RectangularIsometric ? 1448 : 1024;
|
||||
TileScale = World.Map.Grid.TileScale;
|
||||
Viewport = new Viewport(this, world.Map);
|
||||
|
||||
createPaletteReference = CreatePaletteReference;
|
||||
@@ -68,7 +65,6 @@ namespace OpenRA.Graphics
|
||||
|
||||
palette.Initialize();
|
||||
|
||||
Theater = new Theater(world.Map.Rules.TileSet);
|
||||
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||
|
||||
@@ -87,7 +83,13 @@ namespace OpenRA.Graphics
|
||||
return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette);
|
||||
}
|
||||
|
||||
public PaletteReference Palette(string name) { return palettes.GetOrAdd(name, createPaletteReference); }
|
||||
public PaletteReference Palette(string name)
|
||||
{
|
||||
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
|
||||
// and can be removed once this has been fixed.
|
||||
return string.IsNullOrEmpty(name) ? null : palettes.GetOrAdd(name, createPaletteReference);
|
||||
}
|
||||
|
||||
public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
|
||||
{
|
||||
if (allowOverwrite && palette.Contains(name))
|
||||
@@ -107,8 +109,13 @@ namespace OpenRA.Graphics
|
||||
palette.ReplacePalette(name, pal);
|
||||
|
||||
// Update cached PlayerReference if one exists
|
||||
if (palettes.ContainsKey(name))
|
||||
palettes[name].Palette = pal;
|
||||
if (palettes.TryGetValue(name, out var paletteReference))
|
||||
paletteReference.Palette = pal;
|
||||
}
|
||||
|
||||
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float valueModifier, float minHue, float maxHue)
|
||||
{
|
||||
palette.SetColorShift(name, hueOffset, satOffset, valueModifier, minHue, maxHue);
|
||||
}
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
@@ -144,14 +151,14 @@ namespace OpenRA.Graphics
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateOverlayRenderables()
|
||||
{
|
||||
foreach (var a in World.ActorsWithTrait<IRenderAboveShroud>())
|
||||
World.ApplyToActorsWithTrait<IRenderAboveShroud>((actor, trait) =>
|
||||
{
|
||||
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
|
||||
continue;
|
||||
if (!actor.IsInWorld || actor.Disposed || (trait.SpatiallyPartitionable && !onScreenActors.Contains(actor)))
|
||||
return;
|
||||
|
||||
foreach (var renderable in a.Trait.RenderAboveShroud(a.Actor, this))
|
||||
foreach (var renderable in trait.RenderAboveShroud(actor, this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var a in World.Selection.Actors)
|
||||
{
|
||||
@@ -170,8 +177,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
foreach (var e in World.Effects)
|
||||
{
|
||||
var ea = e as IEffectAboveShroud;
|
||||
if (ea == null)
|
||||
if (e is not IEffectAboveShroud ea)
|
||||
continue;
|
||||
|
||||
foreach (var renderable in ea.RenderAboveShroud(this))
|
||||
@@ -186,14 +192,14 @@ namespace OpenRA.Graphics
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateAnnotationRenderables()
|
||||
{
|
||||
foreach (var a in World.ActorsWithTrait<IRenderAnnotations>())
|
||||
World.ApplyToActorsWithTrait<IRenderAnnotations>((actor, trait) =>
|
||||
{
|
||||
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
|
||||
continue;
|
||||
if (!actor.IsInWorld || actor.Disposed || (trait.SpatiallyPartitionable && !onScreenActors.Contains(actor)))
|
||||
return;
|
||||
|
||||
foreach (var renderAnnotation in a.Trait.RenderAnnotations(a.Actor, this))
|
||||
foreach (var renderAnnotation in trait.RenderAnnotations(actor, this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var a in World.Selection.Actors)
|
||||
{
|
||||
@@ -212,8 +218,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
foreach (var e in World.Effects)
|
||||
{
|
||||
var ea = e as IEffectAnnotation;
|
||||
if (ea == null)
|
||||
if (e is not IEffectAnnotation ea)
|
||||
continue;
|
||||
|
||||
foreach (var renderAnnotation in ea.RenderAnnotation(this))
|
||||
@@ -247,11 +252,7 @@ namespace OpenRA.Graphics
|
||||
if (World.WorldActor.Disposed)
|
||||
return;
|
||||
|
||||
if (debugVis.Value != null && lastDepthPreviewEnabled != debugVis.Value.DepthBuffer)
|
||||
{
|
||||
lastDepthPreviewEnabled = debugVis.Value.DepthBuffer;
|
||||
Game.Renderer.WorldSpriteRenderer.SetDepthPreviewEnabled(lastDepthPreviewEnabled);
|
||||
}
|
||||
debugVis.Value?.UpdateDepthBuffer();
|
||||
|
||||
var bounds = Viewport.GetScissorBounds(World.Type != WorldType.Editor);
|
||||
Game.Renderer.EnableScissor(bounds);
|
||||
@@ -269,15 +270,16 @@ namespace OpenRA.Graphics
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.ClearDepthBuffer();
|
||||
|
||||
foreach (var a in World.ActorsWithTrait<IRenderAboveWorld>())
|
||||
if (a.Actor.IsInWorld && !a.Actor.Disposed)
|
||||
a.Trait.RenderAboveWorld(a.Actor, this);
|
||||
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
|
||||
{
|
||||
if (actor.IsInWorld && !actor.Disposed)
|
||||
trait.RenderAboveWorld(actor, this);
|
||||
});
|
||||
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.ClearDepthBuffer();
|
||||
|
||||
foreach (var a in World.ActorsWithTrait<IRenderShroud>())
|
||||
a.Trait.RenderShroud(this);
|
||||
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
|
||||
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.Context.DisableDepthBuffer();
|
||||
@@ -356,7 +358,13 @@ namespace OpenRA.Graphics
|
||||
|
||||
public float3 Screen3DPosition(WPos pos)
|
||||
{
|
||||
var z = ZPosition(pos, 0) * (float)TileSize.Height / TileScale;
|
||||
// The projection from world coordinates to screen coordinates has
|
||||
// a non-obvious relationship between the y and z coordinates:
|
||||
// * A flat surface with constant y (e.g. a vertical wall) in world coordinates
|
||||
// transforms into a flat surface with constant z (depth) in screen coordinates.
|
||||
// * Increasing the world y coordinate increases screen y and z coordinates equally.
|
||||
// * Increases the world z coordinate decreases screen y but doesn't change screen z.
|
||||
var z = pos.Y * (float)TileSize.Height / TileScale;
|
||||
return new float3((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale, z);
|
||||
}
|
||||
|
||||
@@ -375,7 +383,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// For scaling vectors to pixel sizes in the model renderer
|
||||
public float3 ScreenVectorComponents(WVec vec)
|
||||
public float3 ScreenVectorComponents(in WVec vec)
|
||||
{
|
||||
return new float3(
|
||||
(float)TileSize.Width * vec.X / TileScale,
|
||||
@@ -384,29 +392,19 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// For scaling vectors to pixel sizes in the model renderer
|
||||
public float[] ScreenVector(WVec vec)
|
||||
public float[] ScreenVector(in WVec vec)
|
||||
{
|
||||
var xyz = ScreenVectorComponents(vec);
|
||||
return new[] { xyz.X, xyz.Y, xyz.Z, 1f };
|
||||
}
|
||||
|
||||
public int2 ScreenPxOffset(WVec vec)
|
||||
public int2 ScreenPxOffset(in WVec vec)
|
||||
{
|
||||
// Round to nearest pixel
|
||||
var xyz = ScreenVectorComponents(vec);
|
||||
return new int2((int)Math.Round(xyz.X), (int)Math.Round(xyz.Y));
|
||||
}
|
||||
|
||||
public float ScreenZPosition(WPos pos, int offset)
|
||||
{
|
||||
return ZPosition(pos, offset) * (float)TileSize.Height / TileScale;
|
||||
}
|
||||
|
||||
static int ZPosition(WPos pos, int offset)
|
||||
{
|
||||
return pos.Y + pos.Z + offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a position in the world that is projected to the given screen position.
|
||||
/// There are many possible world positions, and the returned value chooses the value with no elevation.
|
||||
@@ -425,7 +423,6 @@ namespace OpenRA.Graphics
|
||||
World.Dispose();
|
||||
|
||||
palette.Dispose();
|
||||
Theater.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -19,7 +19,9 @@ namespace OpenRA
|
||||
public readonly string Name;
|
||||
public readonly Hotkey Default = Hotkey.Invalid;
|
||||
public readonly string Description = "";
|
||||
public readonly HashSet<string> Types = new HashSet<string>();
|
||||
public readonly HashSet<string> Types = new();
|
||||
public readonly HashSet<string> Contexts = new();
|
||||
public readonly bool Readonly = false;
|
||||
public bool HasDuplicates { get; internal set; }
|
||||
|
||||
public HotkeyDefinition(string name, MiniYaml node)
|
||||
@@ -36,6 +38,22 @@ namespace OpenRA
|
||||
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
|
||||
if (typesNode != null)
|
||||
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value);
|
||||
|
||||
var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts");
|
||||
if (contextsNode != null)
|
||||
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextsNode.Value.Value);
|
||||
|
||||
var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform");
|
||||
if (platformNode != null)
|
||||
{
|
||||
var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString());
|
||||
if (platformOverride != null)
|
||||
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
|
||||
}
|
||||
|
||||
var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly");
|
||||
if (readonlyNode != null)
|
||||
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyNode.Value.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -18,8 +18,8 @@ namespace OpenRA
|
||||
public sealed class HotkeyManager
|
||||
{
|
||||
readonly Dictionary<string, Hotkey> settings;
|
||||
readonly Dictionary<string, HotkeyDefinition> definitions = new Dictionary<string, HotkeyDefinition>();
|
||||
readonly Dictionary<string, Hotkey> keys = new Dictionary<string, Hotkey>();
|
||||
readonly Dictionary<string, HotkeyDefinition> definitions = new();
|
||||
readonly Dictionary<string, Hotkey> keys = new();
|
||||
|
||||
public HotkeyManager(IReadOnlyFileSystem fileSystem, Dictionary<string, Hotkey> settings, Manifest manifest)
|
||||
{
|
||||
@@ -35,14 +35,17 @@ namespace OpenRA
|
||||
|
||||
foreach (var kv in settings)
|
||||
{
|
||||
if (definitions.ContainsKey(kv.Key))
|
||||
if (definitions.TryGetValue(kv.Key, out var definition) && !definition.Readonly)
|
||||
keys[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
foreach (var hd in definitions)
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Performance", "CA1854:Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method",
|
||||
Justification = "Func must perform a live lookup in the collection, as the lookup value can change.")]
|
||||
internal Func<Hotkey> GetHotkeyReference(string name)
|
||||
{
|
||||
// Is this a mod-defined hotkey?
|
||||
@@ -61,6 +64,9 @@ namespace OpenRA
|
||||
if (!definitions.TryGetValue(name, out var definition))
|
||||
return;
|
||||
|
||||
if (definition.Readonly)
|
||||
return;
|
||||
|
||||
keys[name] = value;
|
||||
if (value != definition.Default)
|
||||
settings[name] = value;
|
||||
@@ -68,7 +74,7 @@ namespace OpenRA
|
||||
settings.Remove(name);
|
||||
|
||||
var hadDuplicates = definition.HasDuplicates;
|
||||
definition.HasDuplicates = GetFirstDuplicate(definition.Name, this[definition.Name].GetValue(), definition) != null;
|
||||
definition.HasDuplicates = GetFirstDuplicate(definition, this[definition.Name].GetValue()) != null;
|
||||
|
||||
if (hadDuplicates || definition.HasDuplicates)
|
||||
{
|
||||
@@ -77,33 +83,30 @@ namespace OpenRA
|
||||
if (hd.Value == definition)
|
||||
continue;
|
||||
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeyDefinition GetFirstDuplicate(string name, Hotkey value, HotkeyDefinition definition)
|
||||
public HotkeyDefinition GetFirstDuplicate(HotkeyDefinition definition, Hotkey value)
|
||||
{
|
||||
if (definition == null)
|
||||
return null;
|
||||
|
||||
foreach (var kv in keys)
|
||||
{
|
||||
if (kv.Key == name)
|
||||
if (kv.Key == definition.Name)
|
||||
continue;
|
||||
|
||||
if (kv.Value == value && definitions[kv.Key].Types.Overlaps(definition.Types))
|
||||
if (kv.Value == value && definitions[kv.Key].Contexts.Overlaps(definition.Contexts))
|
||||
return definitions[kv.Key];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public HotkeyReference this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
return new HotkeyReference(GetHotkeyReference(name));
|
||||
}
|
||||
}
|
||||
public HotkeyReference this[string name] => new(GetHotkeyReference(name));
|
||||
|
||||
public IEnumerable<HotkeyDefinition> Definitions { get { return definitions.Values; } }
|
||||
public IEnumerable<HotkeyDefinition> Definitions => definitions.Values;
|
||||
}
|
||||
}
|
||||
|
||||
63
OpenRA.Game/HttpExtension.cs
Normal file
63
OpenRA.Game/HttpExtension.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
#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.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public delegate void OnProgress(long total, long totalRead, int progressPercentage);
|
||||
|
||||
public static class HttpExtension
|
||||
{
|
||||
public static async Task ReadAsStreamWithProgress(this HttpResponseMessage response, Stream outputStream, OnProgress onProgress, CancellationToken token)
|
||||
{
|
||||
var total = response.Content.Headers.ContentLength ?? -1;
|
||||
var canReportProgress = total > 0;
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
using (var contentStream = await response.Content.ReadAsStreamAsync(token))
|
||||
#else
|
||||
using (var contentStream = await response.Content.ReadAsStreamAsync())
|
||||
#endif
|
||||
{
|
||||
var totalRead = 0L;
|
||||
var buffer = new byte[8192];
|
||||
var hasMoreToRead = true;
|
||||
|
||||
do
|
||||
{
|
||||
var read = await contentStream.ReadAsync(buffer.AsMemory(0, buffer.Length), token);
|
||||
if (read == 0)
|
||||
hasMoreToRead = false;
|
||||
else
|
||||
{
|
||||
await outputStream.WriteAsync(buffer.AsMemory(0, read), token);
|
||||
|
||||
totalRead += read;
|
||||
|
||||
if (canReportProgress)
|
||||
{
|
||||
var progressPercentage = (int)((double)totalRead / total * 100);
|
||||
onProgress?.Invoke(total, totalRead, progressPercentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (hasMoreToRead && !token.IsCancellationRequested);
|
||||
|
||||
onProgress?.Invoke(total, totalRead, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -9,12 +9,41 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class Utility
|
||||
{
|
||||
static readonly ConcurrentCache<Type, FieldInfo[]> TypeFields =
|
||||
new(type => type.GetFields());
|
||||
|
||||
static readonly ConcurrentCache<(MemberInfo Member, Type AttributeType), bool> MemberHasAttribute =
|
||||
new(x => Attribute.IsDefined(x.Member, x.AttributeType));
|
||||
|
||||
static readonly ConcurrentCache<(MemberInfo Member, Type AttributeType, bool Inherit), object[]> MemberCustomAttributes =
|
||||
new(x => x.Member.GetCustomAttributes(x.AttributeType, x.Inherit));
|
||||
|
||||
public static FieldInfo[] GetFields(Type type)
|
||||
{
|
||||
return TypeFields[type];
|
||||
}
|
||||
|
||||
public static bool HasAttribute<TAttribute>(MemberInfo member)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return MemberHasAttribute[(member, typeof(TAttribute))];
|
||||
}
|
||||
|
||||
public static TAttribute[] GetCustomAttributes<TAttribute>(MemberInfo member, bool inherit)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return (TAttribute[])MemberCustomAttributes[(member, typeof(TAttribute), inherit)];
|
||||
}
|
||||
|
||||
public readonly ModData ModData;
|
||||
public readonly InstalledMods Mods;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -13,9 +13,9 @@ using System;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public struct Hotkey : IEquatable<Hotkey>
|
||||
public readonly struct Hotkey : IEquatable<Hotkey>
|
||||
{
|
||||
public static Hotkey Invalid = new Hotkey(Keycode.UNKNOWN, Modifiers.None);
|
||||
public static Hotkey Invalid = new(Keycode.UNKNOWN, Modifiers.None);
|
||||
public bool IsValid()
|
||||
{
|
||||
return Key != Keycode.UNKNOWN;
|
||||
@@ -42,7 +42,7 @@ namespace OpenRA
|
||||
var mods = Modifiers.None;
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
var modString = s.Substring(s.IndexOf(' '));
|
||||
var modString = s[s.IndexOf(' ')..];
|
||||
if (!Enum<Modifiers>.TryParse(modString, true, out mods))
|
||||
return false;
|
||||
}
|
||||
@@ -81,11 +81,10 @@ namespace OpenRA
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var o = obj as Hotkey?;
|
||||
return o != null && o == this;
|
||||
return obj is Hotkey o && (Hotkey?)o == this;
|
||||
}
|
||||
|
||||
public override string ToString() { return "{0} {1}".F(Key, Modifiers.ToString("F")); }
|
||||
public override string ToString() { return $"{Key} {Modifiers:F}"; }
|
||||
|
||||
public string DisplayString()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -14,7 +14,7 @@ using System;
|
||||
namespace OpenRA
|
||||
{
|
||||
/// <summary>
|
||||
/// A reference to either a named hotkey (defined in the game settings) or a statically assigned hotkey
|
||||
/// A reference to either a named hotkey (defined in the game settings) or a statically assigned hotkey.
|
||||
/// </summary>
|
||||
public class HotkeyReference
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -37,36 +37,24 @@ namespace OpenRA
|
||||
|
||||
public void OnKeyInput(KeyInput input)
|
||||
{
|
||||
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleKeyPress(input));
|
||||
Sync.RunUnsynced(world, () => Ui.HandleKeyPress(input));
|
||||
}
|
||||
|
||||
public void OnTextInput(string text)
|
||||
{
|
||||
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleTextInput(text));
|
||||
Sync.RunUnsynced(world, () => Ui.HandleTextInput(text));
|
||||
}
|
||||
|
||||
public void OnMouseInput(MouseInput input)
|
||||
{
|
||||
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleInput(input));
|
||||
Sync.RunUnsynced(world, () => Ui.HandleInput(input));
|
||||
}
|
||||
}
|
||||
|
||||
public class MouseButtonPreference
|
||||
{
|
||||
public MouseButton Action
|
||||
{
|
||||
get
|
||||
{
|
||||
return Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right;
|
||||
}
|
||||
}
|
||||
public MouseButton Action => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right;
|
||||
|
||||
public MouseButton Cancel
|
||||
{
|
||||
get
|
||||
{
|
||||
return Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left;
|
||||
}
|
||||
}
|
||||
public MouseButton Cancel => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -13,7 +13,8 @@ using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
// List of keycodes, duplicated from SDL 2.0.1
|
||||
// List of keycodes. Duplicated from SDL 2.0.1, with the addition
|
||||
// of MOUSE4 and MOUSE5.
|
||||
public enum Keycode
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
@@ -252,11 +253,13 @@ namespace OpenRA
|
||||
KBDILLUMUP = 280 | (1 << 30),
|
||||
EJECT = 281 | (1 << 30),
|
||||
SLEEP = 282 | (1 << 30),
|
||||
MOUSE4 = 283 | (1 << 30),
|
||||
MOUSE5 = 284 | (1 << 30)
|
||||
}
|
||||
|
||||
public static class KeycodeExts
|
||||
{
|
||||
static readonly Dictionary<Keycode, string> KeyNames = new Dictionary<Keycode, string>
|
||||
static readonly Dictionary<Keycode, string> KeyNames = new()
|
||||
{
|
||||
{ Keycode.UNKNOWN, "Undefined" },
|
||||
{ Keycode.RETURN, "Return" },
|
||||
@@ -494,6 +497,8 @@ namespace OpenRA
|
||||
{ Keycode.KBDILLUMUP, "KBDIllumUp" },
|
||||
{ Keycode.EJECT, "Eject" },
|
||||
{ Keycode.SLEEP, "Sleep" },
|
||||
{ Keycode.MOUSE4, "Mouse 4" },
|
||||
{ Keycode.MOUSE5, "Mouse 5" },
|
||||
};
|
||||
|
||||
public static string DisplayString(Keycode k)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -15,22 +15,18 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class InstalledMods : IReadOnlyDictionary<string, Manifest>
|
||||
{
|
||||
readonly Dictionary<string, Manifest> mods;
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
/// <summary>Initializes the collection of locally installed mods.</summary>
|
||||
/// <param name="searchPaths">Filesystem paths to search for mod packages.</param>
|
||||
/// <param name="explicitPaths">Filesystem paths to additional mod packages.</param>
|
||||
public InstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
|
||||
{
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, 256);
|
||||
mods = GetInstalledMods(searchPaths, explicitPaths);
|
||||
}
|
||||
|
||||
@@ -58,7 +54,7 @@ namespace OpenRA
|
||||
return mods;
|
||||
}
|
||||
|
||||
Manifest LoadMod(string id, string path)
|
||||
static Manifest LoadMod(string id, string path)
|
||||
{
|
||||
IReadOnlyPackage package = null;
|
||||
try
|
||||
@@ -75,7 +71,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Load mod '{0}': {1}".F(path, e));
|
||||
Log.Write("debug", $"Load mod '{path}': {e}");
|
||||
}
|
||||
|
||||
package?.Dispose();
|
||||
@@ -83,7 +79,7 @@ namespace OpenRA
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
|
||||
static Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
|
||||
{
|
||||
var ret = new Dictionary<string, Manifest>();
|
||||
var candidates = GetCandidateMods(searchPaths)
|
||||
@@ -99,10 +95,10 @@ namespace OpenRA
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Manifest this[string key] { get { return mods[key]; } }
|
||||
public int Count { get { return mods.Count; } }
|
||||
public ICollection<string> Keys { get { return mods.Keys; } }
|
||||
public ICollection<Manifest> Values { get { return mods.Values; } }
|
||||
public Manifest this[string key] => mods[key];
|
||||
public IEnumerable<string> Keys => mods.Keys;
|
||||
public IEnumerable<Manifest> Values => mods.Values;
|
||||
public int Count => mods.Count;
|
||||
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
|
||||
public IEnumerator<KeyValuePair<string, Manifest>> GetEnumerator() { return mods.GetEnumerator(); }
|
||||
public bool TryGetValue(string key, out Manifest value) { return mods.TryGetValue(key, out value); }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -12,10 +12,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -24,11 +24,11 @@ namespace OpenRA
|
||||
const int AuthKeySize = 2048;
|
||||
public enum LinkState { Uninitialized, GeneratingKeys, Unlinked, CheckingLink, ConnectionFailed, Linked }
|
||||
|
||||
public LinkState State { get { return innerState; } }
|
||||
public string Fingerprint { get { return innerFingerprint; } }
|
||||
public string PublicKey { get { return innerPublicKey; } }
|
||||
public LinkState State => innerState;
|
||||
public string Fingerprint => innerFingerprint;
|
||||
public string PublicKey => innerPublicKey;
|
||||
|
||||
public PlayerProfile ProfileData { get { return innerData; } }
|
||||
public PlayerProfile ProfileData => innerData;
|
||||
|
||||
volatile LinkState innerState;
|
||||
volatile PlayerProfile innerData;
|
||||
@@ -66,8 +66,10 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Failed to load keys: {0}", e);
|
||||
Log.Write("debug", "Failed to load player keypair from `{0}` with exception: {1}", filePath, e);
|
||||
Console.WriteLine("Failed to load keys:");
|
||||
Console.WriteLine(e);
|
||||
Log.Write("debug", $"Failed to load player keypair from `{filePath}` with exception:");
|
||||
Log.Write("debug", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,23 +78,22 @@ namespace OpenRA
|
||||
if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed)
|
||||
return;
|
||||
|
||||
Action<DownloadDataCompletedEventArgs> onQueryComplete = i =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (i.Error != null)
|
||||
{
|
||||
innerState = LinkState.ConnectionFailed;
|
||||
return;
|
||||
}
|
||||
var client = HttpClientFactory.Create();
|
||||
|
||||
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First();
|
||||
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
|
||||
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
||||
|
||||
var yaml = MiniYaml.FromStream(result).First();
|
||||
if (yaml.Key == "Player")
|
||||
{
|
||||
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
||||
if (innerData.KeyRevoked)
|
||||
{
|
||||
Log.Write("debug", "Revoking key with fingerprint {0}", Fingerprint);
|
||||
Log.Write("debug", $"Revoking key with fingerprint {Fingerprint}");
|
||||
DeleteKeypair();
|
||||
}
|
||||
else
|
||||
@@ -103,17 +104,17 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse player data result with exception: {0}", e);
|
||||
Log.Write("debug", "Failed to parse player data result with exception:");
|
||||
Log.Write("debug", e);
|
||||
innerState = LinkState.ConnectionFailed;
|
||||
}
|
||||
finally
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
innerState = LinkState.CheckingLink;
|
||||
new Download(playerDatabase.Profile + Fingerprint, _ => { }, onQueryComplete);
|
||||
}
|
||||
|
||||
public void GenerateKeypair()
|
||||
@@ -138,8 +139,10 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to generate keypair with exception: {1}", e);
|
||||
Console.WriteLine("Key generation failed: {0}", e);
|
||||
Log.Write("debug", "Failed to generate keypair with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("Key generation failed:");
|
||||
Console.WriteLine(e);
|
||||
|
||||
innerState = LinkState.Uninitialized;
|
||||
}
|
||||
@@ -154,12 +157,14 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to delete keypair with exception: {1}", e);
|
||||
Console.WriteLine("Key deletion failed: {0}", e);
|
||||
Log.Write("debug", "Failed to delete keypair with exception:");
|
||||
Log.Write("debug", e);
|
||||
Console.WriteLine("Key deletion failed:");
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
|
||||
innerState = LinkState.Uninitialized;
|
||||
parameters = default(RSAParameters);
|
||||
parameters = default;
|
||||
innerFingerprint = null;
|
||||
innerData = null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -14,12 +14,12 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public struct MPos : IEquatable<MPos>
|
||||
public readonly struct MPos : IEquatable<MPos>
|
||||
{
|
||||
public readonly int U, V;
|
||||
|
||||
public MPos(int u, int v) { U = u; V = v; }
|
||||
public static readonly MPos Zero = new MPos(0, 0);
|
||||
public static readonly MPos Zero = new(0, 0);
|
||||
|
||||
public static bool operator ==(MPos me, MPos other) { return me.U == other.U && me.V == other.V; }
|
||||
public static bool operator !=(MPos me, MPos other) { return !(me == other); }
|
||||
@@ -27,7 +27,7 @@ namespace OpenRA
|
||||
public override int GetHashCode() { return U.GetHashCode() ^ V.GetHashCode(); }
|
||||
|
||||
public bool Equals(MPos other) { return other == this; }
|
||||
public override bool Equals(object obj) { return obj is MPos && Equals((MPos)obj); }
|
||||
public override bool Equals(object obj) { return obj is MPos uv && Equals(uv); }
|
||||
|
||||
public MPos Clamp(Rectangle r)
|
||||
{
|
||||
@@ -64,14 +64,14 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projected map position
|
||||
/// Projected map position.
|
||||
/// </summary>
|
||||
public struct PPos : IEquatable<PPos>
|
||||
public readonly struct PPos : IEquatable<PPos>
|
||||
{
|
||||
public readonly int U, V;
|
||||
|
||||
public PPos(int u, int v) { U = u; V = v; }
|
||||
public static readonly PPos Zero = new PPos(0, 0);
|
||||
public static readonly PPos Zero = new(0, 0);
|
||||
|
||||
public static bool operator ==(PPos me, PPos other) { return me.U == other.U && me.V == other.V; }
|
||||
public static bool operator !=(PPos me, PPos other) { return !(me == other); }
|
||||
@@ -88,7 +88,7 @@ namespace OpenRA
|
||||
public override int GetHashCode() { return U.GetHashCode() ^ V.GetHashCode(); }
|
||||
|
||||
public bool Equals(PPos other) { return other == this; }
|
||||
public override bool Equals(object obj) { return obj is PPos && Equals((PPos)obj); }
|
||||
public override bool Equals(object obj) { return obj is PPos puv && Equals(puv); }
|
||||
|
||||
public override string ToString() { return U + "," + V; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
@@ -20,6 +21,17 @@ namespace OpenRA
|
||||
{
|
||||
public interface IGlobalModData { }
|
||||
|
||||
public sealed class TerrainFormat : IGlobalModData
|
||||
{
|
||||
public readonly string Type;
|
||||
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
|
||||
public TerrainFormat(MiniYaml yaml)
|
||||
{
|
||||
Type = yaml.Value;
|
||||
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SpriteSequenceFormat : IGlobalModData
|
||||
{
|
||||
public readonly string Type;
|
||||
@@ -48,11 +60,12 @@ namespace OpenRA
|
||||
public string Version;
|
||||
public string Website;
|
||||
public string WebIcon32;
|
||||
public string WindowTitle;
|
||||
public bool Hidden;
|
||||
}
|
||||
|
||||
/// <summary> Describes what is to be loaded in order to run a mod. </summary>
|
||||
public class Manifest : IDisposable
|
||||
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
|
||||
public sealed class Manifest : IDisposable
|
||||
{
|
||||
public readonly string Id;
|
||||
public readonly IReadOnlyPackage Package;
|
||||
@@ -66,21 +79,23 @@ namespace OpenRA
|
||||
public readonly IReadOnlyDictionary<string, string> Packages;
|
||||
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
||||
public readonly MiniYaml LoadScreen;
|
||||
public readonly string DefaultOrderGenerator;
|
||||
|
||||
public readonly string[] SoundFormats = { };
|
||||
public readonly string[] SpriteFormats = { };
|
||||
public readonly string[] PackageFormats = { };
|
||||
public readonly string[] SoundFormats = Array.Empty<string>();
|
||||
public readonly string[] SpriteFormats = Array.Empty<string>();
|
||||
public readonly string[] PackageFormats = Array.Empty<string>();
|
||||
public readonly string[] VideoFormats = Array.Empty<string>();
|
||||
|
||||
readonly string[] reservedModuleNames =
|
||||
{
|
||||
"Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
||||
"ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
|
||||
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
|
||||
"RequiresMods", "PackageFormats"
|
||||
};
|
||||
|
||||
readonly TypeDictionary modules = new TypeDictionary();
|
||||
readonly TypeDictionary modules = new();
|
||||
readonly Dictionary<string, MiniYaml> yaml;
|
||||
|
||||
bool customDataLoaded;
|
||||
@@ -100,7 +115,7 @@ namespace OpenRA
|
||||
var filename = nodes[i].Value.Value;
|
||||
var contents = package.GetStream(filename);
|
||||
if (contents == null)
|
||||
throw new YamlException("{0}: File `{1}` not found.".F(nodes[i].Location, filename));
|
||||
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
|
||||
|
||||
nodes.RemoveAt(i);
|
||||
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
|
||||
@@ -115,7 +130,7 @@ namespace OpenRA
|
||||
MapFolders = YamlDictionary(yaml, "MapFolders");
|
||||
|
||||
if (yaml.TryGetValue("Packages", out var packages))
|
||||
Packages = packages.ToDictionary(x => x.Value).AsReadOnly();
|
||||
Packages = packages.ToDictionary(x => x.Value);
|
||||
|
||||
Rules = YamlList(yaml, "Rules");
|
||||
Sequences = YamlList(yaml, "Sequences");
|
||||
@@ -142,19 +157,25 @@ namespace OpenRA
|
||||
// Allow inherited mods to import parent maps.
|
||||
var compat = new List<string> { Id };
|
||||
|
||||
if (yaml.ContainsKey("SupportsMapsFrom"))
|
||||
compat.AddRange(yaml["SupportsMapsFrom"].Value.Split(',').Select(c => c.Trim()));
|
||||
if (yaml.TryGetValue("SupportsMapsFrom", out var entry))
|
||||
compat.AddRange(entry.Value.Split(',').Select(c => c.Trim()));
|
||||
|
||||
MapCompatibility = compat.ToArray();
|
||||
|
||||
if (yaml.ContainsKey("PackageFormats"))
|
||||
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", yaml["PackageFormats"].Value);
|
||||
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
|
||||
DefaultOrderGenerator = entry.Value;
|
||||
|
||||
if (yaml.ContainsKey("SoundFormats"))
|
||||
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", yaml["SoundFormats"].Value);
|
||||
if (yaml.TryGetValue("PackageFormats", out entry))
|
||||
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", entry.Value);
|
||||
|
||||
if (yaml.ContainsKey("SpriteFormats"))
|
||||
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
|
||||
if (yaml.TryGetValue("SoundFormats", out entry))
|
||||
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", entry.Value);
|
||||
|
||||
if (yaml.TryGetValue("SpriteFormats", out entry))
|
||||
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", entry.Value);
|
||||
|
||||
if (yaml.TryGetValue("VideoFormats", out entry))
|
||||
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
|
||||
}
|
||||
|
||||
public void LoadCustomData(ObjectCreator oc)
|
||||
@@ -166,7 +187,7 @@ namespace OpenRA
|
||||
|
||||
var t = oc.FindType(kv.Key);
|
||||
if (t == null || !typeof(IGlobalModData).IsAssignableFrom(t))
|
||||
throw new InvalidDataException("`{0}` is not a valid mod manifest entry.".F(kv.Key));
|
||||
throw new InvalidDataException($"`{kv.Key}` is not a valid mod manifest entry.");
|
||||
|
||||
IGlobalModData module;
|
||||
var ctor = t.GetConstructor(new[] { typeof(MiniYaml) });
|
||||
@@ -188,10 +209,10 @@ namespace OpenRA
|
||||
customDataLoaded = true;
|
||||
}
|
||||
|
||||
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key, bool parsePaths = false)
|
||||
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)
|
||||
{
|
||||
if (!yaml.ContainsKey(key))
|
||||
return new string[] { };
|
||||
return Array.Empty<string>();
|
||||
|
||||
return yaml[key].ToDictionary().Keys.ToArray();
|
||||
}
|
||||
@@ -199,10 +220,9 @@ namespace OpenRA
|
||||
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
|
||||
{
|
||||
if (!yaml.ContainsKey(key))
|
||||
return new ReadOnlyDictionary<string, string>();
|
||||
return new Dictionary<string, string>();
|
||||
|
||||
var inner = yaml[key].ToDictionary(my => my.Value);
|
||||
return new ReadOnlyDictionary<string, string>(inner);
|
||||
return yaml[key].ToDictionary(my => my.Value);
|
||||
}
|
||||
|
||||
public bool Contains<T>() where T : IGlobalModData
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -37,7 +37,7 @@ namespace OpenRA
|
||||
public class ActorInitializer : IActorInitializer
|
||||
{
|
||||
public readonly Actor Self;
|
||||
public World World { get { return Self.World; } }
|
||||
public World World => Self.World;
|
||||
|
||||
internal TypeDictionary Dict;
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace OpenRA
|
||||
// Traits tagged with an instance name prefer inits with the same name.
|
||||
// If a more specific init is not available, fall back to an unnamed init.
|
||||
// If duplicate inits are defined, take the last to match standard yaml override expectations
|
||||
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
|
||||
if (!string.IsNullOrEmpty(info?.InstanceName))
|
||||
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
||||
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace OpenRA
|
||||
{
|
||||
var init = GetOrDefault<T>(info);
|
||||
if (init == null)
|
||||
throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
|
||||
throw new InvalidOperationException($"TypeDictionary does not contain instance of type `{typeof(T)}`");
|
||||
|
||||
return init;
|
||||
}
|
||||
@@ -140,7 +140,7 @@ namespace OpenRA
|
||||
|
||||
public abstract class ValueActorInit<T> : ActorInit
|
||||
{
|
||||
protected readonly T value;
|
||||
readonly T value;
|
||||
|
||||
protected ValueActorInit(TraitInfo info, T value)
|
||||
: base(info.InstanceName) { this.value = value; }
|
||||
@@ -150,18 +150,18 @@ namespace OpenRA
|
||||
|
||||
protected ValueActorInit(T value) { this.value = value; }
|
||||
|
||||
public virtual T Value { get { return value; } }
|
||||
public virtual T Value => value;
|
||||
|
||||
public virtual void Initialize(MiniYaml yaml)
|
||||
{
|
||||
Initialize((T)FieldLoader.GetValue("value", typeof(T), yaml.Value));
|
||||
Initialize((T)FieldLoader.GetValue(nameof(value), typeof(T), yaml.Value));
|
||||
}
|
||||
|
||||
public virtual void Initialize(T value)
|
||||
{
|
||||
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, value);
|
||||
typeof(ValueActorInit<T>)
|
||||
.GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?.SetValue(this, value);
|
||||
}
|
||||
|
||||
public override MiniYaml Save()
|
||||
@@ -226,7 +226,7 @@ namespace OpenRA
|
||||
public class OwnerInit : ActorInit, ISingleInstanceInit
|
||||
{
|
||||
public readonly string InternalName;
|
||||
protected readonly Player value;
|
||||
readonly Player value;
|
||||
|
||||
public OwnerInit(Player value)
|
||||
{
|
||||
@@ -246,16 +246,16 @@ namespace OpenRA
|
||||
|
||||
public void Initialize(MiniYaml yaml)
|
||||
{
|
||||
var field = GetType().GetField("InternalName", BindingFlags.Public | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, yaml.Value);
|
||||
typeof(OwnerInit)
|
||||
.GetField(nameof(InternalName), BindingFlags.Public | BindingFlags.Instance)
|
||||
?.SetValue(this, yaml.Value);
|
||||
}
|
||||
|
||||
public void Initialize(Player player)
|
||||
{
|
||||
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, player);
|
||||
typeof(OwnerInit)
|
||||
.GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?.SetValue(this, player);
|
||||
}
|
||||
|
||||
public override MiniYaml Save()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user