Compare commits
1251 Commits
devtest-20
...
playtest-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7691507baf | ||
|
|
8d6cebe654 | ||
|
|
daf10c34a6 | ||
|
|
d96ec21b95 | ||
|
|
aea1182bb5 | ||
|
|
b413b97a52 | ||
|
|
7daa27f123 | ||
|
|
f3f98d8750 | ||
|
|
69eeb2dc84 | ||
|
|
7cee29ff70 | ||
|
|
5e80fee913 | ||
|
|
c1474204e2 | ||
|
|
223f9408fd | ||
|
|
674ca8555b | ||
|
|
275325b0cf | ||
|
|
06e399d3ce | ||
|
|
d6d77eab7c | ||
|
|
2392566c1d | ||
|
|
028467f150 | ||
|
|
eee4b04248 | ||
|
|
f551b542f3 | ||
|
|
5d5419702b | ||
|
|
47e89b7d76 | ||
|
|
9bb0409f19 | ||
|
|
80ac494948 | ||
|
|
eb82f2b975 | ||
|
|
a5c8db82da | ||
|
|
2a26ff6818 | ||
|
|
5a11d54956 | ||
|
|
26c5a8b76c | ||
|
|
efcfab78dc | ||
|
|
ac351f6e65 | ||
|
|
42bdc9e53e | ||
|
|
3097efc5b6 | ||
|
|
853422409f | ||
|
|
f097250394 | ||
|
|
96e0f96c12 | ||
|
|
0a4c4162be | ||
|
|
10ff24f24e | ||
|
|
aeb96d98f3 | ||
|
|
cf4bfdcdfb | ||
|
|
d55af8d75d | ||
|
|
dd9e75d017 | ||
|
|
1b6220962e | ||
|
|
f70f2acb39 | ||
|
|
32e2507bff | ||
|
|
22a42b7dc9 | ||
|
|
c01e4043e8 | ||
|
|
c8665c98a6 | ||
|
|
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 |
194
.editorconfig
194
.editorconfig
@@ -25,82 +25,186 @@ csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
|
||||
#### Naming styles ####
|
||||
## 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
|
||||
## Symbol specifications:
|
||||
|
||||
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.static_private_or_internal_field_should_be_pascal_case.severity = none
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.symbols = static_private_or_internal_field
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.const_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_private_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.symbols = const_private_field
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.const_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.const_private_or_internal_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.symbols = internal_field
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.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.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
|
||||
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
|
||||
|
||||
# Naming rules
|
||||
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
|
||||
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
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
|
||||
|
||||
## Formatting:
|
||||
|
||||
# Also handled by StyleCopAnalyzers - SA1024: ColonsMustBeSpacedCorrectly.
|
||||
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
|
||||
|
||||
# Also handled by StyleCopAnalyzers - SA1024: ColonsMustBeSpacedCorrectly.
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
|
||||
#Formatting - wrapping options
|
||||
# Also handled by StyleCopAnalyzers - SA1000: KeywordsMustBeSpacedCorrectly.
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
|
||||
#leave code block on single line
|
||||
# Leave code block on single line.
|
||||
csharp_preserve_single_line_blocks = true
|
||||
#leave statements and member declarations on the same line
|
||||
|
||||
# Leave statements and member declarations on the same line.
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
# IDE0049, IDE-only counterpart of StyleCopAnalyzers - SA1121: UseBuiltInTypeAlias.
|
||||
dotnet_style_predefined_type_for_member_access = true
|
||||
|
||||
#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
|
||||
# IDE0049, IDE-only counterpart of StyleCopAnalyzers - SA1121: UseBuiltInTypeAlias.
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
|
||||
## Others:
|
||||
|
||||
# Show an IDE warning when default access modifiers are explicitly specified.
|
||||
dotnet_style_require_accessibility_modifiers = omit_if_default:warning
|
||||
|
||||
# Don't prefer braces (for one liners).
|
||||
dotnet_diagnostic.IDE0011.severity = silent
|
||||
|
||||
# Object initialization can be simplified / Use object initializer.
|
||||
dotnet_diagnostic.IDE0017.severity = warning
|
||||
|
||||
# Collection initialization can be simplified
|
||||
dotnet_diagnostic.IDE0028.severity = warning
|
||||
|
||||
# Simplify 'default' expression
|
||||
dotnet_diagnostic.IDE0034.severity = warning
|
||||
|
||||
# Modifiers are not ordered.
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
|
||||
# Raise a warning on build when default access modifiers are explicitly specified.
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# Make field readonly.
|
||||
dotnet_diagnostic.IDE0044.severity = warning
|
||||
|
||||
# Unused private member.
|
||||
dotnet_diagnostic.IDE0052.severity = warning
|
||||
|
||||
# Unnecessary value assignment.
|
||||
dotnet_diagnostic.IDE0059.severity = warning
|
||||
|
||||
# Unused parameter.
|
||||
dotnet_diagnostic.IDE0060.severity = warning
|
||||
|
||||
# Naming rule violation.
|
||||
dotnet_diagnostic.IDE1006.severity = warning
|
||||
|
||||
# Avoid unnecessary zero-length array allocations.
|
||||
dotnet_diagnostic.CA1825.severity = warning
|
||||
|
||||
# Do not use Enumerable methods on indexable collections. Instead use the collection directly.
|
||||
dotnet_diagnostic.CA1826.severity = warning
|
||||
|
||||
# Count() is used where Any() could be used instead to improve performance.
|
||||
dotnet_diagnostic.CA1827.severity = warning
|
||||
|
||||
# Use Length/Count property instead of Enumerable.Count method.
|
||||
dotnet_diagnostic.CA1829.severity = warning
|
||||
|
||||
# Use string.Contains(char) instead of string.Contains(string) with single characters.
|
||||
dotnet_diagnostic.CA1847.severity = warning
|
||||
|
||||
; 4-column tab indentation
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
# 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
|
||||
77
.github/workflows/ci.yml
vendored
Normal file
77
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed, 'prep-*' ]
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (.NET 6.0)
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
make check
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make test
|
||||
|
||||
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 RUNTIME=mono check
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
# check-scripts does not depend on .net/mono, so is not needed here
|
||||
make RUNTIME=mono test
|
||||
|
||||
windows:
|
||||
name: Windows (.NET 6.0)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
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
|
||||
dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64
|
||||
dotnet test bin\OpenRA.Test.dll --test-adapter-path:.
|
||||
|
||||
- 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
|
||||
40
.github/workflows/documentation.yml
vendored
40
.github/workflows/documentation.yml
vendored
@@ -19,6 +19,11 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
@@ -65,6 +70,11 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
@@ -81,27 +91,39 @@ jobs:
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/playtest/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/playtest/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/playtest/lua.md"
|
||||
git checkout playtest
|
||||
./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/api/release/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/release/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/release/lua.md"
|
||||
git checkout release
|
||||
./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: |
|
||||
git push origin release
|
||||
|
||||
- name: Push docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
run: |
|
||||
git push origin playtest
|
||||
|
||||
34
.github/workflows/itch.yml
vendored
34
.github/workflows/itch.yml
vendored
@@ -15,17 +15,15 @@ jobs:
|
||||
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 +31,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 +41,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 +51,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 +61,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 +71,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 +81,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
|
||||
|
||||
50
.github/workflows/packaging.yml
vendored
50
.github/workflows/packaging.yml
vendored
@@ -8,6 +8,30 @@ on:
|
||||
- 'devtest-*'
|
||||
|
||||
jobs:
|
||||
source:
|
||||
name: Source Tarball
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Source
|
||||
run: |
|
||||
mkdir -p build/source
|
||||
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/source/*
|
||||
|
||||
linux:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -15,6 +39,11 @@ jobs:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
@@ -33,16 +62,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
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
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 +87,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 }}
|
||||
@@ -69,10 +103,16 @@ jobs:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
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"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
36
AUTHORS
36
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)
|
||||
@@ -75,6 +80,7 @@ Also thanks to:
|
||||
* Imago
|
||||
* Iran
|
||||
* Ishan Bhargava (ishantheperson)
|
||||
* Ivaylo Draganov (dragunoff)
|
||||
* Jacob Dufault (jacobdufault)
|
||||
* James Dunne (jsd)
|
||||
* James Gilbert (DSUK)
|
||||
@@ -135,12 +141,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 +179,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 +205,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.
|
||||
|
||||
52
Directory.Build.props
Normal file
52
Directory.Build.props
Normal file
@@ -0,0 +1,52 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
|
||||
<OutputPath>$(EngineRootPath)/bin</OutputPath>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ExternalConsole>false</ExternalConsole>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<CodeAnalysisRuleSet>$(EngineRootPath)/OpenRA.ruleset</CodeAnalysisRuleSet>
|
||||
<Nullable>disable</Nullable>
|
||||
</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>
|
||||
</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="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" 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.4 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.4 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`.
|
||||
|
||||
149
Makefile
149
Makefile
@@ -1,42 +1,40 @@
|
||||
############################# INSTRUCTIONS #############################
|
||||
#
|
||||
# to compile, run:
|
||||
# make [DEBUG=true]
|
||||
# make
|
||||
#
|
||||
# to compile using Mono (version 6.4 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,160 @@ 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)
|
||||
|
||||
# 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 ($(RUNTIME)-$(DOTNET_RID),net6-osx-arm64)
|
||||
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.4."; 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)
|
||||
# Enabling EnforceCodeStyleInBuild and GenerateDocumentationFile as a workaround for some code style rules (in particular IDE0005) being bugged and not reporting warnings/errors otherwise.
|
||||
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -warnaserror -p:TargetPlatform=$(TARGETPLATFORM) -p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true
|
||||
else
|
||||
# Enabling EnforceCodeStyleInBuild and GenerateDocumentationFile as a workaround for some code style rules (in particular IDE0005) being bugged and not reporting warnings/errors otherwise.
|
||||
@$(DOTNET) build -c Debug -nologo -warnaserror -p:TargetPlatform=$(TARGETPLATFORM) -p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true
|
||||
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 lua/ mods/*/{maps,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
|
||||
|
||||
############# 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.4 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] 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 +207,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 2007-2022 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
|
||||
@@ -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,14 +74,14 @@ 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;
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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,18 @@ 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
|
||||
internal readonly struct SyncHash
|
||||
{
|
||||
public readonly ISync Trait;
|
||||
readonly Func<object, int> hashFunction;
|
||||
@@ -48,30 +58,25 @@ 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;
|
||||
@@ -98,7 +103,7 @@ namespace OpenRA
|
||||
/// <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,7 +580,7 @@ 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);
|
||||
@@ -615,8 +606,8 @@ namespace OpenRA
|
||||
|
||||
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 +620,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 2007-2022 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
|
||||
@@ -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)
|
||||
@@ -58,7 +58,13 @@ namespace OpenRA
|
||||
public bool Equals(CPos other) { return Bits == other.Bits; }
|
||||
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
|
||||
|
||||
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 2007-2022 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
|
||||
@@ -17,7 +17,7 @@ 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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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,20 +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
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public interface ICacheStorage<T>
|
||||
{
|
||||
void Remove(string key);
|
||||
void Store(string key, T data);
|
||||
T Retrieve(string key);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -17,8 +17,8 @@ namespace OpenRA.Effects
|
||||
{
|
||||
public class AsyncAction : IEffect
|
||||
{
|
||||
Action a;
|
||||
IAsyncResult ar;
|
||||
readonly Action a;
|
||||
readonly IAsyncResult ar;
|
||||
|
||||
public AsyncAction(IAsyncResult ar, Action a)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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))
|
||||
@@ -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");
|
||||
|
||||
@@ -191,23 +186,9 @@ namespace OpenRA
|
||||
/// * Filename doesn't match internal key
|
||||
/// * Fails to parse as a mod registration
|
||||
/// </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,13 +203,10 @@ 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)
|
||||
@@ -258,23 +236,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
|
||||
@@ -290,10 +255,33 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
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; } }
|
||||
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 2007-2022 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
|
||||
@@ -38,12 +38,6 @@ namespace OpenRA
|
||||
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)
|
||||
@@ -53,7 +47,7 @@ namespace OpenRA
|
||||
|
||||
public static bool HasAttribute<T>(this MemberInfo mi)
|
||||
{
|
||||
return mi.GetCustomAttributes(typeof(T), true).Length != 0;
|
||||
return Attribute.IsDefined(mi, typeof(T));
|
||||
}
|
||||
|
||||
public static T[] GetCustomAttributes<T>(this MemberInfo mi, bool inherit)
|
||||
@@ -107,7 +101,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);
|
||||
}
|
||||
|
||||
@@ -159,9 +153,9 @@ namespace OpenRA
|
||||
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);
|
||||
@@ -397,7 +401,8 @@ namespace OpenRA
|
||||
|
||||
// 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 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.Slice(0, index);
|
||||
str = span.Slice(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -17,7 +17,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
@@ -25,6 +24,8 @@ namespace OpenRA
|
||||
{
|
||||
public static class FieldLoader
|
||||
{
|
||||
const char SplitComma = ',';
|
||||
|
||||
[Serializable]
|
||||
public class MissingFieldsException : YamlException
|
||||
{
|
||||
@@ -56,26 +57,476 @@ namespace OpenRA
|
||||
|
||||
public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
|
||||
{
|
||||
throw new YamlException("FieldLoader: Cannot parse `{0}` into `{1}.{2}` ".F(s, f, t));
|
||||
throw new YamlException($"FieldLoader: Cannot parse `{s}` into `{f}.{t}` ");
|
||||
};
|
||||
|
||||
public static Action<string, Type> UnknownFieldAction = (s, f) =>
|
||||
{
|
||||
throw new NotImplementedException("FieldLoader: Missing field `{0}` on `{1}`".F(s, f.Name));
|
||||
throw new NotImplementedException($"FieldLoader: Missing field `{s}` on `{f.Name}`");
|
||||
};
|
||||
|
||||
static readonly ConcurrentCache<Type, FieldLoadInfo[]> TypeLoadInfo =
|
||||
new ConcurrentCache<Type, FieldLoadInfo[]>(BuildTypeLoadInfo);
|
||||
static readonly ConcurrentCache<MemberInfo, bool> MemberHasTranslateAttribute =
|
||||
new ConcurrentCache<MemberInfo, bool>(member => member.HasAttribute<TranslateAttribute>());
|
||||
|
||||
static readonly ConcurrentCache<string, BooleanExpression> BooleanExpressionCache =
|
||||
new ConcurrentCache<string, BooleanExpression>(expression => new BooleanExpression(expression));
|
||||
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
|
||||
new ConcurrentCache<string, IntegerExpression>(expression => new IntegerExpression(expression));
|
||||
|
||||
static readonly object TranslationsLock = new object();
|
||||
static Dictionary<string, string> translations;
|
||||
static readonly Dictionary<Type, Func<string, Type, string, MemberInfo, object>> TypeParsers =
|
||||
new Dictionary<Type, Func<string, Type, string, MemberInfo, object>>()
|
||||
{
|
||||
{ typeof(int), ParseInt },
|
||||
{ typeof(ushort), ParseUShort },
|
||||
{ typeof(long), ParseLong },
|
||||
{ typeof(float), ParseFloat },
|
||||
{ typeof(decimal), ParseDecimal },
|
||||
{ typeof(string), ParseString },
|
||||
{ typeof(Color), ParseColor },
|
||||
{ typeof(Hotkey), ParseHotkey },
|
||||
{ typeof(HotkeyReference), ParseHotkeyReference },
|
||||
{ typeof(WDist), ParseWDist },
|
||||
{ typeof(WVec), ParseWVec },
|
||||
{ typeof(WVec[]), ParseWVecArray },
|
||||
{ typeof(WPos), ParseWPos },
|
||||
{ typeof(WAngle), ParseWAngle },
|
||||
{ typeof(WRot), ParseWRot },
|
||||
{ typeof(CPos), ParseCPos },
|
||||
{ typeof(CVec), ParseCVec },
|
||||
{ typeof(CVec[]), ParseCVecArray },
|
||||
{ typeof(BooleanExpression), ParseBooleanExpression },
|
||||
{ typeof(IntegerExpression), ParseIntegerExpression },
|
||||
{ typeof(Enum), ParseEnum },
|
||||
{ typeof(bool), ParseBool },
|
||||
{ typeof(int2[]), ParseInt2Array },
|
||||
{ typeof(Size), ParseSize },
|
||||
{ typeof(int2), ParseInt2 },
|
||||
{ typeof(float2), ParseFloat2 },
|
||||
{ typeof(float3), ParseFloat3 },
|
||||
{ typeof(Rectangle), ParseRectangle },
|
||||
{ typeof(DateTime), ParseDateTime }
|
||||
};
|
||||
|
||||
static readonly Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>> GenericTypeParsers =
|
||||
new Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>>()
|
||||
{
|
||||
{ typeof(HashSet<>), ParseHashSetOrList },
|
||||
{ typeof(List<>), ParseHashSetOrList },
|
||||
{ typeof(Dictionary<,>), ParseDictionary },
|
||||
{ typeof(BitSet<>), ParseBitSet },
|
||||
{ typeof(Nullable<>), ParseNullable },
|
||||
};
|
||||
|
||||
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseUShort(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseLong(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseFloat(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res * (value.Contains('%') ? 0.01f : 1f);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseDecimal(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res * (value.Contains('%') ? 0.01m : 1m);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseString(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
static object ParseColor(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null && Color.TryParse(value, out var color))
|
||||
return color;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseHotkey(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (Hotkey.TryParse(value, out var res))
|
||||
return res;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseHotkeyReference(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
return Game.ModData.Hotkeys[value];
|
||||
}
|
||||
|
||||
static object ParseWDist(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (WDist.TryParse(value, out var res))
|
||||
return res;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWVec(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||
return new WVec(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
|
||||
if (parts.Length % 3 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var vecs = new WVec[parts.Length / 3];
|
||||
|
||||
for (var i = 0; i < vecs.Length; ++i)
|
||||
{
|
||||
if (WDist.TryParse(parts[3 * i], out var rx)
|
||||
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
||||
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||
vecs[i] = new WVec(rx, ry, rz);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWPos(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (WDist.TryParse(parts[0], out var rx)
|
||||
&& WDist.TryParse(parts[1], out var ry)
|
||||
&& WDist.TryParse(parts[2], out var rz))
|
||||
return new WPos(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
return new WAngle(res);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseWRot(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(parts[0], out var rr)
|
||||
&& Exts.TryParseIntegerInvariant(parts[1], out var rp)
|
||||
&& Exts.TryParseIntegerInvariant(parts[2], out var ry))
|
||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseCPos(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 3)
|
||||
return new CPos(
|
||||
Exts.ParseIntegerInvariant(parts[0]),
|
||||
Exts.ParseIntegerInvariant(parts[1]),
|
||||
Exts.ParseByte(parts[2]));
|
||||
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseCVec(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseCVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma);
|
||||
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var vecs = new CVec[parts.Length / 2];
|
||||
for (var i = 0; i < vecs.Length; i++)
|
||||
{
|
||||
if (int.TryParse(parts[2 * i], out var rx)
|
||||
&& int.TryParse(parts[2 * i + 1], out var ry))
|
||||
vecs[i] = new CVec(rx, ry);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseBooleanExpression(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return BooleanExpressionCache[value];
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseIntegerExpression(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return IntegerExpressionCache[value];
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseEnum(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Enum.Parse(fieldType, value, true);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
static object ParseBool(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (bool.TryParse(value.ToLowerInvariant(), out var result))
|
||||
return result;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseInt2Array(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var ints = new int2[parts.Length / 2];
|
||||
for (var i = 0; i < ints.Length; i++)
|
||||
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
|
||||
|
||||
return ints;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseSize(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseInt2(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseFloat2(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
float xx = 0;
|
||||
float yy = 0;
|
||||
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
|
||||
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
|
||||
return new float2(xx, yy);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseFloat3(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
|
||||
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
|
||||
|
||||
// z component is optional for compatibility with older float2 definitions
|
||||
float z = 0;
|
||||
if (parts.Length > 2)
|
||||
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
|
||||
|
||||
return new float3(x, y, z);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseRectangle(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new Rectangle(
|
||||
Exts.ParseIntegerInvariant(parts[0]),
|
||||
Exts.ParseIntegerInvariant(parts[1]),
|
||||
Exts.ParseIntegerInvariant(parts[2]),
|
||||
Exts.ParseIntegerInvariant(parts[3]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseDateTime(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||
{
|
||||
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
|
||||
return dt;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
var set = Activator.CreateInstance(fieldType);
|
||||
if (value == null)
|
||||
return set;
|
||||
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
var arguments = fieldType.GetGenericArguments();
|
||||
var addMethod = fieldType.GetMethod(nameof(List<object>.Add), arguments);
|
||||
var addArgs = new object[1];
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
{
|
||||
addArgs[0] = GetValue(fieldName, arguments[0], parts[i].Trim(), field);
|
||||
addMethod.Invoke(set, addArgs);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
var dict = Activator.CreateInstance(fieldType);
|
||||
var arguments = fieldType.GetGenericArguments();
|
||||
var addMethod = fieldType.GetMethod(nameof(Dictionary<object, object>.Add), arguments);
|
||||
var addArgs = new object[2];
|
||||
foreach (var node in yaml.Nodes)
|
||||
{
|
||||
addArgs[0] = GetValue(fieldName, arguments[0], node.Key, field);
|
||||
addArgs[1] = GetValue(fieldName, arguments[1], node.Value, field);
|
||||
addMethod.Invoke(dict, addArgs);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
static object ParseBitSet(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
var ctor = fieldType.GetConstructor(new[] { typeof(string[]) });
|
||||
return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() });
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
static object ParseNullable(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return null;
|
||||
|
||||
var innerType = fieldType.GetGenericArguments().First();
|
||||
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
||||
}
|
||||
|
||||
public static void Load(object self, MiniYaml my)
|
||||
{
|
||||
@@ -113,7 +564,7 @@ namespace OpenRA
|
||||
fli.Field.SetValue(self, val);
|
||||
}
|
||||
|
||||
if (missing.Any())
|
||||
if (missing.Count > 0)
|
||||
throw new MissingFieldsException(missing.ToArray());
|
||||
}
|
||||
|
||||
@@ -180,402 +631,46 @@ namespace OpenRA
|
||||
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
var value = yaml.Value?.Trim();
|
||||
if (fieldType.IsGenericType)
|
||||
{
|
||||
if (GenericTypeParsers.TryGetValue(fieldType.GetGenericTypeDefinition(), out var parseFuncGeneric))
|
||||
return parseFuncGeneric(fieldName, fieldType, value, yaml, field);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TypeParsers.TryGetValue(fieldType, out var parseFunc))
|
||||
return parseFunc(fieldName, fieldType, value, field);
|
||||
|
||||
if (fieldType == typeof(int))
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(ushort))
|
||||
{
|
||||
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
if (fieldType == typeof(long))
|
||||
{
|
||||
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(float))
|
||||
{
|
||||
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res * (value.Contains('%') ? 0.01f : 1f);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(decimal))
|
||||
{
|
||||
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res * (value.Contains('%') ? 0.01m : 1m);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(string))
|
||||
{
|
||||
if (field != null && MemberHasTranslateAttribute[field] && value != null)
|
||||
return Regex.Replace(value, "@[^@]+@", m => Translate(m.Value.Substring(1, m.Value.Length - 2)), RegexOptions.Compiled);
|
||||
return value;
|
||||
}
|
||||
else if (fieldType == typeof(Color))
|
||||
{
|
||||
if (value != null && Color.TryParse(value, out var color))
|
||||
return color;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(Hotkey))
|
||||
{
|
||||
if (Hotkey.TryParse(value, out var res))
|
||||
return res;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(HotkeyReference))
|
||||
{
|
||||
return Game.ModData.Hotkeys[value];
|
||||
}
|
||||
else if (fieldType == typeof(WDist))
|
||||
{
|
||||
if (WDist.TryParse(value, out var res))
|
||||
return res;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(WVec))
|
||||
{
|
||||
if (value != null)
|
||||
if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||
return new WVec(rx, ry, rz);
|
||||
}
|
||||
if (value == null)
|
||||
return Array.CreateInstance(fieldType.GetElementType(), 0);
|
||||
|
||||
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ?
|
||||
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries;
|
||||
var parts = value.Split(SplitComma, options);
|
||||
|
||||
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(WVec[]))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
|
||||
if (parts.Length % 3 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var vecs = new WVec[parts.Length / 3];
|
||||
|
||||
for (var i = 0; i < vecs.Length; ++i)
|
||||
{
|
||||
if (WDist.TryParse(parts[3 * i], out var rx) && WDist.TryParse(parts[3 * i + 1], out var ry) && WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||
vecs[i] = new WVec(rx, ry, rz);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(WPos))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||
return new WPos(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(WAngle))
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
return new WAngle(res);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(WRot))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(parts[0], out var rr) && Exts.TryParseIntegerInvariant(parts[1], out var rp) && Exts.TryParseIntegerInvariant(parts[2], out var ry))
|
||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(CPos))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(CVec))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(CVec[]))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var vecs = new CVec[parts.Length / 2];
|
||||
for (var i = 0; i < vecs.Length; i++)
|
||||
{
|
||||
if (int.TryParse(parts[2 * i], out var rx) && int.TryParse(parts[2 * i + 1], out var ry))
|
||||
vecs[i] = new CVec(rx, ry);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(BooleanExpression))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return BooleanExpressionCache[value];
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(IntegerExpression))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return IntegerExpressionCache[value];
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType.IsEnum)
|
||||
var conv = TypeDescriptor.GetConverter(fieldType);
|
||||
if (conv.CanConvertFrom(typeof(string)))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Enum.Parse(fieldType, value, true);
|
||||
return conv.ConvertFromInvariantString(value);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
catch
|
||||
{
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
}
|
||||
else if (fieldType == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(value.ToLowerInvariant(), out var result))
|
||||
return result;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(int2[]))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var ints = new int2[parts.Length / 2];
|
||||
for (var i = 0; i < ints.Length; i++)
|
||||
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
|
||||
|
||||
return ints;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
|
||||
{
|
||||
if (value == null)
|
||||
return Array.CreateInstance(fieldType.GetElementType(), 0);
|
||||
|
||||
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ?
|
||||
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries;
|
||||
var parts = value.Split(new char[] { ',' }, options);
|
||||
|
||||
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
|
||||
return ret;
|
||||
}
|
||||
else if (fieldType.IsGenericType && (fieldType.GetGenericTypeDefinition() == typeof(HashSet<>) || fieldType.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
{
|
||||
var set = Activator.CreateInstance(fieldType);
|
||||
if (value == null)
|
||||
return set;
|
||||
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var addMethod = fieldType.GetMethod("Add", fieldType.GetGenericArguments());
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
addMethod.Invoke(set, new[] { GetValue(fieldName, fieldType.GetGenericArguments()[0], parts[i].Trim(), field) });
|
||||
return set;
|
||||
}
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
var dict = Activator.CreateInstance(fieldType);
|
||||
var arguments = fieldType.GetGenericArguments();
|
||||
var addMethod = fieldType.GetMethod("Add", arguments);
|
||||
|
||||
foreach (var node in yaml.Nodes)
|
||||
{
|
||||
var key = GetValue(fieldName, arguments[0], node.Key, field);
|
||||
var val = GetValue(fieldName, arguments[1], node.Value, field);
|
||||
addMethod.Invoke(dict, new[] { key, val });
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
else if (fieldType == typeof(Size))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(int2))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(float2))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
float xx = 0;
|
||||
float yy = 0;
|
||||
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
|
||||
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
|
||||
return new float2(xx, yy);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(float3))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
|
||||
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
|
||||
|
||||
// z component is optional for compatibility with older float2 definitions
|
||||
float z = 0;
|
||||
if (parts.Length > 2)
|
||||
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
|
||||
|
||||
return new float3(x, y, z);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(Rectangle))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new Rectangle(
|
||||
Exts.ParseIntegerInvariant(parts[0]),
|
||||
Exts.ParseIntegerInvariant(parts[1]),
|
||||
Exts.ParseIntegerInvariant(parts[2]),
|
||||
Exts.ParseIntegerInvariant(parts[3]));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(BitSet<>))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var ctor = fieldType.GetConstructor(new[] { typeof(string[]) });
|
||||
return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() });
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return null;
|
||||
|
||||
var innerType = fieldType.GetGenericArguments().First();
|
||||
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
||||
}
|
||||
else if (fieldType == typeof(DateTime))
|
||||
{
|
||||
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
|
||||
return dt;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var conv = TypeDescriptor.GetConverter(fieldType);
|
||||
if (conv.CanConvertFrom(typeof(string)))
|
||||
{
|
||||
try
|
||||
{
|
||||
return conv.ConvertFromInvariantString(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnknownFieldAction("[Type] {0}".F(value), fieldType);
|
||||
UnknownFieldAction($"[Type] {value}", fieldType);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -661,7 +756,7 @@ namespace OpenRA
|
||||
{
|
||||
public static readonly SerializeAttribute Default = new SerializeAttribute(true);
|
||||
|
||||
public bool IsDefault { get { return this == Default; } }
|
||||
public bool IsDefault => this == Default;
|
||||
|
||||
public readonly bool Serialize;
|
||||
public string YamlName;
|
||||
@@ -686,7 +781,7 @@ namespace OpenRA
|
||||
{
|
||||
var method = type.GetMethod(Loader, Flags);
|
||||
if (method == null)
|
||||
throw new InvalidOperationException("{0} does not specify a loader function '{1}'".F(type.Name, Loader));
|
||||
throw new InvalidOperationException($"{type.Name} does not specify a loader function '{Loader}'");
|
||||
|
||||
return (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), method);
|
||||
}
|
||||
@@ -694,34 +789,8 @@ namespace OpenRA
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string Translate(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return key;
|
||||
|
||||
lock (TranslationsLock)
|
||||
{
|
||||
if (translations == null)
|
||||
return key;
|
||||
|
||||
if (!translations.TryGetValue(key, out var value))
|
||||
return key;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetTranslations(IDictionary<string, string> translations)
|
||||
{
|
||||
lock (TranslationsLock)
|
||||
FieldLoader.translations = new Dictionary<string, string>(translations);
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class TranslateAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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,12 +26,15 @@ 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 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 Dictionary<string, string>();
|
||||
|
||||
public int PixelStride => Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4;
|
||||
|
||||
public Png(Stream s)
|
||||
{
|
||||
if (!Verify(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)
|
||||
{
|
||||
@@ -65,14 +68,12 @@ namespace OpenRA.FileFormats
|
||||
|
||||
var bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadByte();
|
||||
isPaletted = IsPaletted(bitDepth, colorType);
|
||||
is24Bit = colorType == PngColorType.Color;
|
||||
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[dataLength];
|
||||
Data = new byte[Width * Height * PixelStride];
|
||||
|
||||
var compression = ms.ReadByte();
|
||||
/*var filter = */ms.ReadByte();
|
||||
@@ -133,39 +134,28 @@ 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];
|
||||
var prevLine = new byte[rowStride];
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)ds.ReadByte();
|
||||
var line = ds.ReadBytes(srcStride);
|
||||
var line = ds.ReadBytes(rowStride);
|
||||
|
||||
for (var i = 0; i < srcStride; i++)
|
||||
for (var i = 0; i < rowStride; 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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
else
|
||||
Array.Copy(line, 0, Data, y * destStride, line.Length);
|
||||
Array.Copy(line, 0, Data, y * rowStride, rowStride);
|
||||
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPaletted && Palette == null)
|
||||
if (Type == SpriteFrameType.Indexed8 && Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
return;
|
||||
@@ -175,7 +165,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 +175,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;
|
||||
@@ -274,9 +299,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 +310,7 @@ namespace OpenRA.FileFormats
|
||||
WritePngChunk(output, "IHDR", header);
|
||||
}
|
||||
|
||||
bool alphaPalette = false;
|
||||
var alphaPalette = false;
|
||||
if (Palette != null)
|
||||
{
|
||||
using (var palette = new MemoryStream())
|
||||
@@ -318,12 +342,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 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public class FileSystem : IReadOnlyFileSystem
|
||||
{
|
||||
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
|
||||
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys;
|
||||
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
|
||||
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
|
||||
readonly string modID;
|
||||
@@ -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
|
||||
@@ -95,7 +95,7 @@ namespace OpenRA.FileSystem
|
||||
name = name.Substring(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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -27,7 +27,7 @@ namespace OpenRA.FileSystem
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public string Name { get { return path; } }
|
||||
public string Name => path;
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -141,8 +141,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
sealed class ZipFolder : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get { return path; } }
|
||||
public ReadOnlyZipFile Parent { get; private set; }
|
||||
public string Name => path;
|
||||
public ReadOnlyZipFile Parent { get; }
|
||||
readonly string path;
|
||||
|
||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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,6 @@ 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;
|
||||
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
|
||||
|
||||
public static InstalledMods Mods { get; private set; }
|
||||
@@ -42,6 +38,7 @@ namespace OpenRA
|
||||
public static Settings Settings;
|
||||
public static CursorManager Cursor;
|
||||
public static bool HideCursor;
|
||||
|
||||
static WorldRenderer worldRenderer;
|
||||
static string modLaunchWrapper;
|
||||
|
||||
@@ -56,7 +53,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 +60,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(() => { return 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 +83,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)
|
||||
{
|
||||
@@ -164,6 +188,7 @@ namespace OpenRA
|
||||
|
||||
using (new PerfTimer("PrepareMap"))
|
||||
map = ModData.PrepareMap(mapUID);
|
||||
|
||||
using (new PerfTimer("NewWorld"))
|
||||
OrderManager.World = new World(ModData, map, OrderManager, type);
|
||||
|
||||
@@ -186,11 +211,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 +229,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")
|
||||
};
|
||||
|
||||
@@ -284,7 +318,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 +330,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 +364,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 +386,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 +395,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 +405,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);
|
||||
|
||||
@@ -379,24 +421,16 @@ namespace OpenRA
|
||||
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath.Substring(1, launchPath.Length - 2);
|
||||
|
||||
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 +439,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 +463,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 +476,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 +501,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);
|
||||
@@ -543,11 +567,13 @@ 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;
|
||||
|
||||
public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); }
|
||||
public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
|
||||
|
||||
[TranslationReference("filename")]
|
||||
static readonly string SavedScreenshot = "saved-screenshot";
|
||||
|
||||
static void TakeScreenshotInner()
|
||||
{
|
||||
using (new PerfTimer("Renderer.SaveScreenshot"))
|
||||
@@ -561,7 +587,7 @@ namespace OpenRA
|
||||
Log.Write("debug", "Taking screenshot " + path);
|
||||
|
||||
Renderer.SaveScreenshot(path);
|
||||
Debug("Saved screenshot " + filename);
|
||||
TextNotificationsManager.Debug(ModData.Translation.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,48 +597,29 @@ 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, () =>
|
||||
Sync.RunUnsynced(world, () =>
|
||||
{
|
||||
world.OrderGenerator.Tick(world);
|
||||
});
|
||||
@@ -621,12 +628,10 @@ namespace OpenRA
|
||||
|
||||
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 +642,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 +782,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 +829,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 +844,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 +898,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 +969,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 +989,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 2007-2022 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
|
||||
@@ -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 TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero;
|
||||
|
||||
public IList<Player> Players { get; }
|
||||
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
|
||||
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
|
||||
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 2007-2022 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
|
||||
@@ -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,10 +110,11 @@ 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 resolved = source.Where(s => s.Dependencies.Count == 0 && s.OptionalDependencies.Count == 0).ToList();
|
||||
var unresolved = source.Except(resolved);
|
||||
|
||||
var testResolve = new Func<Type, Type, bool>((a, b) => a == b || a.IsAssignableFrom(b));
|
||||
@@ -123,7 +123,9 @@ namespace OpenRA
|
||||
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.
|
||||
!unresolved.Any(u1 => testResolve(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 => testResolve(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.
|
||||
@@ -143,7 +145,9 @@ namespace OpenRA
|
||||
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} }}\r\n";
|
||||
}
|
||||
|
||||
throw new YamlException(exceptionString);
|
||||
@@ -162,6 +166,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 2007-2022 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
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -22,12 +22,12 @@ 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 ITerrainInfo TerrainInfo;
|
||||
public readonly SequenceProvider Sequences;
|
||||
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
|
||||
|
||||
@@ -37,16 +37,16 @@ namespace OpenRA
|
||||
IReadOnlyDictionary<string, SoundInfo> voices,
|
||||
IReadOnlyDictionary<string, SoundInfo> notifications,
|
||||
IReadOnlyDictionary<string, MusicInfo> music,
|
||||
TileSet tileSet,
|
||||
ITerrainInfo terrainInfo,
|
||||
SequenceProvider sequences,
|
||||
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
{
|
||||
Actors = actors;
|
||||
Actors = new ActorInfoDictionary(actors);
|
||||
Weapons = weapons;
|
||||
Voices = voices;
|
||||
Notifications = notifications;
|
||||
Music = music;
|
||||
TileSet = tileSet;
|
||||
TerrainInfo = terrainInfo;
|
||||
Sequences = sequences;
|
||||
ModelSequences = modelSequences;
|
||||
|
||||
@@ -60,15 +60,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 +75,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 +89,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 +115,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)
|
||||
@@ -133,7 +131,7 @@ namespace OpenRA
|
||||
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));
|
||||
@@ -171,10 +169,10 @@ namespace OpenRA
|
||||
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
|
||||
{
|
||||
var dr = modData.DefaultRules;
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
var sequences = modData.DefaultSequences[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, sequences, dr.ModelSequences);
|
||||
}
|
||||
|
||||
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
|
||||
@@ -192,7 +190,7 @@ namespace OpenRA
|
||||
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,8 +201,8 @@ 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: Add support for merging custom terrain modifications
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
|
||||
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
|
||||
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
|
||||
@@ -215,7 +213,7 @@ namespace OpenRA
|
||||
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, sequences, modelSequences);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
@@ -237,7 +235,7 @@ namespace OpenRA
|
||||
|
||||
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,7 +248,7 @@ 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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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>();
|
||||
|
||||
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 2007-2022 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
|
||||
@@ -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;
|
||||
@@ -121,13 +121,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")]
|
||||
[FieldLoader.LoadUsing(nameof(LoadWarheads))]
|
||||
public readonly List<IWarhead> Warheads = new List<IWarhead>();
|
||||
|
||||
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 +144,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 +159,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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -10,27 +10,49 @@
|
||||
#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;
|
||||
[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 2007-2022 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
|
||||
@@ -49,42 +49,51 @@ namespace OpenRA.Graphics
|
||||
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());
|
||||
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());
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -52,7 +52,7 @@ namespace OpenRA.Graphics
|
||||
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
|
||||
}
|
||||
|
||||
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)));
|
||||
|
||||
@@ -146,6 +144,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 +162,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 +182,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 +200,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 +234,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")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -35,7 +35,7 @@ namespace OpenRA.Graphics
|
||||
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,28 @@ 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++)
|
||||
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;
|
||||
|
||||
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 +171,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 +191,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 +219,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 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -17,73 +17,94 @@ 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, MutablePalette> mutablePalettes = new Dictionary<string, MutablePalette>();
|
||||
readonly Dictionary<string, int> indices = new Dictionary<string, int>();
|
||||
byte[] buffer = new byte[0];
|
||||
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 * 4);
|
||||
}
|
||||
|
||||
if (allowModifiers)
|
||||
modifiablePalettes.Add(name, new MutablePalette(p));
|
||||
mutablePalettes.Add(name, new MutablePalette(p));
|
||||
else
|
||||
CopyPaletteToBuffer(index, p);
|
||||
}
|
||||
|
||||
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 minHue, float maxHue)
|
||||
{
|
||||
var index = GetPaletteIndex(name);
|
||||
colorShiftBuffer[4 * index + 0] = hueOffset;
|
||||
colorShiftBuffer[4 * index + 1] = satOffset;
|
||||
colorShiftBuffer[4 * index + 2] = minHue;
|
||||
colorShiftBuffer[4 * index + 3] = maxHue;
|
||||
}
|
||||
|
||||
public bool HasColorShift(string name)
|
||||
{
|
||||
var index = GetPaletteIndex(name);
|
||||
return colorShiftBuffer[4 * index + 2] != 0 || colorShiftBuffer[4 * index + 3] != 0;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
CopyModifiablePalettesToBuffer();
|
||||
@@ -97,26 +118,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, 1, 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 +149,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 2007-2022 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
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -29,7 +30,7 @@ namespace OpenRA.Graphics
|
||||
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; }
|
||||
@@ -62,10 +64,15 @@ namespace OpenRA.Graphics
|
||||
|
||||
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();
|
||||
@@ -77,6 +84,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "IDE0060:Remove unused parameter", Justification = "Load game API")]
|
||||
public PlaceholderModelSequenceLoader(ModData modData) { }
|
||||
|
||||
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -42,6 +42,7 @@ namespace OpenRA.Graphics
|
||||
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;
|
||||
@@ -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());
|
||||
@@ -205,7 +209,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));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -42,9 +42,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -18,43 +17,35 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public class PlayerColorRemap : IPaletteRemap
|
||||
{
|
||||
Dictionary<int, Color> remapColors;
|
||||
readonly int[] remapIndices;
|
||||
readonly float hue;
|
||||
readonly float saturation;
|
||||
|
||||
public static int GetRemapIndex(int[] ramp, int i)
|
||||
public PlayerColorRemap(int[] remapIndices, float hue, float saturation)
|
||||
{
|
||||
return ramp[i];
|
||||
}
|
||||
|
||||
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);
|
||||
this.remapIndices = remapIndices;
|
||||
this.hue = hue;
|
||||
this.saturation = saturation;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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,7 +72,7 @@ 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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -26,6 +26,7 @@ namespace OpenRA.Graphics
|
||||
int Length { get; }
|
||||
int Stride { get; }
|
||||
int Facings { get; }
|
||||
int InterpolatedFacings { get; }
|
||||
int Tick { get; }
|
||||
int ZOffset { get; }
|
||||
int ShadowStart { get; }
|
||||
@@ -33,10 +34,13 @@ namespace OpenRA.Graphics
|
||||
int[] Frames { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
float Scale { get; }
|
||||
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
(Sprite, WAngle) GetSpriteWithRotation(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
float GetAlpha(int frame);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
@@ -50,7 +54,7 @@ namespace OpenRA.Graphics
|
||||
readonly string tileSet;
|
||||
readonly Lazy<Sequences> sequences;
|
||||
readonly Lazy<SpriteCache> spriteCache;
|
||||
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
|
||||
public SpriteCache SpriteCache => spriteCache.Value;
|
||||
|
||||
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
|
||||
|
||||
@@ -70,15 +74,15 @@ namespace OpenRA.Graphics
|
||||
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));
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have a sequence named `{sequenceName}`");
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images { get { return sequences.Value.Keys; } }
|
||||
public IEnumerable<string> Images => sequences.Value.Keys;
|
||||
|
||||
public bool HasSequence(string unitName)
|
||||
{
|
||||
@@ -88,7 +92,7 @@ namespace OpenRA.Graphics
|
||||
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));
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
return unitSeq.Value.ContainsKey(sequenceName);
|
||||
}
|
||||
@@ -96,7 +100,7 @@ namespace OpenRA.Graphics
|
||||
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));
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
return unitSeq.Value.Keys;
|
||||
}
|
||||
@@ -123,7 +127,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
return new ReadOnlyDictionary<string, UnitSequences>(items);
|
||||
return items;
|
||||
}
|
||||
|
||||
public void Preload()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -52,9 +52,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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,16 +81,16 @@ namespace OpenRA.Graphics
|
||||
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);
|
||||
|
||||
var rect = Allocate(size, zRamp, spriteOffset);
|
||||
Util.FastCopyIntoChannel(rect, src);
|
||||
Util.FastCopyIntoChannel(rect, src, type);
|
||||
current.CommitBufferedData();
|
||||
return rect;
|
||||
}
|
||||
@@ -96,15 +103,6 @@ namespace OpenRA.Graphics
|
||||
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;
|
||||
@@ -149,9 +147,9 @@ namespace OpenRA.Graphics
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sheet Current { get { return current; } }
|
||||
public TextureChannel CurrentChannel { get { return channel; } }
|
||||
public IEnumerable<Sheet> AllSheets { get { return sheets; } }
|
||||
public Sheet Current => current;
|
||||
public TextureChannel CurrentChannel => channel;
|
||||
public IEnumerable<Sheet> AllSheets => sheets;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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,10 +114,10 @@ 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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -18,11 +18,33 @@ 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
|
||||
@@ -47,7 +69,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public class SpriteCache
|
||||
{
|
||||
public readonly Cache<SpriteFrameType, SheetBuilder> SheetBuilders;
|
||||
public readonly Cache<SheetType, SheetBuilder> SheetBuilders;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly IReadOnlyFileSystem fileSystem;
|
||||
|
||||
@@ -57,7 +79,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
{
|
||||
SheetBuilders = new Cache<SpriteFrameType, SheetBuilder>(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t)));
|
||||
SheetBuilders = new Cache<SheetType, SheetBuilder>(t => new SheetBuilder(t));
|
||||
|
||||
this.fileSystem = fileSystem;
|
||||
this.loaders = loaders;
|
||||
@@ -103,7 +125,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
if (unloaded[i] != null)
|
||||
{
|
||||
sprite[i] = SheetBuilders[unloaded[i].Type].Add(unloaded[i]);
|
||||
sprite[i] = SheetBuilders[SheetBuilder.FrameTypeToSheetType(unloaded[i].Type)].Add(unloaded[i]);
|
||||
unloaded[i] = null;
|
||||
}
|
||||
}
|
||||
@@ -142,7 +164,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 +173,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 +181,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 2007-2022 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
|
||||
@@ -9,14 +9,15 @@
|
||||
*/
|
||||
#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;
|
||||
@@ -24,17 +25,14 @@ namespace OpenRA.Graphics
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
readonly float scale;
|
||||
readonly WAngle rotation = WAngle.Zero;
|
||||
readonly float3 tint;
|
||||
readonly TintModifiers tintModifiers;
|
||||
readonly float alpha;
|
||||
readonly bool isDecoration;
|
||||
readonly bool ignoreWorldTint;
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
|
||||
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, false) { }
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, bool ignoreWorldTint)
|
||||
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, ignoreWorldTint) { }
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float3 tint, bool isDecoration, bool ignoreWorldTint)
|
||||
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;
|
||||
@@ -42,46 +40,83 @@ namespace OpenRA.Graphics
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
this.scale = scale;
|
||||
this.rotation = rotation;
|
||||
this.tint = tint;
|
||||
this.isDecoration = isDecoration;
|
||||
this.ignoreWorldTint = ignoreWorldTint;
|
||||
this.tintModifiers = tintModifiers;
|
||||
this.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))
|
||||
this.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 => offset;
|
||||
public PaletteReference Palette => palette;
|
||||
public int ZOffset => zOffset;
|
||||
public bool IsDecoration => isDecoration;
|
||||
|
||||
public IRenderable WithTint(in float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
|
||||
public float Alpha => alpha;
|
||||
public float3 Tint => tint;
|
||||
public TintModifiers TintModifiers => tintModifiers;
|
||||
|
||||
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 +124,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 2007-2022 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
|
||||
@@ -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)
|
||||
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 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
@@ -12,7 +12,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -20,9 +19,9 @@ 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;
|
||||
@@ -35,22 +34,22 @@ namespace OpenRA.Graphics
|
||||
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;
|
||||
@@ -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,194 +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++));
|
||||
if (!tileset.TryGetTileInfo(tile, out var tileInfo))
|
||||
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 2007-2022 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
|
||||
@@ -13,7 +13,7 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct UISpriteRenderable : IRenderable, IFinalizedRenderable
|
||||
public class UISpriteRenderable : IRenderable, IPalettedRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly Sprite sprite;
|
||||
readonly WPos effectiveWorldPos;
|
||||
@@ -21,8 +21,10 @@ namespace OpenRA.Graphics
|
||||
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;
|
||||
@@ -30,37 +32,48 @@ namespace OpenRA.Graphics
|
||||
this.zOffset = zOffset;
|
||||
this.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))
|
||||
this.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 => effectiveWorldPos;
|
||||
public WVec Offset => WVec.Zero;
|
||||
public bool IsDecoration => true;
|
||||
|
||||
public PaletteReference Palette { get { return palette; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
public PaletteReference Palette => palette;
|
||||
public int ZOffset => zOffset;
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale); }
|
||||
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, 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 2007-2022 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
|
||||
@@ -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 = Util.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 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
|
||||
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 2007-2022 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.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 2007-2022 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
|
||||
@@ -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 Rectangle(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;
|
||||
@@ -75,10 +75,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get
|
||||
{
|
||||
return zoom;
|
||||
}
|
||||
get => zoom;
|
||||
|
||||
private set
|
||||
{
|
||||
@@ -89,7 +86,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public float MinZoom { get { return minZoom; } }
|
||||
public float MinZoom => minZoom;
|
||||
|
||||
public void AdjustZoom(float dz)
|
||||
{
|
||||
@@ -241,7 +238,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
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
|
||||
@@ -253,6 +250,9 @@ namespace OpenRA.Graphics
|
||||
else
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -21,13 +21,12 @@ 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;
|
||||
@@ -46,8 +45,6 @@ namespace OpenRA.Graphics
|
||||
|
||||
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
|
||||
|
||||
bool lastDepthPreviewEnabled;
|
||||
|
||||
internal WorldRenderer(ModData modData, World world)
|
||||
{
|
||||
World = world;
|
||||
@@ -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 name == null ? null : palettes.GetOrAdd(name, createPaletteReference);
|
||||
}
|
||||
|
||||
public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
|
||||
{
|
||||
if (allowOverwrite && palette.Contains(name))
|
||||
@@ -111,6 +113,11 @@ namespace OpenRA.Graphics
|
||||
palettes[name].Palette = pal;
|
||||
}
|
||||
|
||||
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
|
||||
{
|
||||
palette.SetColorShift(name, hueOffset, satOffset, minHue, maxHue);
|
||||
}
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateRenderables()
|
||||
{
|
||||
@@ -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 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 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 2007-2022 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
|
||||
@@ -20,6 +20,8 @@ namespace OpenRA
|
||||
public readonly Hotkey Default = Hotkey.Invalid;
|
||||
public readonly string Description = "";
|
||||
public readonly HashSet<string> Types = new HashSet<string>();
|
||||
public readonly HashSet<string> Contexts = new HashSet<string>();
|
||||
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 2007-2022 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
|
||||
@@ -35,12 +35,12 @@ namespace OpenRA
|
||||
|
||||
foreach (var kv in settings)
|
||||
{
|
||||
if (definitions.ContainsKey(kv.Key))
|
||||
if (definitions.ContainsKey(kv.Key) && !definitions[kv.Key].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;
|
||||
}
|
||||
|
||||
internal Func<Hotkey> GetHotkeyReference(string name)
|
||||
@@ -61,6 +61,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 +71,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 +80,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 HotkeyReference(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 2007-2022 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.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 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -13,7 +13,7 @@ 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 bool IsValid()
|
||||
@@ -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.ToString("F")}"; }
|
||||
|
||||
public string DisplayString()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -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 2007-2022 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
|
||||
@@ -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;
|
||||
@@ -76,17 +76,16 @@ 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);
|
||||
@@ -110,10 +109,9 @@ namespace OpenRA
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
innerState = LinkState.CheckingLink;
|
||||
new Download(playerDatabase.Profile + Fingerprint, _ => { }, onQueryComplete);
|
||||
}
|
||||
|
||||
public void GenerateKeypair()
|
||||
@@ -159,7 +157,7 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
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 2007-2022 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -14,7 +14,7 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public struct MPos : IEquatable<MPos>
|
||||
public readonly struct MPos : IEquatable<MPos>
|
||||
{
|
||||
public readonly int U, V;
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace OpenRA
|
||||
/// <summary>
|
||||
/// Projected map position
|
||||
/// </summary>
|
||||
public struct PPos : IEquatable<PPos>
|
||||
public readonly struct PPos : IEquatable<PPos>
|
||||
{
|
||||
public readonly int U, V;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2022 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
|
||||
@@ -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,6 +60,7 @@ namespace OpenRA
|
||||
public string Version;
|
||||
public string Website;
|
||||
public string WebIcon32;
|
||||
public string WindowTitle;
|
||||
public bool Hidden;
|
||||
}
|
||||
|
||||
@@ -66,17 +79,19 @@ 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"
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
@@ -147,6 +162,9 @@ namespace OpenRA
|
||||
|
||||
MapCompatibility = compat.ToArray();
|
||||
|
||||
if (yaml.ContainsKey("DefaultOrderGenerator"))
|
||||
DefaultOrderGenerator = yaml["DefaultOrderGenerator"].Value;
|
||||
|
||||
if (yaml.ContainsKey("PackageFormats"))
|
||||
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", yaml["PackageFormats"].Value);
|
||||
|
||||
@@ -155,6 +173,9 @@ namespace OpenRA
|
||||
|
||||
if (yaml.ContainsKey("SpriteFormats"))
|
||||
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
|
||||
|
||||
if (yaml.ContainsKey("VideoFormats"))
|
||||
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", yaml["VideoFormats"].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 2007-2022 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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,16 +150,16 @@ 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);
|
||||
var field = typeof(ValueActorInit<T>).GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, value);
|
||||
}
|
||||
@@ -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,14 +246,14 @@ namespace OpenRA
|
||||
|
||||
public void Initialize(MiniYaml yaml)
|
||||
{
|
||||
var field = GetType().GetField("InternalName", BindingFlags.Public | BindingFlags.Instance);
|
||||
var field = typeof(OwnerInit).GetField(nameof(InternalName), BindingFlags.Public | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, yaml.Value);
|
||||
}
|
||||
|
||||
public void Initialize(Player player)
|
||||
{
|
||||
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var field = typeof(OwnerInit).GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, player);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user