Compare commits
1400 Commits
playtest-2
...
sponsors
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71feb90ef2 | ||
|
|
f08a0b113e | ||
|
|
3a8957c6f3 | ||
|
|
99006a02ed | ||
|
|
77e1e77387 | ||
|
|
0604a58581 | ||
|
|
58e482c05a | ||
|
|
eba266aecf | ||
|
|
5bf4daddec | ||
|
|
54c08748e0 | ||
|
|
b8e343bee9 | ||
|
|
5ae4662f08 | ||
|
|
b3159d7515 | ||
|
|
a1e62158e2 | ||
|
|
777d966958 | ||
|
|
0b75991fbc | ||
|
|
e0e219793f | ||
|
|
8f412f869d | ||
|
|
6421c17515 | ||
|
|
408f30b5cd | ||
|
|
db74f155bb | ||
|
|
978de64903 | ||
|
|
e616cd1bcb | ||
|
|
2c84c43607 | ||
|
|
442d91537e | ||
|
|
7f92d64d84 | ||
|
|
c7700a8a2b | ||
|
|
e389c00a11 | ||
|
|
52b597d5d2 | ||
|
|
8a587ddeab | ||
|
|
512dee0ac0 | ||
|
|
024beacafb | ||
|
|
b4256df9c1 | ||
|
|
c860bf19ee | ||
|
|
06ea7bf923 | ||
|
|
3551eb3128 | ||
|
|
7f94d67d39 | ||
|
|
72c0c7e38b | ||
|
|
2a2785d8c9 | ||
|
|
7a3dae428a | ||
|
|
dcd3e8d444 | ||
|
|
68710e48a6 | ||
|
|
c88d1bbefa | ||
|
|
d509d3f5f9 | ||
|
|
8a4945d20a | ||
|
|
0ea6a5626f | ||
|
|
864cc4becc | ||
|
|
d2257f9784 | ||
|
|
f70457a66f | ||
|
|
278a4acf96 | ||
|
|
be3412ee74 | ||
|
|
27f9f35efb | ||
|
|
ef1aee5e95 | ||
|
|
962d6496bd | ||
|
|
91c5f2cabe | ||
|
|
a9c6430924 | ||
|
|
2ea2815529 | ||
|
|
5eb4c6a0bb | ||
|
|
4145723ef0 | ||
|
|
b8ba1b36fe | ||
|
|
f63e15f5de | ||
|
|
2f44b016b0 | ||
|
|
b7bba5d55a | ||
|
|
fba4c5049c | ||
|
|
573a6cf645 | ||
|
|
eff7e803bf | ||
|
|
58f55b808a | ||
|
|
777a927c04 | ||
|
|
0249116206 | ||
|
|
2d0e7040db | ||
|
|
edd3a2eb75 | ||
|
|
29f4f5a0cd | ||
|
|
ba7e1319ac | ||
|
|
7a1169744e | ||
|
|
453d59ae16 | ||
|
|
35e9fade06 | ||
|
|
8fc042fed1 | ||
|
|
2c5fce5e3c | ||
|
|
7a93ff3258 | ||
|
|
9291263609 | ||
|
|
f4a5878a53 | ||
|
|
1ecb3bf99a | ||
|
|
c88994a0dd | ||
|
|
29b0999da3 | ||
|
|
b08117dc93 | ||
|
|
99322cee8f | ||
|
|
e201e410f4 | ||
|
|
24f64ae1a8 | ||
|
|
1f3f489328 | ||
|
|
31056d4253 | ||
|
|
70892a6661 | ||
|
|
8e94e1d5ec | ||
|
|
2e6f444285 | ||
|
|
dcaa658678 | ||
|
|
249bc4abc8 | ||
|
|
757ca0561f | ||
|
|
001134ce59 | ||
|
|
f8ed768e39 | ||
|
|
1c6ca394c1 | ||
|
|
627fd4e68b | ||
|
|
7bfe83cce7 | ||
|
|
d169210531 | ||
|
|
902006bf53 | ||
|
|
dcb70d12e3 | ||
|
|
cd90c70cdf | ||
|
|
f1f5df3749 | ||
|
|
b2f18ad0ad | ||
|
|
af0b7621d2 | ||
|
|
2bd7869059 | ||
|
|
e1816a6da0 | ||
|
|
f3777a25e6 | ||
|
|
5e1468facb | ||
|
|
3e6e5a83f3 | ||
|
|
25c095619a | ||
|
|
407372268d | ||
|
|
aa798c252b | ||
|
|
9c658ad36b | ||
|
|
2923077b3d | ||
|
|
cbdf6c3747 | ||
|
|
9687988976 | ||
|
|
64e76e1a90 | ||
|
|
df8295fa2c | ||
|
|
0ac277a88d | ||
|
|
5a548d6acc | ||
|
|
1262a9c6c9 | ||
|
|
5ecb3eec16 | ||
|
|
2ea6bfba7b | ||
|
|
6af354ff99 | ||
|
|
0a02bd524a | ||
|
|
f9b058d36b | ||
|
|
bc51461427 | ||
|
|
fca12fd707 | ||
|
|
1f1373509e | ||
|
|
a8900d9860 | ||
|
|
6967c1fff3 | ||
|
|
2742985520 | ||
|
|
347a09e6cf | ||
|
|
e1a28078bb | ||
|
|
27562ab88f | ||
|
|
9371cecc00 | ||
|
|
feba9f2b9f | ||
|
|
6d55161043 | ||
|
|
7356f2506b | ||
|
|
ad4425d11e | ||
|
|
c8ab409d38 | ||
|
|
9a587d2aaa | ||
|
|
bcbd2418b4 | ||
|
|
8a776c7138 | ||
|
|
6810469634 | ||
|
|
5a7a09a6a7 | ||
|
|
e60e7beeef | ||
|
|
9313638997 | ||
|
|
1af2ab566e | ||
|
|
de6b8b6a74 | ||
|
|
62207313a0 | ||
|
|
b3b10729cd | ||
|
|
91f99eeb4c | ||
|
|
a1b3450b47 | ||
|
|
da4bf7f191 | ||
|
|
a893cf9cb6 | ||
|
|
9fa5dcc055 | ||
|
|
fe146cb77a | ||
|
|
fc49d6943a | ||
|
|
44b2dda585 | ||
|
|
3980e4fa90 | ||
|
|
ca2bef3cd1 | ||
|
|
cbd82c7204 | ||
|
|
82115c6bf7 | ||
|
|
2a26ddc622 | ||
|
|
6535411744 | ||
|
|
7c02b4d264 | ||
|
|
7e79e69eae | ||
|
|
dacacdf130 | ||
|
|
8fede9d6ba | ||
|
|
d10c592987 | ||
|
|
acccb01c76 | ||
|
|
6876fe45e1 | ||
|
|
52a4b5acd7 | ||
|
|
92b6401360 | ||
|
|
ea24a15011 | ||
|
|
243dc965c0 | ||
|
|
f1f9098109 | ||
|
|
79b3f9bf06 | ||
|
|
dff245a9ce | ||
|
|
91f9fa0f1f | ||
|
|
6f919fc232 | ||
|
|
ce277481c3 | ||
|
|
d2039a14e1 | ||
|
|
a27ef16911 | ||
|
|
dc3dc7df73 | ||
|
|
872adbec0a | ||
|
|
c64cfea179 | ||
|
|
84dff779ac | ||
|
|
0d4b81fe6f | ||
|
|
0735345674 | ||
|
|
bb15bd20c0 | ||
|
|
95f5d162ef | ||
|
|
7967a462a1 | ||
|
|
e225785744 | ||
|
|
46dcdfa58e | ||
|
|
f8debe340f | ||
|
|
83f99727a7 | ||
|
|
cbf84f62d4 | ||
|
|
fa68954dda | ||
|
|
98caae106f | ||
|
|
3bc42543fa | ||
|
|
57d955ec72 | ||
|
|
96e333a30e | ||
|
|
560f1a6466 | ||
|
|
4042d5b179 | ||
|
|
9d62ce214c | ||
|
|
f65de2dd43 | ||
|
|
7b58f03f1c | ||
|
|
52577c1de9 | ||
|
|
fc3f200357 | ||
|
|
d89f14dcbc | ||
|
|
96b8273916 | ||
|
|
eda79d8626 | ||
|
|
0c50057220 | ||
|
|
10676be377 | ||
|
|
1385aca783 | ||
|
|
121959efe4 | ||
|
|
8d2ec78713 | ||
|
|
01371f2c65 | ||
|
|
b344bba59a | ||
|
|
a6d393f19b | ||
|
|
7e19d6a205 | ||
|
|
92a9f1e234 | ||
|
|
c39f7e521a | ||
|
|
c9b9efe745 | ||
|
|
a5a371f1ff | ||
|
|
b491e892ff | ||
|
|
771932354b | ||
|
|
89f270b67c | ||
|
|
3276373745 | ||
|
|
5667081764 | ||
|
|
b23d533006 | ||
|
|
1f01d0b6b1 | ||
|
|
f1a9a5180d | ||
|
|
bb8a634ba8 | ||
|
|
f9294f0e9e | ||
|
|
9770967b04 | ||
|
|
0bbb32e8ac | ||
|
|
61d64287e1 | ||
|
|
abee274f88 | ||
|
|
53e6d974f0 | ||
|
|
e3fd54e147 | ||
|
|
52d39db84a | ||
|
|
27ddae3df9 | ||
|
|
4c7e3d8f3a | ||
|
|
646495fc5f | ||
|
|
a9661a233a | ||
|
|
2d05e10819 | ||
|
|
5a0bcc01a6 | ||
|
|
0d3c624bbc | ||
|
|
26fbcf6076 | ||
|
|
418fca3d9e | ||
|
|
dd366f8cf9 | ||
|
|
6b63e88056 | ||
|
|
a96e2fb588 | ||
|
|
ca2f966c3b | ||
|
|
e161d9daa7 | ||
|
|
aa834db1e3 | ||
|
|
a1df91b665 | ||
|
|
bc286b78bf | ||
|
|
19c7e14393 | ||
|
|
1a9dfc0893 | ||
|
|
fe129956bb | ||
|
|
eec7de4646 | ||
|
|
605181efe4 | ||
|
|
fc0ed75a94 | ||
|
|
d95c4146e2 | ||
|
|
38f0d50648 | ||
|
|
808d8e63bc | ||
|
|
55967035d9 | ||
|
|
13a101f11f | ||
|
|
35a0a3cf90 | ||
|
|
08d8f5d8d9 | ||
|
|
f7c9eccf7a | ||
|
|
6ba9e64380 | ||
|
|
8b0a3ea680 | ||
|
|
e12ff2c59d | ||
|
|
afbdb395b2 | ||
|
|
3d381e6e32 | ||
|
|
a02737107e | ||
|
|
590ab88c45 | ||
|
|
7a7c07e9c4 | ||
|
|
40aafe586d | ||
|
|
6b93f955a4 | ||
|
|
75a3bb4f0b | ||
|
|
441e18b898 | ||
|
|
b8e64df4b1 | ||
|
|
d15e7f76fc | ||
|
|
3f510b6d93 | ||
|
|
d60c05eff3 | ||
|
|
7c0e4b25ae | ||
|
|
852241d98e | ||
|
|
8deba81214 | ||
|
|
887a093f46 | ||
|
|
4778dba36d | ||
|
|
438b2240f4 | ||
|
|
58a6333834 | ||
|
|
f8feec685f | ||
|
|
79404cd397 | ||
|
|
6a6c5848c2 | ||
|
|
420c8ebc4c | ||
|
|
c0ea95ca46 | ||
|
|
1a8d971145 | ||
|
|
7afcb9d757 | ||
|
|
8a5c0736f5 | ||
|
|
12b6bb9448 | ||
|
|
e2a6b55d44 | ||
|
|
c240c0a24b | ||
|
|
2528b79610 | ||
|
|
e13fd4816e | ||
|
|
1f6e0f582a | ||
|
|
bbbed49f82 | ||
|
|
cefb2e7cc6 | ||
|
|
470bc4e092 | ||
|
|
08c7c80bb7 | ||
|
|
5832ec76d4 | ||
|
|
594e5b80d7 | ||
|
|
14a434975a | ||
|
|
6854b23bcc | ||
|
|
0c52d275fa | ||
|
|
e63b9b4986 | ||
|
|
0e270bec56 | ||
|
|
f5f06b86ad | ||
|
|
fcc3008b00 | ||
|
|
0bdd46451e | ||
|
|
c35e9fb016 | ||
|
|
80e92849da | ||
|
|
dcd8eccee4 | ||
|
|
5adcbe4c78 | ||
|
|
0b93556c06 | ||
|
|
7e9d291223 | ||
|
|
1dc26a9b8e | ||
|
|
3dbc6400a6 | ||
|
|
adf1aeb06a | ||
|
|
be224934a5 | ||
|
|
6b74093c04 | ||
|
|
65c796dec7 | ||
|
|
503e706d45 | ||
|
|
a1f974bd40 | ||
|
|
11171ff649 | ||
|
|
3ff1888d74 | ||
|
|
cbea08e1fe | ||
|
|
b622afd7fd | ||
|
|
7694d0842b | ||
|
|
a90e6940ab | ||
|
|
96c3825b6a | ||
|
|
a6467cb515 | ||
|
|
d52ba83f96 | ||
|
|
2473b8763b | ||
|
|
7073279ab8 | ||
|
|
ed43071792 | ||
|
|
4a1e4f3e16 | ||
|
|
555c43843b | ||
|
|
a0d75d1e63 | ||
|
|
1a7a47fa08 | ||
|
|
c2279d3071 | ||
|
|
ed295ae315 | ||
|
|
53b781960c | ||
|
|
6b794ba3e5 | ||
|
|
2f95e56256 | ||
|
|
c8644adc85 | ||
|
|
8b81078929 | ||
|
|
76a10283c4 | ||
|
|
0975102e92 | ||
|
|
445d943549 | ||
|
|
554486fb0b | ||
|
|
1c1af89bb9 | ||
|
|
536c130a39 | ||
|
|
5aa67a42c1 | ||
|
|
f661d1ba48 | ||
|
|
717b9ba1e6 | ||
|
|
c57cc96145 | ||
|
|
16fa1ef144 | ||
|
|
788f0f3e24 | ||
|
|
ccf9c8fb22 | ||
|
|
ee31146501 | ||
|
|
f176a0ed83 | ||
|
|
e7cfd2765c | ||
|
|
96c4554644 | ||
|
|
df94f0ec8b | ||
|
|
9ee7294c81 | ||
|
|
5cf622fb6e | ||
|
|
8711d8799c | ||
|
|
e9a6a3afc5 | ||
|
|
9c29264be7 | ||
|
|
e6c9d5fc96 | ||
|
|
d09476c603 | ||
|
|
5bda6852a4 | ||
|
|
641b05eb21 | ||
|
|
62fd11d5c4 | ||
|
|
e807664437 | ||
|
|
e46440b94f | ||
|
|
30ea1e23b4 | ||
|
|
4db2fd980a | ||
|
|
9580d61fb0 | ||
|
|
27aefcc0ce | ||
|
|
b8b1a4930c | ||
|
|
d7a40f3bdb | ||
|
|
fb0031d34a | ||
|
|
b3821e71dc | ||
|
|
8a90413b87 | ||
|
|
115984054d | ||
|
|
69addda86c | ||
|
|
c2379f251d | ||
|
|
3422bf1b59 | ||
|
|
e830d1e9c2 | ||
|
|
f767c24601 | ||
|
|
2c09b1414c | ||
|
|
df1191db5b | ||
|
|
62433ecb8b | ||
|
|
09f680201f | ||
|
|
5ba662cfae | ||
|
|
d6d0fd597d | ||
|
|
fec1c813a5 | ||
|
|
547b6d19dc | ||
|
|
5e6dd50c3b | ||
|
|
4251ed69bb | ||
|
|
b05ee80d5c | ||
|
|
84ced8704d | ||
|
|
58313520f0 | ||
|
|
0d8ef1a1dd | ||
|
|
28ddab7cc2 | ||
|
|
8f06b0a836 | ||
|
|
3b768dacf5 | ||
|
|
d67b3245e0 | ||
|
|
a1dd1fc5d1 | ||
|
|
889153ce81 | ||
|
|
30e5c807b0 | ||
|
|
db2fded24d | ||
|
|
82a9809192 | ||
|
|
f6b40b2bce | ||
|
|
fd75e03d9c | ||
|
|
d6a05f2ea2 | ||
|
|
f51603e440 | ||
|
|
f341c9a8a6 | ||
|
|
765944cfa2 | ||
|
|
15c926b6b9 | ||
|
|
b486112c98 | ||
|
|
223a0015a6 | ||
|
|
632af7c7e6 | ||
|
|
142870d78a | ||
|
|
b0aa32cd1b | ||
|
|
281229e8b4 | ||
|
|
8f0d53ffd9 | ||
|
|
ddfa5a4d35 | ||
|
|
cbf2e2e2ef | ||
|
|
62803aff88 | ||
|
|
bc9cd57fa0 | ||
|
|
93fa9bdfb8 | ||
|
|
808c5f0951 | ||
|
|
a70637f28f | ||
|
|
a12e4657ee | ||
|
|
53db1230ab | ||
|
|
207e09fea9 | ||
|
|
b86b638700 | ||
|
|
995c33a942 | ||
|
|
6d6efd5fe8 | ||
|
|
2782620081 | ||
|
|
be2ca77acf | ||
|
|
87790069e9 | ||
|
|
0a374e2264 | ||
|
|
2eee911c58 | ||
|
|
bb65646c4d | ||
|
|
fea700fb72 | ||
|
|
560c3230cd | ||
|
|
e990a83b7a | ||
|
|
02a2624bcc | ||
|
|
c7c78eda80 | ||
|
|
b1200b8078 | ||
|
|
0cb25f1044 | ||
|
|
8ee728891d | ||
|
|
bbaa475287 | ||
|
|
7bc17b59f5 | ||
|
|
514652bb6a | ||
|
|
3e7665146a | ||
|
|
16d0f8a5a6 | ||
|
|
aeab9a8116 | ||
|
|
f9a5669eb4 | ||
|
|
868736fe1a | ||
|
|
0984de6bea | ||
|
|
69ffd70d3f | ||
|
|
2fae69c0ba | ||
|
|
f1d66a4c70 | ||
|
|
84ce33fe9c | ||
|
|
e583165dff | ||
|
|
7bedba5837 | ||
|
|
8c9b9df125 | ||
|
|
96641873ae | ||
|
|
8edd9de278 | ||
|
|
67754e8693 | ||
|
|
b88495c689 | ||
|
|
71b13c7b5d | ||
|
|
b3f39bffce | ||
|
|
b44a9ab5fc | ||
|
|
e34255b0b2 | ||
|
|
2fee62af4d | ||
|
|
fc1032cd9e | ||
|
|
60195e2842 | ||
|
|
6fed31717c | ||
|
|
aee3eb99c3 | ||
|
|
3aa6fd3dc4 | ||
|
|
5e74e58b22 | ||
|
|
fef7a018f2 | ||
|
|
1d4891b017 | ||
|
|
e9a803e3c1 | ||
|
|
4abe7d5895 | ||
|
|
cd68db5c11 | ||
|
|
ae9f437bb8 | ||
|
|
a047893049 | ||
|
|
5b2733decf | ||
|
|
ff60540fac | ||
|
|
a68a91bb39 | ||
|
|
0bf8d2241f | ||
|
|
06850e6271 | ||
|
|
62fa3b7c9c | ||
|
|
ce09b402d0 | ||
|
|
6e7ad9df25 | ||
|
|
fb20479379 | ||
|
|
9d181e88d2 | ||
|
|
e90fc1ef39 | ||
|
|
ce013f17d6 | ||
|
|
78253ce284 | ||
|
|
2671e40c1d | ||
|
|
04cda69ef9 | ||
|
|
b4c483ce1a | ||
|
|
9a9f58d744 | ||
|
|
d38fe542a2 | ||
|
|
80503fbf36 | ||
|
|
99a23b4056 | ||
|
|
13a7de4b6b | ||
|
|
73bba97aaa | ||
|
|
d6e9cdab5b | ||
|
|
1a177bc2de | ||
|
|
e0b3e631fe | ||
|
|
2518a353af | ||
|
|
989800efff | ||
|
|
c02846e2cb | ||
|
|
09db4a0e25 | ||
|
|
a85da9d86c | ||
|
|
920d00bbae | ||
|
|
62475279ee | ||
|
|
d8e979d283 | ||
|
|
299b8880dd | ||
|
|
61027e4067 | ||
|
|
a7249c10dc | ||
|
|
611d12ac78 | ||
|
|
ef9f26a60d | ||
|
|
aeaffc0a8e | ||
|
|
e3084e230e | ||
|
|
4c01c772f8 | ||
|
|
ed94f7680a | ||
|
|
408d66cdaf | ||
|
|
c4a0f2f169 | ||
|
|
26b28d26da | ||
|
|
8ded6dafd4 | ||
|
|
57a94ad667 | ||
|
|
53933a4d8f | ||
|
|
6606d7dd93 | ||
|
|
7a256dcafa | ||
|
|
7899c52b6d | ||
|
|
919c670502 | ||
|
|
aac3174efc | ||
|
|
a8d3d5c79a | ||
|
|
7c852d90fb | ||
|
|
269ce9c406 | ||
|
|
53d98ec255 | ||
|
|
7a7cd21578 | ||
|
|
2b363e4e1c | ||
|
|
b1560ae69c | ||
|
|
f3ebe07540 | ||
|
|
6cac587753 | ||
|
|
09e6cc4add | ||
|
|
5a2f91be1c | ||
|
|
25071b813e | ||
|
|
d61bd675c4 | ||
|
|
582e2774ac | ||
|
|
5a52ce5330 | ||
|
|
31c9b4fc80 | ||
|
|
4953ce314b | ||
|
|
86519bfda5 | ||
|
|
2c9a36b9a3 | ||
|
|
d3847d49ed | ||
|
|
57f40a0b20 | ||
|
|
fa05f4e4b0 | ||
|
|
754f41ecd1 | ||
|
|
4b6e1c2198 | ||
|
|
e6db2c98d0 | ||
|
|
942f1e2d9e | ||
|
|
da5c94858f | ||
|
|
77ffc20a5f | ||
|
|
d75fed3a00 | ||
|
|
2aba054fe8 | ||
|
|
3b1f4ba07f | ||
|
|
d35768e0f9 | ||
|
|
07a10069db | ||
|
|
20fe59e844 | ||
|
|
6ad5b9ebc4 | ||
|
|
dd0b08d54a | ||
|
|
1cc1f93fb0 | ||
|
|
72f1f06ebc | ||
|
|
151cea96e9 | ||
|
|
888915b53b | ||
|
|
de7a84e8ed | ||
|
|
1dd5b113c7 | ||
|
|
e9ad38667e | ||
|
|
05e8026713 | ||
|
|
5b75649888 | ||
|
|
c22392eb1e | ||
|
|
7c2a51df38 | ||
|
|
05cb9b1fbf | ||
|
|
6bba35c330 | ||
|
|
90d9ee1f5c | ||
|
|
385f01247b | ||
|
|
996029ee38 | ||
|
|
87929b3d91 | ||
|
|
e7e50cc101 | ||
|
|
d6c0926856 | ||
|
|
99ec119ffd | ||
|
|
6b2920cc91 | ||
|
|
05f933f007 | ||
|
|
80aeb5ada6 | ||
|
|
83ea65d4ff | ||
|
|
46b3b01b89 | ||
|
|
973f679939 | ||
|
|
6de1b7b915 | ||
|
|
e7ce739fec | ||
|
|
bf14a4ce80 | ||
|
|
8840a690c6 | ||
|
|
77e85e7c58 | ||
|
|
6943cf5ad5 | ||
|
|
17996dfdfc | ||
|
|
7f32776701 | ||
|
|
fce109a46d | ||
|
|
748292324a | ||
|
|
efe0de2ecb | ||
|
|
cac9940736 | ||
|
|
559b143265 | ||
|
|
a66305e282 | ||
|
|
b3aa61ee8e | ||
|
|
80436a3195 | ||
|
|
5127a6813d | ||
|
|
84eb3c54ef | ||
|
|
ea3c7a3c34 | ||
|
|
718cf37146 | ||
|
|
10f645bf77 | ||
|
|
eda9966d27 | ||
|
|
672172d1f1 | ||
|
|
a366e37014 | ||
|
|
d66e0bb22e | ||
|
|
13581c030d | ||
|
|
13a8b6bda2 | ||
|
|
3aaaa95618 | ||
|
|
e3929d7ded | ||
|
|
2c62f747d9 | ||
|
|
e08818cca1 | ||
|
|
b4670345dd | ||
|
|
0320dcdef9 | ||
|
|
8b12cce250 | ||
|
|
6bb7ab0f97 | ||
|
|
b32346d65f | ||
|
|
8f9c212921 | ||
|
|
df39d4fcc4 | ||
|
|
4daa5193b6 | ||
|
|
13596c1474 | ||
|
|
bb71b59e18 | ||
|
|
f2797c711c | ||
|
|
6b6b1e56e6 | ||
|
|
7b75a78e38 | ||
|
|
2cea4b26e8 | ||
|
|
48f4a98c6a | ||
|
|
9ddc9073c2 | ||
|
|
774f2e0852 | ||
|
|
f5a963ac47 | ||
|
|
38f1f1e5c2 | ||
|
|
4cdbf74256 | ||
|
|
bd0738c5c4 | ||
|
|
72c82cb080 | ||
|
|
b9dd59cd63 | ||
|
|
79019b06ca | ||
|
|
90b25be1b6 | ||
|
|
e4faa6b0f0 | ||
|
|
1a3dfdc67f | ||
|
|
8d2156fb30 | ||
|
|
5e032edd28 | ||
|
|
63d597e4ad | ||
|
|
4135045ca4 | ||
|
|
538623c835 | ||
|
|
3674583053 | ||
|
|
daa8c74c37 | ||
|
|
8aeec24c9b | ||
|
|
54c4a05062 | ||
|
|
466de89e17 | ||
|
|
6eaf51d450 | ||
|
|
7c8dc5d5f4 | ||
|
|
4f34d3edb3 | ||
|
|
0efdbc762d | ||
|
|
5e42c03afc | ||
|
|
58726160a9 | ||
|
|
8cd9215756 | ||
|
|
0e33640b14 | ||
|
|
a1c8cbab0b | ||
|
|
50b484df56 | ||
|
|
99facd2797 | ||
|
|
f79e1cacf0 | ||
|
|
82069db724 | ||
|
|
5a7dc385a3 | ||
|
|
49e7a33db0 | ||
|
|
90b26681eb | ||
|
|
14fc0254c6 | ||
|
|
214aa64ce3 | ||
|
|
a7bb217887 | ||
|
|
10cb8090ec | ||
|
|
1fdecb4e9d | ||
|
|
575596c9ad | ||
|
|
e11c8436bd | ||
|
|
c23efea402 | ||
|
|
8d3cec5bea | ||
|
|
87389d3051 | ||
|
|
71e3ca4493 | ||
|
|
2adee1e374 | ||
|
|
094ccf76b0 | ||
|
|
bb116034c7 | ||
|
|
da53d5b776 | ||
|
|
75fe0e524f | ||
|
|
0ded0355c1 | ||
|
|
4da14cee0a | ||
|
|
90c8e112e2 | ||
|
|
d49bf81d95 | ||
|
|
abbe16b6ec | ||
|
|
274bf06eae | ||
|
|
dcf7b6b3f6 | ||
|
|
4173e40a9d | ||
|
|
1abd272862 | ||
|
|
946bf7b9fa | ||
|
|
0d64fa549b | ||
|
|
8a7020b4ef | ||
|
|
1bc19e788c | ||
|
|
c17110dac5 | ||
|
|
d07b34e67e | ||
|
|
accecee018 | ||
|
|
1861174d38 | ||
|
|
8d5ed65feb | ||
|
|
a375f0e58a | ||
|
|
b2b639434c | ||
|
|
5eadd26f66 | ||
|
|
c0cbca26ea | ||
|
|
8fb65fd9bf | ||
|
|
9072d645fd | ||
|
|
597b8b1caa | ||
|
|
2c0d512727 | ||
|
|
87c5cc96ad | ||
|
|
fc844cfa6d | ||
|
|
1ab1c30e39 | ||
|
|
f2a1a497c7 | ||
|
|
1ddbe50b3f | ||
|
|
dd7b8b24af | ||
|
|
6dcb701d1d | ||
|
|
f67b7ad837 | ||
|
|
a4a409f39b | ||
|
|
904a5f60d1 | ||
|
|
ca8341d432 | ||
|
|
815bbc6ee8 | ||
|
|
ca8870a5cf | ||
|
|
e5da58e2b4 | ||
|
|
dd18829def | ||
|
|
41814a881d | ||
|
|
d708f46d50 | ||
|
|
11f57b2b26 | ||
|
|
60df247416 | ||
|
|
6f32196f89 | ||
|
|
d647aab7fe | ||
|
|
be88c33399 | ||
|
|
7372da150a | ||
|
|
3ec3eac160 | ||
|
|
0990caefd7 | ||
|
|
fc1786e243 | ||
|
|
7e61199458 | ||
|
|
0672553a07 | ||
|
|
8d1f72c104 | ||
|
|
6337067032 | ||
|
|
7395a3ed2d | ||
|
|
e4e1878a4b | ||
|
|
cc8908d7eb | ||
|
|
0dac4520ad | ||
|
|
cc2e369475 | ||
|
|
92189e4b50 | ||
|
|
2e8c85ff0b | ||
|
|
798aff1140 | ||
|
|
8596ce00cc | ||
|
|
b5613acad8 | ||
|
|
7e992d44c8 | ||
|
|
7a7393b9f0 | ||
|
|
f13e6fb76d | ||
|
|
3e83346915 | ||
|
|
761d3583c4 | ||
|
|
1a4b773fda | ||
|
|
930f8ab207 | ||
|
|
2e438f1da9 | ||
|
|
4627387ae3 | ||
|
|
fd69bce609 | ||
|
|
391d9030cb | ||
|
|
1ad9a4b65d | ||
|
|
c6cc2405d3 | ||
|
|
0e39cc7829 | ||
|
|
8d7e5f4663 | ||
|
|
e320bbfc87 | ||
|
|
2e104046d4 | ||
|
|
9cf38c1784 | ||
|
|
1aca6da1ea | ||
|
|
ce4d263b52 | ||
|
|
53c02eb2b9 | ||
|
|
82a2148300 | ||
|
|
b72a58b917 | ||
|
|
7f7bce50dc | ||
|
|
290e214638 | ||
|
|
ad4d6eaec9 | ||
|
|
cd9bf53e1a | ||
|
|
ac8b312140 | ||
|
|
3e849568ff | ||
|
|
5d6961619d | ||
|
|
94180f6a0a | ||
|
|
7803686aec | ||
|
|
b8e60ca8ec | ||
|
|
b985edbc29 | ||
|
|
ffdb3f86d7 | ||
|
|
c9b2a34561 | ||
|
|
e5dc0309f1 | ||
|
|
57a3ad8ae2 | ||
|
|
ad3722e19f | ||
|
|
a12d127fd6 | ||
|
|
e80ebfae35 | ||
|
|
4669d6b378 | ||
|
|
9f093da61e | ||
|
|
c0d31688c4 | ||
|
|
86698e7822 | ||
|
|
3fc5859f08 | ||
|
|
f9bb663f6b | ||
|
|
5e8121bd0b | ||
|
|
9cd6df2929 | ||
|
|
b16cbeacb6 | ||
|
|
4d46464bc6 | ||
|
|
7746dc55f0 | ||
|
|
5d9e8b56c5 | ||
|
|
d1c06365a8 | ||
|
|
b56ddc1a08 | ||
|
|
ae8c8e781f | ||
|
|
1344b1f2e3 | ||
|
|
36df25dcb4 | ||
|
|
84246d287d | ||
|
|
6d0fbfa21f | ||
|
|
034c6824de | ||
|
|
c8afa4a2a8 | ||
|
|
1a77f7320b | ||
|
|
183ece1360 | ||
|
|
035cc3da99 | ||
|
|
c01c39954a | ||
|
|
c48eb572e3 | ||
|
|
b03ab1212f | ||
|
|
8a9b5e7e01 | ||
|
|
9b90e4f25a | ||
|
|
8a4401bdcb | ||
|
|
d52e90cf23 | ||
|
|
14ef0a7740 | ||
|
|
c1f79b348a | ||
|
|
62166a50d9 | ||
|
|
283b330403 | ||
|
|
2cf6b74295 | ||
|
|
1a9f707d18 | ||
|
|
06a1c88e86 | ||
|
|
f358b566b1 | ||
|
|
15fc27d142 | ||
|
|
ad20597d74 | ||
|
|
6d409a7c97 | ||
|
|
36d5ae5421 | ||
|
|
db9744ea7f | ||
|
|
5e62fe86fc | ||
|
|
6cfa27c33b | ||
|
|
a405969199 | ||
|
|
9a6f3b4c05 | ||
|
|
7be059a79b | ||
|
|
29b55de042 | ||
|
|
235fb19aa8 | ||
|
|
c0f54fa4fc | ||
|
|
cdc216aca0 | ||
|
|
4505053618 | ||
|
|
e0d53126d6 | ||
|
|
12ff1dd14c | ||
|
|
50db3152f6 | ||
|
|
9c4fd0e3d3 | ||
|
|
8d27d22100 | ||
|
|
27f1a7ab27 | ||
|
|
d52e4793fe | ||
|
|
544ac6cb33 | ||
|
|
06fbc1a6cf | ||
|
|
edab10e6a6 | ||
|
|
dd99fc93e4 | ||
|
|
dbe824d4e5 | ||
|
|
6e73d7f5c2 | ||
|
|
960056d300 | ||
|
|
3efac3287e | ||
|
|
c4b4a8c8a5 | ||
|
|
b833f033bf | ||
|
|
ad75e2be89 | ||
|
|
7ee4fbeb0d | ||
|
|
9886f0ca9a | ||
|
|
46cf56d6ff | ||
|
|
e7af295b5e | ||
|
|
9d179d9a1a | ||
|
|
15010f9567 | ||
|
|
a7f4f6c1cf | ||
|
|
3eeb677f14 | ||
|
|
ef69a3de66 | ||
|
|
0aa5e07252 | ||
|
|
07d58337f1 | ||
|
|
b5e3f25418 | ||
|
|
19b02875c7 | ||
|
|
8a74f6ea18 | ||
|
|
a847f3eafa | ||
|
|
a751f074e7 | ||
|
|
75cb5c2166 | ||
|
|
70a86bed7a | ||
|
|
f67f8ed05e | ||
|
|
1ae53220d6 | ||
|
|
c546cb552e | ||
|
|
10f8836d7b | ||
|
|
7ecd4124ce | ||
|
|
54cd77be8e | ||
|
|
43388cb7fc | ||
|
|
4cc5104fde | ||
|
|
d519cabae3 | ||
|
|
9852e29835 | ||
|
|
3a427c3630 | ||
|
|
336656e8f7 | ||
|
|
06ad9666e8 | ||
|
|
728e0c6600 | ||
|
|
657e690bdd | ||
|
|
2d36d0a659 | ||
|
|
c42fd5d2e2 | ||
|
|
10bf97eff6 | ||
|
|
ea9992247d | ||
|
|
b90fecff76 | ||
|
|
a4fc9fea3b | ||
|
|
b7c7eff2a2 | ||
|
|
8c10dc406a | ||
|
|
05c3861426 | ||
|
|
1ef5db8896 | ||
|
|
006a87692a | ||
|
|
e019b70420 | ||
|
|
70ec5b0344 | ||
|
|
5401ace540 | ||
|
|
ab9081c852 | ||
|
|
3a9b35980c | ||
|
|
150439d215 | ||
|
|
b01a534a98 | ||
|
|
758b0b08d0 | ||
|
|
f87ba1d8a4 | ||
|
|
67fa7bdcc9 | ||
|
|
0b03aca104 | ||
|
|
3bf61f1043 | ||
|
|
ac975f4139 | ||
|
|
6d12301f88 | ||
|
|
914950c4a5 | ||
|
|
b417b267dd | ||
|
|
aae497eff1 | ||
|
|
3c9db4c2ac | ||
|
|
8c3793e7ea | ||
|
|
60a7f53491 | ||
|
|
04bfd62f2f | ||
|
|
117b8b3653 | ||
|
|
70cb0d2924 | ||
|
|
c5ea496c45 | ||
|
|
01e955ca37 | ||
|
|
fdb66c769c | ||
|
|
38b3fbbdbe | ||
|
|
3bc5d2d02c | ||
|
|
ac7eda8ca2 | ||
|
|
baf58f53b3 | ||
|
|
8513a83331 | ||
|
|
bf7fecff10 | ||
|
|
6e1f2f636c | ||
|
|
8b3db6f3d6 | ||
|
|
39d0abe982 | ||
|
|
c2026dc254 | ||
|
|
cae6c28754 | ||
|
|
3b99924799 | ||
|
|
b2b548b103 | ||
|
|
c2e3806a77 | ||
|
|
25500a7dda | ||
|
|
8c394a4cb5 | ||
|
|
2e7bd4de4b | ||
|
|
7261322e41 | ||
|
|
cada396733 | ||
|
|
9f9709f058 | ||
|
|
26fc65209d | ||
|
|
f642cead44 | ||
|
|
616d9421d6 | ||
|
|
20e5219cf4 | ||
|
|
9edda21b06 | ||
|
|
6d6822ca15 | ||
|
|
a2269e7ee7 | ||
|
|
e8f443f4a9 | ||
|
|
67f8452178 | ||
|
|
47f6e407d9 | ||
|
|
889e2152a4 | ||
|
|
173aae1f81 | ||
|
|
baed80983b | ||
|
|
b00423dc76 | ||
|
|
376ed15079 | ||
|
|
a6d8d6cd8e | ||
|
|
b3ee8b447e | ||
|
|
5f588561b6 | ||
|
|
83c53e17e0 | ||
|
|
c028488894 | ||
|
|
b066005f7e | ||
|
|
87e33a75c6 | ||
|
|
4961b0943b | ||
|
|
eda1e9c266 | ||
|
|
bc7bf174d8 | ||
|
|
2e06d5790b | ||
|
|
ab8790e8f1 | ||
|
|
7d630e63e7 | ||
|
|
77899191f3 | ||
|
|
6871873e93 | ||
|
|
fc4bd131cd | ||
|
|
d866286f82 | ||
|
|
76dfda164e | ||
|
|
134d47e48c | ||
|
|
a2dbd5e013 | ||
|
|
c27412c83a | ||
|
|
4143aba595 | ||
|
|
39ccac4022 | ||
|
|
7943f4deb6 | ||
|
|
2dda2d7689 | ||
|
|
b8f2a14ea0 | ||
|
|
595809f090 | ||
|
|
477db9cd4a | ||
|
|
67ff292d62 | ||
|
|
ae882b85a9 | ||
|
|
5e92915095 | ||
|
|
95809db03c | ||
|
|
6d5a5121bc | ||
|
|
86992751c7 | ||
|
|
2eba8b6c37 | ||
|
|
e1523e939d | ||
|
|
15a92f443d | ||
|
|
b57c68e392 | ||
|
|
e95fcb6bc0 | ||
|
|
6581fcb6a7 | ||
|
|
6551337bd8 | ||
|
|
137df52fdd | ||
|
|
b79aa7eb6a | ||
|
|
1c8c49dc8e | ||
|
|
f0c808d2fc | ||
|
|
26d9ae88df | ||
|
|
3c7f119bb1 | ||
|
|
fea35923f0 | ||
|
|
9627776318 | ||
|
|
6dcde3af72 | ||
|
|
43717a89b5 | ||
|
|
c7ba359688 | ||
|
|
dc3dbf6d85 | ||
|
|
71664c85ff | ||
|
|
cb41be113a | ||
|
|
4fe7daa85e | ||
|
|
f75afc6ee4 | ||
|
|
67fd71ab92 | ||
|
|
385e70552e | ||
|
|
1e2c67bfca | ||
|
|
56739f87fb | ||
|
|
571eb7614f | ||
|
|
c6c3a8c60d | ||
|
|
ae7cfa56b7 | ||
|
|
b856613194 | ||
|
|
86305879cb | ||
|
|
e5a1a8a706 | ||
|
|
27602a4a97 | ||
|
|
a98e460257 | ||
|
|
0349435650 | ||
|
|
c3fbdca18f | ||
|
|
acb5245a28 | ||
|
|
23561cd76b | ||
|
|
d3ab3d7d78 | ||
|
|
5b870be83f | ||
|
|
6130d5622c | ||
|
|
318c4e3456 | ||
|
|
ab701449e2 | ||
|
|
9a3447d863 | ||
|
|
5280637adf | ||
|
|
3bce55ac44 | ||
|
|
a3f79503ed | ||
|
|
02d462a82c | ||
|
|
3d17328d0d | ||
|
|
0e81abc21b | ||
|
|
803b930405 | ||
|
|
6adf45bcb4 | ||
|
|
01417c88c5 | ||
|
|
60bbbe0d93 | ||
|
|
888dfd3654 | ||
|
|
12de56ff62 | ||
|
|
b7cee41c54 | ||
|
|
10aac03f75 | ||
|
|
0eb0041f90 | ||
|
|
b38018af9c | ||
|
|
4df5ac0385 | ||
|
|
a7476bc303 | ||
|
|
cc4b3cb361 | ||
|
|
31a965b29a | ||
|
|
7a213338a2 | ||
|
|
fb27a25e52 | ||
|
|
534b09ae4a | ||
|
|
341a9f370c | ||
|
|
607d9b2d5c | ||
|
|
507ce40ad2 | ||
|
|
f58c3aed32 | ||
|
|
55e85bd9ca | ||
|
|
4bf614c5cd | ||
|
|
96b06c75d1 | ||
|
|
d261648ab0 | ||
|
|
7b81b9e806 | ||
|
|
a93aea3e4e | ||
|
|
2cfacc2c7d | ||
|
|
6d6b21a0eb | ||
|
|
7a78c37851 | ||
|
|
b8a9f41892 | ||
|
|
227567dfe1 | ||
|
|
d5ff5c672b | ||
|
|
393f6eca3a | ||
|
|
d193ef856e | ||
|
|
44d3691fa1 | ||
|
|
27d0465891 | ||
|
|
52a9fcef3c | ||
|
|
7386816f52 | ||
|
|
9c0075b233 | ||
|
|
e4c5700baf | ||
|
|
e1b7df8b6a | ||
|
|
c999b2d778 | ||
|
|
bfb6c671fb | ||
|
|
7c6ec577dc | ||
|
|
626b40f31b | ||
|
|
5af12440ba | ||
|
|
4614f6febe | ||
|
|
1354ffc32e | ||
|
|
3723939c99 | ||
|
|
e099739e13 | ||
|
|
21a48cc41d | ||
|
|
4740266308 | ||
|
|
78139413d7 | ||
|
|
f0578a75f4 | ||
|
|
7a0e55a02a | ||
|
|
f132bac80d | ||
|
|
109ea4fe5b | ||
|
|
f33feafd0e | ||
|
|
9195356e3a | ||
|
|
eff91108f4 | ||
|
|
e40c0516e6 | ||
|
|
c580a94ab7 | ||
|
|
6a545bb942 | ||
|
|
672bd2d9fe | ||
|
|
3ab4a584ab | ||
|
|
150d02ac0d | ||
|
|
6d26f60904 | ||
|
|
b42276953f | ||
|
|
7c290b9f76 | ||
|
|
51fe1d6629 | ||
|
|
8f558d2b47 | ||
|
|
3f5fadf2e9 | ||
|
|
07c16cee1d | ||
|
|
24130dfcdc | ||
|
|
15a2341a91 | ||
|
|
86f61298e6 | ||
|
|
3cd7ec3878 | ||
|
|
8b13d3e4c7 | ||
|
|
327d451abc | ||
|
|
2dac16ee02 | ||
|
|
d4b92a19d7 | ||
|
|
e0357596f5 | ||
|
|
1ef27d18c1 | ||
|
|
1d2d8ed107 | ||
|
|
1bf01bc214 | ||
|
|
0015deca47 | ||
|
|
ddfdc6e90f | ||
|
|
5db2ad54f2 | ||
|
|
b8a5750529 | ||
|
|
98d5b8c7cc | ||
|
|
3f34154a1e | ||
|
|
3119f831b3 | ||
|
|
8a07b762a2 | ||
|
|
551ab2fc59 | ||
|
|
99957e57b9 | ||
|
|
be2c59bc6e | ||
|
|
57f9a49b66 | ||
|
|
54bd0eb99d | ||
|
|
485faac294 | ||
|
|
d531d6f3ef | ||
|
|
3a9fdb82f5 | ||
|
|
5024ae1156 | ||
|
|
0135dd9ed3 | ||
|
|
a6e9b86bbe | ||
|
|
237c4444b5 | ||
|
|
a68467292e | ||
|
|
978c69d0c3 | ||
|
|
3eabc59921 | ||
|
|
2b3d99fac2 | ||
|
|
2bdefe0e9e | ||
|
|
de81fc2aca | ||
|
|
b514e0a6e7 | ||
|
|
715dfa4541 | ||
|
|
ac57a37224 | ||
|
|
afd620b092 | ||
|
|
552bceb07c | ||
|
|
fe58ed1283 | ||
|
|
361e2d463c | ||
|
|
259c8d2c98 | ||
|
|
e12c1dc9aa | ||
|
|
7fb49e383d | ||
|
|
1df3e28253 | ||
|
|
bacec2689d | ||
|
|
bf397591f9 | ||
|
|
bd1a936c7a | ||
|
|
0871d6e321 | ||
|
|
265d296db6 | ||
|
|
5ec136b57c | ||
|
|
f1e8f9c9d0 | ||
|
|
f03841c4e4 | ||
|
|
0ae58ff0ea | ||
|
|
390c1899ca | ||
|
|
89aa6d1e4e | ||
|
|
24638b02a9 | ||
|
|
4b0ab6ab37 | ||
|
|
1f02d9f141 | ||
|
|
0103c38c13 | ||
|
|
3cc76f91b4 | ||
|
|
10dc248f07 | ||
|
|
d1f89c6217 | ||
|
|
76ba4fc32d | ||
|
|
37f90fff44 | ||
|
|
9fa6da3bc7 | ||
|
|
aa8cf237ab | ||
|
|
c131728aa4 | ||
|
|
2abd137494 | ||
|
|
90815ace59 | ||
|
|
53d916d7f1 | ||
|
|
2b4035979b | ||
|
|
38cdc93010 | ||
|
|
346dad3898 | ||
|
|
42256bc262 | ||
|
|
70babb4067 | ||
|
|
3603e6373d | ||
|
|
bd1760682f | ||
|
|
52d0490f95 | ||
|
|
b3b0aa75ae | ||
|
|
e42d177920 | ||
|
|
c2156af7b0 | ||
|
|
86394eb56c | ||
|
|
2d7790f5e4 | ||
|
|
3df43529a6 | ||
|
|
a1c9b27057 | ||
|
|
089dd233a5 | ||
|
|
86a7a0bd6c | ||
|
|
7ebca36a9c | ||
|
|
d5aed5a88a | ||
|
|
dac1f270ce | ||
|
|
839be24053 | ||
|
|
91c4179f05 | ||
|
|
a4b427bfac | ||
|
|
250f5bec18 | ||
|
|
336e2a10e0 | ||
|
|
b5e9b7635e | ||
|
|
0df7fa1596 | ||
|
|
c10487d635 | ||
|
|
bc9b3bef74 | ||
|
|
e57462e7ca | ||
|
|
78bf27709f | ||
|
|
fc84cd9204 | ||
|
|
e361f7b246 | ||
|
|
b0497b7505 | ||
|
|
a894e31fa5 | ||
|
|
dd062adec2 | ||
|
|
d187575a2c | ||
|
|
85096c4ba2 | ||
|
|
e13fd693c3 | ||
|
|
cc35512472 | ||
|
|
4e548291ce | ||
|
|
3ba86f329f | ||
|
|
5b34af0f12 | ||
|
|
0a9eb1ff83 | ||
|
|
942dd0e5f7 | ||
|
|
400102f3d3 | ||
|
|
9ccdeb3d36 | ||
|
|
7e0f0dd2d2 | ||
|
|
d920cbb7f6 | ||
|
|
417677e6f4 | ||
|
|
33f3038316 | ||
|
|
429dbe3e0c | ||
|
|
471fc44751 | ||
|
|
1b8e346307 | ||
|
|
a456583a08 | ||
|
|
a63c17baab | ||
|
|
9c4faddc0f | ||
|
|
6828c4c1e9 | ||
|
|
80131e7ec0 | ||
|
|
ac381a6f58 | ||
|
|
521b516bf9 | ||
|
|
6a825f8e60 | ||
|
|
f0a243ca10 | ||
|
|
e5457a3390 | ||
|
|
47e21f8bef | ||
|
|
331b854e4e | ||
|
|
274bc9cbba | ||
|
|
827f8d95b4 | ||
|
|
2946dd35d5 | ||
|
|
74d884787d | ||
|
|
5516e16fb8 | ||
|
|
cadf4eb322 | ||
|
|
269249e86e | ||
|
|
30f87d2308 | ||
|
|
8b7e72b95e | ||
|
|
1e64048956 | ||
|
|
8512e696f5 | ||
|
|
6a03a9ec5f | ||
|
|
5e04c99d57 | ||
|
|
82f15491c0 | ||
|
|
101843fbb7 | ||
|
|
9e534f3804 | ||
|
|
ca3cfc0184 | ||
|
|
d62fb901e2 | ||
|
|
73a2b59c2c | ||
|
|
2c7a56625c | ||
|
|
e2572b214f | ||
|
|
d22cd3a74f | ||
|
|
0c8fcedfdf | ||
|
|
99009c37ce | ||
|
|
d9f5771778 | ||
|
|
d35b5070fb | ||
|
|
02f41f9afc | ||
|
|
c797aa1d5e | ||
|
|
b2f7f67756 | ||
|
|
09014ab6d5 | ||
|
|
8f8747d65e | ||
|
|
be19e137e2 | ||
|
|
3155291064 | ||
|
|
a5b22e6a36 | ||
|
|
9c251e8b6a | ||
|
|
6056568182 | ||
|
|
7b7c1da18d | ||
|
|
fb5b4b3547 | ||
|
|
19918d485e | ||
|
|
45c6c6ba10 | ||
|
|
0e436bc686 | ||
|
|
b0dfea0a09 | ||
|
|
2c4e6c4188 | ||
|
|
9f3254dbd1 | ||
|
|
88cdad4189 | ||
|
|
4ba50a4379 | ||
|
|
2b6c104011 | ||
|
|
4b446d100e | ||
|
|
f9ca2114a9 | ||
|
|
afc9c6ef85 | ||
|
|
ac200f6173 | ||
|
|
73a78eadb1 | ||
|
|
c5139fb6c2 | ||
|
|
9faf9aa1b9 | ||
|
|
3c2e9be248 | ||
|
|
5b59f6612f | ||
|
|
3d69363f35 | ||
|
|
b580b4fd33 | ||
|
|
74f86d70f8 | ||
|
|
3959104f9b | ||
|
|
1ff037a257 | ||
|
|
32700df117 | ||
|
|
b4edec215e | ||
|
|
dffa1e45f4 | ||
|
|
df3b6dde34 | ||
|
|
834bbf467e | ||
|
|
4d4f94208e | ||
|
|
416713de0c | ||
|
|
c523ca8efe | ||
|
|
9acea56108 | ||
|
|
44a7422375 | ||
|
|
0d0e7eb179 | ||
|
|
ea6c840343 | ||
|
|
d2db0913ac | ||
|
|
3721dae74d | ||
|
|
9050a2447b | ||
|
|
df4c363e9c | ||
|
|
a909a3e692 | ||
|
|
dd26253905 | ||
|
|
69b7ba2d22 | ||
|
|
d2f306e488 | ||
|
|
4a6fefa434 | ||
|
|
16e0ea611e | ||
|
|
05a2e77be2 | ||
|
|
dd2fa36261 | ||
|
|
5fa1dec6d8 | ||
|
|
f86d96794d |
101
.editorconfig
101
.editorconfig
@@ -6,13 +6,110 @@ charset=utf-8
|
||||
[*]
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
; 4-column tab indentation
|
||||
; 4-column tab indentation and .NET coding conventions
|
||||
[*.cs]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
csharp_prefer_braces = when_multiline:suggestion
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
dotnet_naming_symbols.const_private_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.const_private_field.required_modifiers = const
|
||||
dotnet_naming_symbols.const_private_field.applicable_accessibilities = private
|
||||
|
||||
dotnet_naming_symbols.internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.internal_field.applicable_accessibilities = internal
|
||||
|
||||
dotnet_naming_symbols.static_private_or_internal_field.required_modifiers = static
|
||||
dotnet_naming_symbols.static_private_or_internal_field.applicable_accessibilities = internal, private
|
||||
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
dotnet_naming_symbols.parameters.applicable_kinds = parameter
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.severity = none
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.symbols = static_private_or_internal_field
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.symbols = const_private_field
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.symbols = internal_field
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.severity = warning
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case
|
||||
|
||||
dotnet_naming_rule.parameters.severity = warning
|
||||
dotnet_naming_rule.parameters.symbols = parameters
|
||||
dotnet_naming_rule.parameters.style = camel_case
|
||||
|
||||
# Naming rules
|
||||
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
#require a space after a keyword in a control flow statement such as a for loop
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
|
||||
#Formatting - wrapping options
|
||||
|
||||
#leave code block on single line
|
||||
csharp_preserve_single_line_blocks = true
|
||||
#leave statements and member declarations on the same line
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
|
||||
#don't prefer braces (for one liners)
|
||||
dotnet_diagnostic.IDE0011.severity = none
|
||||
|
||||
; 4-column tab indentation
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
indent_size = 4
|
||||
|
||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
patreon: orahosting
|
||||
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,11 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Frequently Asked Questions
|
||||
url: https://github.com/OpenRA/OpenRA/wiki/FAQ#frequently-asked-questions
|
||||
about: Explanations for common problems and questions.
|
||||
- name: OpenRA Forum
|
||||
url: https://forum.openra.net/
|
||||
about: "Please ask questions about modding here."
|
||||
about: Please ask questions about modding here.
|
||||
- name: OpenRA Discord server
|
||||
url: https://discord.openra.net/
|
||||
about: "Join the community Discord server for community discussion and support."
|
||||
about: Join the community Discord server for community discussion and support.
|
||||
- name: OpenRA IRC Channel
|
||||
url: https://webchat.freenode.net/#openra
|
||||
about: "Join our development IRC channel on freenode for discussion of development topics."
|
||||
about: Join our development IRC channel on freenode for discussion of development topics.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/crash-report.md
vendored
2
.github/ISSUE_TEMPLATE/crash-report.md
vendored
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Crash report
|
||||
about: Report a game crash.
|
||||
about: Report a game crash. Check the FAQ first https://github.com/OpenRA/OpenRA/wiki/FAQ#common-issues
|
||||
title: My game crashed
|
||||
labels: Crash
|
||||
assignees: ''
|
||||
|
||||
77
.github/workflows/ci.yaml
vendored
Normal file
77
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed ]
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (.NET 5.0)
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.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 5.0)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.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
|
||||
107
.github/workflows/documentation.yml
vendored
Normal file
107
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
jobs:
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: wiki
|
||||
|
||||
- name: Update Wiki (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
|
||||
|
||||
- name: Update Wiki (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
|
||||
|
||||
- name: Push Wiki
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd wiki
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
docs:
|
||||
name: Update docs.openra.net
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone docs.openra.net
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
|
||||
- name: Update docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/playtest/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/playtest/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/playtest/lua.md"
|
||||
|
||||
- name: Update docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/release/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/release/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/release/lua.md"
|
||||
|
||||
- name: Push docs.openra.net
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd docs
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
86
.github/workflows/itch.yml
vendored
Normal file
86
.github/workflows/itch.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
name: Deploy itch.io Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
jobs:
|
||||
itch:
|
||||
name: Deploy to itch.io
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'openra/openra'
|
||||
steps:
|
||||
- name: Download Packages
|
||||
run: |
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
||||
|
||||
- name: Publish Windows Installer
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: win
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe
|
||||
|
||||
- name: Publish Windows Itch Bundle
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: itch
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
||||
|
||||
- name: Publish macOS Package
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: macos
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg
|
||||
|
||||
- name: Publish RA AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-ra
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
||||
|
||||
- name: Publish TD AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-cnc
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
||||
|
||||
- name: Publish D2k AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-d2k
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
||||
129
.github/workflows/packaging.yml
vendored
Normal file
129
.github/workflows/packaging.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: Release Packaging
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'release-*'
|
||||
- 'playtest-*'
|
||||
- 'devtest-*'
|
||||
|
||||
jobs:
|
||||
source:
|
||||
name: Source Tarball
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Source
|
||||
run: |
|
||||
mkdir -p build/source
|
||||
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/source/*
|
||||
|
||||
linux:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package AppImages
|
||||
run: |
|
||||
mkdir -p build/linux
|
||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/linux/*
|
||||
|
||||
macos:
|
||||
name: macOS Disk Images
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Disk Images
|
||||
env:
|
||||
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
MACOS_DEVELOPER_USERNAME: ${{ secrets.MACOS_DEVELOPER_USERNAME }}
|
||||
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/macos/*
|
||||
|
||||
windows:
|
||||
name: Windows Installers
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
sudo apt-get update
|
||||
sudo apt-get install nsis wine64
|
||||
|
||||
- name: Package Installers
|
||||
run: |
|
||||
mkdir -p build/windows
|
||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/windows/*
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -26,7 +26,7 @@ mods/*/*.pdb
|
||||
/*.exe
|
||||
/*.exe.config
|
||||
thirdparty/download/*
|
||||
*.mmdb.gz
|
||||
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
|
||||
86
.travis.yml
86
.travis.yml
@@ -1,86 +0,0 @@
|
||||
# Travis-CI Build for OpenRA
|
||||
# see travis-ci.org for details
|
||||
|
||||
language: csharp
|
||||
mono: 6.4.0
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: linux
|
||||
dist: xenial
|
||||
- os: osx
|
||||
if: tag IS present
|
||||
osx_image: xcode10
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- thirdparty/download
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- lua5.1
|
||||
- dpkg
|
||||
- zsync
|
||||
- markdown
|
||||
|
||||
# Environment variables
|
||||
env:
|
||||
secure: "C0+Hlfa0YGErxUuWV00Tj6p45otC/D3YwYFuLpi2mj1rDFn/4dgh5WRngjvdDBVbXJ3duaZ78jPHWm1jr7vn2jqj9yETsCIK9psWd38ep/FEBM0SDr6MUD89OuXk/YyvxJAE+UXF6bXg7giey09g/CwBigjMW7ynET3wNAWPHPs="
|
||||
|
||||
# Fetch dependencies
|
||||
# Run the build script
|
||||
# Check source code with StyleCop
|
||||
# call OpenRA to check for YAML errors
|
||||
# Run the NUnit tests
|
||||
script:
|
||||
- travis_retry make all-dependencies
|
||||
- make all
|
||||
- test "$TRAVIS_OS_NAME" == "linux" && make check || echo "Skipping check"
|
||||
- test "$TRAVIS_OS_NAME" == "linux" && make check-scripts || echo "Skipping scripts check"
|
||||
- test "$TRAVIS_OS_NAME" == "linux" && make test || echo "Skipping tests"
|
||||
- test "$TRAVIS_OS_NAME" == "linux" && make nunit || echo "Skipping nunit tests"
|
||||
|
||||
# Only watch the development branch and tagged release.
|
||||
branches:
|
||||
only:
|
||||
- /^release-.*$/
|
||||
- /^playtest-.*$/
|
||||
- /^pkgtest-.*$/
|
||||
- /^prep-.*$/
|
||||
- bleed
|
||||
|
||||
# Notify developers when build passed/failed.
|
||||
notifications:
|
||||
irc:
|
||||
template:
|
||||
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
|
||||
channels:
|
||||
- "irc.freenode.net#openra"
|
||||
use_notice: true
|
||||
skip_join: true
|
||||
|
||||
before_deploy:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis-common_3.04-1_all.deb;
|
||||
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis_3.04-1_amd64.deb;
|
||||
sudo dpkg -i nsis-common_3.04-1_all.deb;
|
||||
sudo dpkg -i nsis_3.04-1_amd64.deb;
|
||||
cd packaging && ./update-wiki.sh ${TRAVIS_TAG} && cd ..;
|
||||
fi;
|
||||
- export PATH=$PATH:$HOME/usr/bin
|
||||
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
|
||||
- cd packaging
|
||||
- mkdir build
|
||||
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "g/LU11f+mjqv+lj0sR1UliHwogXL4ofJUwoG5Dbqlvdf5UTLWytw/OWSCv8RGyuh10miyWeaoqHh1cn2C1IFhUEqN1sSeKKKOWOTvJ2FR5mzi9uH3d/MOBzG5icQ7Qh0fZ1YPz5RaJJhYu6bmfvA/1gD49GoaX2kxQL4J5cEBgg="
|
||||
file_glob: true
|
||||
file: build/*
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
tags: true
|
||||
repo: OpenRA/OpenRA
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csharp",
|
||||
"EditorConfig.EditorConfig",
|
||||
"ms-vscode.mono-debug"
|
||||
]
|
||||
}
|
||||
49
.vscode/launch.json
vendored
Normal file
49
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch (TD)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (RA)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (D2k)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (TS)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"omnisharp.enableRoslynAnalyzers": true
|
||||
}
|
||||
13
.vscode/tasks.json
vendored
Normal file
13
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "make",
|
||||
"args": ["all", "CONFIGURATION=Debug"],
|
||||
"windows": {
|
||||
"command": "make.cmd"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
37
AUTHORS
37
AUTHORS
@@ -4,6 +4,7 @@ hard work of many contributors.
|
||||
The OpenRA developers are:
|
||||
* Chris Forbes (chrisf)
|
||||
* Lukas Franke (abcdefg30)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Paul Chote (pchote)
|
||||
* Reaperrr
|
||||
|
||||
@@ -13,7 +14,6 @@ Previous developers included:
|
||||
* Curtis Shmyr (hamb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Igor Popov (ihptru)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Megan Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Oliver Brakmann (obrakmann)
|
||||
@@ -40,6 +40,7 @@ Also thanks to:
|
||||
* Biofreak
|
||||
* Braxton Williams (Buddytex)
|
||||
* Brendan Gluth (Mechanical_Man)
|
||||
* Brent Gardner (bggardner)
|
||||
* Bryan Wilbur
|
||||
* Bugra Cuhadaroglu (BugraC)
|
||||
* Chris Cameron (Vesuvian)
|
||||
@@ -137,16 +138,19 @@ Also thanks to:
|
||||
* Sebastien Kerguen (xanax)
|
||||
* Shawn Collins (UberWaffe)
|
||||
* Simon Verbeke (Saticmotion)
|
||||
* Stuart McHattie (SDJMcHattie)
|
||||
* Taryn Hill (Phrohdoh)
|
||||
* Teemu Nieminen (Temeez)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tirili
|
||||
* Tomas Einarsson (Mesacer)
|
||||
* Tom van Leth (tovl)
|
||||
* Trevor Nichols (ocdi)
|
||||
* Tristan Keating (Kilkakon)
|
||||
* Tristan Mühlbacher (MicroBit)
|
||||
* UnknownProgrammer
|
||||
* Vladimir Komarov (VrKomarov)
|
||||
* Wojciech Walaszek (Voidwalker)
|
||||
* Wuschel
|
||||
|
||||
Using GNU FreeFont distributed under the GNU GPL
|
||||
@@ -160,9 +164,6 @@ FreeType License.
|
||||
|
||||
Using OpenAL Soft distributed under the GNU LGPL.
|
||||
|
||||
Using MaxMind GeoIP2 .NET API distributed under
|
||||
the Apache 2.0 license.
|
||||
|
||||
Using SDL2-CS and OpenAL-CS created by Ethan
|
||||
Lee and released under the zlib license.
|
||||
|
||||
@@ -172,9 +173,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.
|
||||
@@ -182,6 +191,20 @@ Krueger and distributed under the GNU GPL terms.
|
||||
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
||||
distributed under MIT License.
|
||||
|
||||
Using DiscordRichPresence developed by Lachee
|
||||
distributed under MIT License.
|
||||
|
||||
Using Json.NET developed by James Newton-King
|
||||
distributed under MIT License.
|
||||
|
||||
Using ANGLE distributed under the BS3 3-Clause license.
|
||||
|
||||
Using Pfim developed by Nick Babcock
|
||||
distributed under the MIT license.
|
||||
|
||||
This site or product includes IP2Location LITE data
|
||||
available from http://www.ip2location.com.
|
||||
|
||||
Finally, special thanks goes to the original teams
|
||||
at Westwood Studios and EA for creating the classic
|
||||
games which OpenRA aims to reimagine.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,40 +1,54 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<OutputPath>..</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<LangVersion>5</LangVersion>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
|
||||
<OutputPath>$(EngineRootPath)/bin</OutputPath>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ExternalConsole>false</ExternalConsole>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
|
||||
<Configurations>Debug;Release;Release-x86</Configurations>
|
||||
<CodeAnalysisRuleSet>$(EngineRootPath)/OpenRA.ruleset</CodeAnalysisRuleSet>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework Condition="'$(Mono)' == ''">net5.0</TargetFramework>
|
||||
<TargetFramework Condition="'$(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>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release-x86'">
|
||||
<Prefer32bit>true</Prefer32bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<AdditionalFiles Include="../stylecop.json" />
|
||||
</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.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(EngineRootPath)/stylecop.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
101
INSTALL.md
101
INSTALL.md
@@ -8,105 +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.6.1 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net461) (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 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) (or via Visual Studio)
|
||||
|
||||
Type `make dependencies` in a command terminal to download pre-compiled native libraries for:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
|
||||
* [zlib](http://gnuwin32.sourceforge.net/packages/zlib.htm)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
|
||||
|
||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||
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.4 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
|
||||
.NET 5 or Mono (version 6.4 or later) is required to compile OpenRA. We recommend using .NET 5 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.
|
||||
|
||||
Use `make dependencies` to map the native libraries to your system and fetch the remaining CLI dependencies to place them at the appropriate places.
|
||||
The [.NET 5 download page](https://dotnet.microsoft.com/download/dotnet/5.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 all` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||
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.
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
Arch Linux
|
||||
----------
|
||||
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.
|
||||
|
||||
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:
|
||||
These can be installed using your package manager on various distros:
|
||||
|
||||
<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)
|
||||
----------------------------------------------------
|
||||
|
||||
The EPEL repository is required in order for the following command to run properly.
|
||||
</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>
|
||||
|
||||
OSX
|
||||
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.4](https://www.mono-project.com/download/stable/#download-mac)
|
||||
[.NET 5](https://dotnet.microsoft.com/download/dotnet/5.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 5 unless you are running a very old version of macOS (10.9 through 10.12).
|
||||
|
||||
Use `make dependencies` to download pre-compiled native libraries for:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
|
||||
|
||||
To compile OpenRA, run `make` from the command line.
|
||||
|
||||
Run with `./launch-game.sh`.
|
||||
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.
|
||||
|
||||
426
Makefile
426
Makefile
@@ -1,57 +1,40 @@
|
||||
############################# INSTRUCTIONS #############################
|
||||
#
|
||||
# to compile, run:
|
||||
# make [DEBUG=true]
|
||||
# make
|
||||
#
|
||||
# to compile using Mono (version 6.4 or greater) instead of .NET 5, run:
|
||||
# make RUNTIME=mono
|
||||
#
|
||||
# to compile using system libraries for native dependencies, run:
|
||||
# make [RUNTIME=net5] TARGETPLATFORM=unix-generic
|
||||
#
|
||||
# to check unit tests (requires NUnit version >= 2.6), run:
|
||||
# make nunit [NUNIT_CONSOLE=<path-to/nunit[2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]
|
||||
# Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths
|
||||
# Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /
|
||||
# Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)
|
||||
# to check the official mods for erroneous yaml files, run:
|
||||
# make test
|
||||
# make [RUNTIME=net5] test
|
||||
#
|
||||
# to check the official mod dlls for StyleCop violations, run:
|
||||
# make check
|
||||
# to check the engine and official mod dlls for code style violations, run:
|
||||
# make [RUNTIME=net5] check
|
||||
#
|
||||
# to install, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
|
||||
# make [RUNTIME=net5] [prefix=/foo] [bindir=/bar/bin] install
|
||||
#
|
||||
# to install Linux startup scripts, desktop files and icons:
|
||||
# make install-linux-shortcuts [DEBUG=false]
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
|
||||
# using system libraries for native dependencies, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
|
||||
#
|
||||
# to install the engine and common mod files (omitting the default mods):
|
||||
# make install-engine
|
||||
# make install-common-mod-files
|
||||
# to install Linux startup scripts, desktop files, icons, and MIME metadata
|
||||
# make install-linux-shortcuts
|
||||
#
|
||||
# to uninstall, run:
|
||||
# make uninstall
|
||||
# to install Linux AppStream metadata
|
||||
# make install-linux-appdata
|
||||
#
|
||||
# for help, run:
|
||||
# make help
|
||||
#
|
||||
# to start the game, run:
|
||||
# openra
|
||||
|
||||
############################## TOOLCHAIN ###############################
|
||||
#
|
||||
# List of .NET assemblies that we can guarantee exist
|
||||
# OpenRA.Game.dll is a harmless false positive that we can ignore
|
||||
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
|
||||
|
||||
# These are explicitly shipped alongside our core files by the packaging script
|
||||
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll MaxMind.Db.dll Eluant.dll rix0rrr.BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.dll
|
||||
|
||||
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
|
||||
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
|
||||
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll
|
||||
|
||||
NUNIT_LIBS_PATH :=
|
||||
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
|
||||
|
||||
######################### UTILITIES/SETTINGS ###########################
|
||||
#
|
||||
# install locations
|
||||
# Install locations for local installs and downstream packaging
|
||||
prefix ?= /usr/local
|
||||
datarootdir ?= $(prefix)/share
|
||||
datadir ?= $(datarootdir)
|
||||
@@ -60,321 +43,144 @@ bindir ?= $(prefix)/bin
|
||||
libdir ?= $(prefix)/lib
|
||||
gameinstalldir ?= $(libdir)/openra
|
||||
|
||||
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
|
||||
DATA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
|
||||
|
||||
# install tools
|
||||
# Toolchain
|
||||
CWD = $(shell pwd)
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
DOTNET = dotnet
|
||||
MONO = mono
|
||||
RM = rm
|
||||
RM_R = $(RM) -r
|
||||
RM_F = $(RM) -f
|
||||
RM_RF = $(RM) -rf
|
||||
CP = cp
|
||||
CP_R = $(CP) -r
|
||||
INSTALL = install
|
||||
INSTALL_DIR = $(INSTALL) -d
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
|
||||
# Toolchain
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
RUNTIME ?= net5
|
||||
CONFIGURATION ?= Release
|
||||
|
||||
# Enable 32 bit builds while generating the windows installer
|
||||
WIN32 = false
|
||||
# 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))
|
||||
|
||||
# program targets
|
||||
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
|
||||
|
||||
# dependencies
|
||||
# Detect target platform for dependencies if not given by the user
|
||||
ifndef TARGETPLATFORM
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
os-dependencies = osx-dependencies
|
||||
TARGETPLATFORM = osx-x64
|
||||
else
|
||||
os-dependencies = linux-dependencies
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
TARGETPLATFORM = linux-x64
|
||||
else
|
||||
TARGETPLATFORM = unix-generic
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
##################### DEVELOPMENT BUILDS AND TESTS #####################
|
||||
#
|
||||
all:
|
||||
@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) -p:Mono=true
|
||||
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 ./*/obj
|
||||
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
check:
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
ifeq ($(RUNTIME), mono)
|
||||
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true
|
||||
else
|
||||
@$(DOTNET) build -c Debug -nologo -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
endif
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@./utility.sh all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect 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')
|
||||
|
||||
check: dependencies
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
@$(MSBUILD) -t:build -p:Configuration=Debug
|
||||
@echo
|
||||
@echo "Checking runtime assemblies..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect conditional trait interface overrides..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-conditional-trait-interface-overrides
|
||||
|
||||
|
||||
NUNIT_CONSOLE := $(shell test -f thirdparty/download/nunit3-console.exe && echo mono thirdparty/download/nunit3-console.exe || \
|
||||
which nunit3-console 2>/dev/null || which nunit2-console 2>/dev/null || which nunit-console 2>/dev/null)
|
||||
nunit: core
|
||||
@echo
|
||||
@echo "Checking unit tests..."
|
||||
@if [ "$(NUNIT_CONSOLE)" = "" ] ; then \
|
||||
echo 'nunit[3|2]-console not found!'; \
|
||||
echo 'Was "make dependencies" called or is NUnit installed?'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if $(NUNIT_CONSOLE) --help | head -n 1 | grep -E "NUnit version (1|2\.[0-5])";then \
|
||||
echo 'NUnit version >= 2.6 required'>&2; \
|
||||
echo 'Try "make dependencies" first to use NUnit from NuGet.'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(NUNIT_CONSOLE) --noresult OpenRA.Test.nunit
|
||||
|
||||
test: core
|
||||
test: all
|
||||
@echo
|
||||
@echo "Testing Tiberian Sun mod MiniYAML..."
|
||||
@mono --debug OpenRA.Utility.exe ts --check-yaml
|
||||
@./utility.sh ts --check-yaml
|
||||
@echo
|
||||
@echo "Testing Dune 2000 mod MiniYAML..."
|
||||
@mono --debug OpenRA.Utility.exe d2k --check-yaml
|
||||
@./utility.sh d2k --check-yaml
|
||||
@echo
|
||||
@echo "Testing Tiberian Dawn mod MiniYAML..."
|
||||
@mono --debug OpenRA.Utility.exe cnc --check-yaml
|
||||
@./utility.sh cnc --check-yaml
|
||||
@echo
|
||||
@echo "Testing Red Alert mod MiniYAML..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-yaml
|
||||
@./utility.sh ra --check-yaml
|
||||
|
||||
########################## MAKE/INSTALL RULES ##########################
|
||||
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
||||
#
|
||||
all: dependencies core
|
||||
|
||||
core:
|
||||
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.4."; exit 1)
|
||||
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
|
||||
@$(MSBUILD) -t:build -p:Configuration="Release-x86"
|
||||
else
|
||||
@$(MSBUILD) -t:build -p:Configuration=Release
|
||||
endif
|
||||
|
||||
clean:
|
||||
@ $(MSBUILD) -t:clean
|
||||
@-$(RM_F) *.config
|
||||
@-$(RM_F) *.exe *.dll *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
|
||||
@-$(RM_RF) ./*/bin ./*/obj
|
||||
@-$(RM_RF) ./thirdparty/download
|
||||
|
||||
distclean: clean
|
||||
|
||||
cli-dependencies:
|
||||
@./thirdparty/fetch-thirdparty-deps.sh
|
||||
@ $(CP_R) thirdparty/download/*.dll .
|
||||
@ $(CP_R) thirdparty/download/*.dll.config .
|
||||
@ test -f OpenRA.Game/obj/project.assets.json || $(MSBUILD) -t:restore
|
||||
|
||||
linux-dependencies: cli-dependencies linux-native-dependencies
|
||||
|
||||
linux-native-dependencies:
|
||||
@./thirdparty/configure-native-deps.sh
|
||||
|
||||
windows-dependencies: cli-dependencies
|
||||
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh x86
|
||||
else
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh x64
|
||||
endif
|
||||
|
||||
osx-dependencies: cli-dependencies
|
||||
@./thirdparty/fetch-thirdparty-deps-osx.sh
|
||||
@ $(CP_R) thirdparty/download/osx/*.dylib .
|
||||
@ $(CP_R) thirdparty/download/osx/*.dll.config .
|
||||
|
||||
dependencies: $(os-dependencies)
|
||||
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
|
||||
|
||||
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
|
||||
@echo "$(VERSION)" > VERSION
|
||||
@for i in $? ; do \
|
||||
awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \
|
||||
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp > $${i} && \
|
||||
rm $${i}.tmp; \
|
||||
done
|
||||
ifeq ($(VERSION),)
|
||||
$(error Unable to determine new version (requires git or override of variable VERSION))
|
||||
endif
|
||||
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
|
||||
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
|
||||
|
||||
install: dependencies core install-core
|
||||
install:
|
||||
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
|
||||
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
|
||||
|
||||
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
|
||||
|
||||
install-engine:
|
||||
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Game.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Server.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Utility.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Platforms.Default.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
@$(INSTALL_DATA) OpenRA.Platforms.Default.dll.config "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
|
||||
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
|
||||
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
|
||||
|
||||
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
|
||||
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) SDL2-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) OpenAL-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) Eluant* "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-common-mod-files:
|
||||
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Common.dll "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Cnc.dll "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
|
||||
|
||||
install-default-mods:
|
||||
@-echo "Installing OpenRA default mods to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/cnc "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(CP_R) mods/ra "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(CP_R) mods/d2k "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) mods/d2k/OpenRA.Mods.D2k.dll "$(DATA_INSTALL_DIR)/mods/d2k"
|
||||
@$(CP_R) mods/modcontent "$(DATA_INSTALL_DIR)/mods/"
|
||||
|
||||
install-core: install-engine install-common-mod-files install-default-mods
|
||||
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-linux-icons:
|
||||
for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/ra_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/cnc_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/d2k_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
|
||||
done
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps"
|
||||
$(INSTALL_DATA) packaging/linux/icons/ra_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
|
||||
$(INSTALL_DATA) packaging/linux/icons/cnc_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
|
||||
|
||||
install-linux-desktop:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Red Alert/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-ra.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-ra.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Tiberian Dawn/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-cnc.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-cnc.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Dune 2000/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-d2k.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-d2k.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@-$(RM) packaging/linux/openra-ra.desktop packaging/linux/openra-cnc.desktop packaging/linux/openra-d2k.desktop
|
||||
|
||||
install-linux-mime:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/mime/packages/"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
|
||||
install-linux-shortcuts:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra'
|
||||
|
||||
install-linux-appdata:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Red Alert/g' | sed 's/{SCREENSHOT_RA}/ type="default"/g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-ra.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-ra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Tiberian Dawn/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}/ type="default"/g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-cnc.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-cnc.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Dune 2000/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}/ type="default"/g'> packaging/linux/openra-d2k.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-d2k.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@-$(RM) packaging/linux/openra-ra.appdata.xml packaging/linux/openra-cnc.appdata.xml packaging/linux/openra-d2k.appdata.xml
|
||||
|
||||
install-man-page:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
|
||||
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
|
||||
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
|
||||
@-$(RM) openra.6
|
||||
|
||||
install-linux-scripts:
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@sed 's/{DEBUG}//' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
|
||||
@sed 's/{DEBUG}//' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
|
||||
else
|
||||
@sed 's/{DEBUG}/--debug/' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
|
||||
@sed 's/{DEBUG}/--debug/' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
|
||||
endif
|
||||
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) packaging/linux/openra-ra packaging/linux/openra-cnc packaging/linux/openra-d2k packaging/linux/openra.debug.in
|
||||
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra-server
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc-server
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k-server
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra-server "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc-server "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k-server "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) packaging/linux/openra-ra-server packaging/linux/openra-cnc-server packaging/linux/openra-d2k-server packaging/linux/openra-server.debug.in
|
||||
|
||||
uninstall:
|
||||
@-$(RM_R) "$(DATA_INSTALL_DIR)"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra-server"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc-server"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k-server"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-ra.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-cnc.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-d2k.desktop"
|
||||
@-for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
|
||||
done
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-ra.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-cnc.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-d2k.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
|
||||
|
||||
help:
|
||||
@echo 'to compile, run:'
|
||||
@echo ' make [DEBUG=false]'
|
||||
@echo ' make'
|
||||
@echo
|
||||
@echo 'to check unit tests (requires NUnit version >= 2.6), run:'
|
||||
@echo ' make nunit [NUNIT_CONSOLE=<path-to/nunit[3|2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]'
|
||||
@echo ' Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths'
|
||||
@echo ' Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /'
|
||||
@echo ' Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)'
|
||||
@echo 'to compile using Mono (version 6.4 or greater) instead of .NET 5, run:'
|
||||
@echo ' make RUNTIME=mono'
|
||||
@echo
|
||||
@echo 'to compile using system libraries for native dependencies, run:'
|
||||
@echo ' make [RUNTIME=net5] TARGETPLATFORM=unix-generic'
|
||||
@echo
|
||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||
@echo ' make test'
|
||||
@echo ' make [RUNTIME=net5] test'
|
||||
@echo
|
||||
@echo 'to install, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
|
||||
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
||||
@echo ' make [RUNTIME=net5] check'
|
||||
@echo
|
||||
@echo 'to install Linux startup scripts, desktop files and icons'
|
||||
@echo ' make install-linux-shortcuts [DEBUG=false]'
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
|
||||
@echo ' make [RUNTIME=net5] [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
|
||||
@echo
|
||||
@echo 'to uninstall, run:'
|
||||
@echo ' make uninstall'
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
|
||||
@echo 'using system libraries for native dependencies, run:'
|
||||
@echo ' make [RUNTIME=net5] [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
|
||||
@echo
|
||||
@echo 'to start the game, run:'
|
||||
@echo ' openra'
|
||||
@echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
|
||||
@echo ' make install-linux-shortcuts'
|
||||
@echo
|
||||
@echo 'to install Linux AppStream metadata'
|
||||
@echo ' make install-linux-appdata'
|
||||
|
||||
########################### MAKEFILE SETTINGS ##########################
|
||||
#
|
||||
@@ -382,4 +188,4 @@ help:
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: check-scripts check nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies dependencies all-dependencies version install install-linux-shortcuts install-engine install-common-mod-files install-default-mods install-core install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help
|
||||
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata help
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,7 +26,7 @@ namespace OpenRA.Activities
|
||||
public readonly Color Color;
|
||||
public readonly Sprite Tile;
|
||||
|
||||
public TargetLineNode(Target target, Color color, Sprite tile = null)
|
||||
public TargetLineNode(in Target target, Color color, Sprite tile = null)
|
||||
{
|
||||
// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
|
||||
// if "yield break" in TargetLineNode(Actor self) is not feasible.
|
||||
@@ -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;
|
||||
@@ -185,8 +186,7 @@ namespace OpenRA.Activities
|
||||
/// </summary>
|
||||
internal void OnActorDisposeOuter(Actor self)
|
||||
{
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.OnActorDisposeOuter(self);
|
||||
ChildActivity?.OnActorDisposeOuter(self);
|
||||
|
||||
OnActorDispose(self);
|
||||
}
|
||||
@@ -199,8 +199,7 @@ namespace OpenRA.Activities
|
||||
if (!IsInterruptible)
|
||||
return;
|
||||
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.Cancel(self);
|
||||
ChildActivity?.Cancel(self);
|
||||
|
||||
// Directly mark activities that are queued and therefore didn't run yet as done
|
||||
State = State == ActivityState.Queued ? ActivityState.Done : ActivityState.Canceling;
|
||||
@@ -208,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>
|
||||
@@ -243,11 +242,9 @@ namespace OpenRA.Activities
|
||||
|
||||
Console.WriteLine(GetType().ToString().Split('.').Last());
|
||||
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.PrintActivityTree(self, origin, level + 1);
|
||||
ChildActivity?.PrintActivityTree(self, origin, level + 1);
|
||||
|
||||
if (NextActivity != null)
|
||||
NextActivity.PrintActivityTree(self, origin, level);
|
||||
NextActivity?.PrintActivityTree(self, origin, level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,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-2021 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,7 +26,7 @@ namespace OpenRA.Activities
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
{
|
||||
if (a != null) a();
|
||||
a.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
@@ -22,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;
|
||||
@@ -47,8 +58,8 @@ 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;
|
||||
@@ -58,26 +69,44 @@ namespace OpenRA
|
||||
public IOccupySpace OccupiesSpace { get; private set; }
|
||||
public ITargetable[] Targetables { 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
|
||||
public WRot Orientation => facing?.Orientation ?? WRot.None;
|
||||
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public static readonly int InvalidConditionToken = -1;
|
||||
|
||||
class ConditionState
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
|
||||
var facingValue = facing != null ? facing.Facing : 0;
|
||||
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
|
||||
}
|
||||
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
||||
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
|
||||
|
||||
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
||||
public readonly HashSet<int> Tokens = new HashSet<int>();
|
||||
}
|
||||
|
||||
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
|
||||
|
||||
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
||||
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
|
||||
|
||||
int nextConditionToken = 1;
|
||||
|
||||
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
|
||||
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
|
||||
|
||||
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
||||
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
|
||||
|
||||
internal SyncHash[] SyncHashes { get; private set; }
|
||||
|
||||
readonly IFacing facing;
|
||||
readonly IHealth health;
|
||||
readonly IResolveOrder[] resolveOrders;
|
||||
readonly IRenderModifier[] renderModifiers;
|
||||
readonly IRender[] renders;
|
||||
readonly IMouseBounds[] mouseBounds;
|
||||
@@ -85,18 +114,28 @@ namespace OpenRA
|
||||
readonly IDefaultVisibility defaultVisibility;
|
||||
readonly INotifyBecomingIdle[] becomingIdles;
|
||||
readonly INotifyIdle[] tickIdles;
|
||||
readonly ITargetablePositions[] targetablePositions;
|
||||
readonly IEnumerable<ITargetablePositions> enabledTargetablePositions;
|
||||
WPos[] staticTargetablePositions;
|
||||
bool created;
|
||||
bool setStaticTargetablePositions;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict)
|
||||
{
|
||||
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
|
||||
.FirstOrDefault(i => i.Count() > 1);
|
||||
|
||||
if (duplicateInit != null)
|
||||
throw new InvalidDataException($"Duplicate initializer '{duplicateInit.Key.Name}'");
|
||||
|
||||
var init = new ActorInitializer(this, initDict);
|
||||
|
||||
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
|
||||
|
||||
World = world;
|
||||
ActorID = world.NextAID();
|
||||
if (initDict.Contains<OwnerInit>())
|
||||
Owner = init.Get<OwnerInit, Player>();
|
||||
var ownerInit = init.GetOrDefault<OwnerInit>();
|
||||
if (ownerInit != null)
|
||||
Owner = ownerInit.Value(world);
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
@@ -106,51 +145,102 @@ namespace OpenRA
|
||||
throw new NotImplementedException("No rules definition for unit " + name);
|
||||
|
||||
Info = world.Map.Rules.Actors[name];
|
||||
foreach (var trait in Info.TraitsInConstructOrder())
|
||||
|
||||
IPositionable positionable = null;
|
||||
var resolveOrdersList = new List<IResolveOrder>();
|
||||
var renderModifiersList = new List<IRenderModifier>();
|
||||
var rendersList = new List<IRender>();
|
||||
var mouseBoundsList = new List<IMouseBounds>();
|
||||
var visibilityModifiersList = new List<IVisibilityModifier>();
|
||||
var becomingIdlesList = new List<INotifyBecomingIdle>();
|
||||
var tickIdlesList = new List<INotifyIdle>();
|
||||
var targetablesList = new List<ITargetable>();
|
||||
var targetablePositionsList = new List<ITargetablePositions>();
|
||||
var syncHashesList = new List<SyncHash>();
|
||||
|
||||
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
||||
{
|
||||
AddTrait(trait.Create(init));
|
||||
var trait = traitInfo.Create(init);
|
||||
AddTrait(trait);
|
||||
|
||||
// Some traits rely on properties provided by IOccupySpace in their initialization,
|
||||
// so we must ready it now, we cannot wait until all traits have finished construction.
|
||||
if (trait is IOccupySpaceInfo)
|
||||
OccupiesSpace = Trait<IOccupySpace>();
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
// Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style
|
||||
// rules for spacing in order to keep these assignments compact and readable.
|
||||
{ if (trait is IPositionable t) positionable = t; }
|
||||
{ if (trait is IOccupySpace t) OccupiesSpace = t; }
|
||||
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; }
|
||||
{ if (trait is IFacing t) facing = t; }
|
||||
{ if (trait is IHealth t) health = t; }
|
||||
{ if (trait is IResolveOrder t) resolveOrdersList.Add(t); }
|
||||
{ if (trait is IRenderModifier t) renderModifiersList.Add(t); }
|
||||
{ if (trait is IRender t) rendersList.Add(t); }
|
||||
{ if (trait is IMouseBounds t) mouseBoundsList.Add(t); }
|
||||
{ if (trait is IVisibilityModifier t) visibilityModifiersList.Add(t); }
|
||||
{ if (trait is IDefaultVisibility t) defaultVisibility = t; }
|
||||
{ if (trait is INotifyBecomingIdle t) becomingIdlesList.Add(t); }
|
||||
{ if (trait is INotifyIdle t) tickIdlesList.Add(t); }
|
||||
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
||||
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
||||
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
||||
}
|
||||
|
||||
resolveOrders = resolveOrdersList.ToArray();
|
||||
renderModifiers = renderModifiersList.ToArray();
|
||||
renders = rendersList.ToArray();
|
||||
mouseBounds = mouseBoundsList.ToArray();
|
||||
visibilityModifiers = visibilityModifiersList.ToArray();
|
||||
becomingIdles = becomingIdlesList.ToArray();
|
||||
tickIdles = tickIdlesList.ToArray();
|
||||
Targetables = targetablesList.ToArray();
|
||||
var targetablePositions = targetablePositionsList.ToArray();
|
||||
enabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
SyncHashes = syncHashesList.ToArray();
|
||||
|
||||
setStaticTargetablePositions = positionable == null && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled);
|
||||
}
|
||||
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
|
||||
facing = TraitOrDefault<IFacing>();
|
||||
health = TraitOrDefault<IHealth>();
|
||||
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
|
||||
renders = TraitsImplementing<IRender>().ToArray();
|
||||
mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
|
||||
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
|
||||
defaultVisibility = Trait<IDefaultVisibility>();
|
||||
becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
|
||||
tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
|
||||
Targetables = TraitsImplementing<ITargetable>().ToArray();
|
||||
targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
|
||||
world.AddFrameEndTask(w =>
|
||||
{
|
||||
// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
|
||||
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
|
||||
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
|
||||
if (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
|
||||
staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
|
||||
});
|
||||
|
||||
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
|
||||
}
|
||||
|
||||
internal void Created()
|
||||
internal void Initialize(bool addToWorld = true)
|
||||
{
|
||||
created = true;
|
||||
|
||||
// Make sure traits are usable for condition notifiers
|
||||
foreach (var t in TraitsImplementing<INotifyCreated>())
|
||||
t.Created(this);
|
||||
|
||||
var allObserverNotifiers = new HashSet<VariableObserverNotifier>();
|
||||
foreach (var provider in TraitsImplementing<IObservesVariables>())
|
||||
{
|
||||
foreach (var variableUser in provider.GetVariableObservers())
|
||||
{
|
||||
allObserverNotifiers.Add(variableUser.Notifier);
|
||||
foreach (var variable in variableUser.Variables)
|
||||
{
|
||||
var cs = conditionStates.GetOrAdd(variable);
|
||||
cs.Notifiers.Add(variableUser.Notifier);
|
||||
|
||||
// Initialize conditions that have not yet been granted to 0
|
||||
// NOTE: Some conditions may have already been granted by INotifyCreated calling GrantCondition,
|
||||
// and we choose to assign the token count to safely cover both cases instead of adding an if branch.
|
||||
conditionCache[variable] = cs.Tokens.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update all traits with their initial condition state
|
||||
foreach (var notify in allObserverNotifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
|
||||
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
|
||||
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
|
||||
if (setStaticTargetablePositions)
|
||||
staticTargetablePositions = enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
|
||||
|
||||
// TODO: Other traits may need initialization after being notified of initial condition state.
|
||||
|
||||
// TODO: A post condition initialization notification phase may allow queueing activities instead.
|
||||
// The initial activity should run before any activities queued by INotifyCreated.Created
|
||||
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
|
||||
ICreationActivity creationActivity = null;
|
||||
@@ -160,7 +250,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)
|
||||
@@ -171,6 +261,9 @@ namespace OpenRA
|
||||
activity.Queue(CurrentActivity);
|
||||
CurrentActivity = activity;
|
||||
}
|
||||
|
||||
if (addToWorld)
|
||||
World.Add(this);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
@@ -233,7 +326,7 @@ namespace OpenRA
|
||||
yield return r;
|
||||
}
|
||||
|
||||
public Rectangle MouseBounds(WorldRenderer wr)
|
||||
public Polygon MouseBounds(WorldRenderer wr)
|
||||
{
|
||||
foreach (var mb in mouseBounds)
|
||||
{
|
||||
@@ -242,7 +335,7 @@ namespace OpenRA
|
||||
return bounds;
|
||||
}
|
||||
|
||||
return Rectangle.Empty;
|
||||
return Polygon.Empty;
|
||||
}
|
||||
|
||||
public void QueueActivity(bool queued, Activity nextActivity)
|
||||
@@ -266,8 +359,7 @@ namespace OpenRA
|
||||
|
||||
public void CancelActivity()
|
||||
{
|
||||
if (CurrentActivity != null)
|
||||
CurrentActivity.Cancel(this);
|
||||
CurrentActivity?.Cancel(this);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -277,8 +369,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)
|
||||
@@ -319,8 +410,7 @@ namespace OpenRA
|
||||
{
|
||||
// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
|
||||
// This should be done before the FrameEndTask to avoid dependency issues.
|
||||
if (CurrentActivity != null)
|
||||
CurrentActivity.OnActorDisposeOuter(this);
|
||||
CurrentActivity?.OnActorDisposeOuter(this);
|
||||
|
||||
// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
|
||||
WillDispose = true;
|
||||
@@ -339,11 +429,16 @@ namespace OpenRA
|
||||
World.TraitDict.RemoveActor(this);
|
||||
Disposed = true;
|
||||
|
||||
if (luaInterface != null)
|
||||
luaInterface.Value.OnActorDestroyed();
|
||||
luaInterface?.Value.OnActorDestroyed();
|
||||
});
|
||||
}
|
||||
|
||||
public void ResolveOrder(Order order)
|
||||
{
|
||||
foreach (var r in resolveOrders)
|
||||
r.ResolveOrder(this, order);
|
||||
}
|
||||
|
||||
// TODO: move elsewhere.
|
||||
public void ChangeOwner(Player newOwner)
|
||||
{
|
||||
@@ -436,7 +531,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;
|
||||
@@ -447,13 +542,71 @@ namespace OpenRA
|
||||
if (staticTargetablePositions != null)
|
||||
return staticTargetablePositions;
|
||||
|
||||
var enabledTargetablePositionTraits = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
if (enabledTargetablePositionTraits.Any())
|
||||
return enabledTargetablePositionTraits.SelectMany(tp => tp.TargetablePositions(this));
|
||||
if (enabledTargetablePositions.Any())
|
||||
return enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||
|
||||
return new[] { CenterPosition };
|
||||
}
|
||||
|
||||
#region Conditions
|
||||
|
||||
void UpdateConditionState(string condition, int token, bool isRevoke)
|
||||
{
|
||||
ConditionState conditionState = conditionStates.GetOrAdd(condition);
|
||||
|
||||
if (isRevoke)
|
||||
conditionState.Tokens.Remove(token);
|
||||
else
|
||||
conditionState.Tokens.Add(token);
|
||||
|
||||
conditionCache[condition] = conditionState.Tokens.Count;
|
||||
|
||||
// Conditions may be granted or revoked before the state is initialized.
|
||||
// These notifications will be processed after INotifyCreated.Created.
|
||||
if (created)
|
||||
foreach (var notify in conditionState.Notifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grants a specified condition if it is valid.
|
||||
/// Otherwise, just returns InvalidConditionToken.
|
||||
/// </summary>
|
||||
/// <returns>The token that is used to revoke this condition.</returns>
|
||||
public int GrantCondition(string condition)
|
||||
{
|
||||
if (string.IsNullOrEmpty(condition))
|
||||
return InvalidConditionToken;
|
||||
|
||||
var token = nextConditionToken++;
|
||||
conditionTokens.Add(token, condition);
|
||||
UpdateConditionState(condition, token, false);
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revokes a previously granted condition.
|
||||
/// </summary>
|
||||
/// <param name="token">The token ID returned by GrantCondition.</param>
|
||||
/// <returns>The invalid token ID.</returns>
|
||||
public int RevokeCondition(int token)
|
||||
{
|
||||
if (!conditionTokens.TryGetValue(token, out var condition))
|
||||
throw new InvalidOperationException($"Attempting to revoke condition with invalid token {token} for {this}.");
|
||||
|
||||
conditionTokens.Remove(token);
|
||||
UpdateConditionState(condition, token, true);
|
||||
return InvalidConditionToken;
|
||||
}
|
||||
|
||||
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
|
||||
public bool TokenValid(int token)
|
||||
{
|
||||
return conditionTokens.ContainsKey(token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scripting interface
|
||||
|
||||
Lazy<ScriptActorInterface> luaInterface;
|
||||
@@ -465,14 +618,13 @@ 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)
|
||||
{
|
||||
Actor a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out Actor a) || !right.TryGetClrValue(out Actor b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -480,7 +632,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-2021 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)
|
||||
@@ -89,41 +89,35 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CPos a;
|
||||
CVec b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException($"Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
}
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CPos a;
|
||||
var rightType = right.WrappedClrType();
|
||||
if (!left.TryGetClrValue(out a))
|
||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
|
||||
if (!left.TryGetClrValue(out CPos a))
|
||||
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
|
||||
|
||||
if (rightType == typeof(CPos))
|
||||
{
|
||||
CPos b;
|
||||
right.TryGetClrValue(out b);
|
||||
right.TryGetClrValue(out CPos b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
else if (rightType == typeof(CVec))
|
||||
{
|
||||
CVec b;
|
||||
right.TryGetClrValue(out b);
|
||||
right.TryGetClrValue(out CVec b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
CPos a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CPos b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -138,14 +132,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-2021 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)
|
||||
{
|
||||
@@ -75,18 +75,16 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException($"Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
}
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException($"Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
@@ -98,8 +96,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -113,14 +110,11 @@ namespace OpenRA
|
||||
{
|
||||
case "X": return X;
|
||||
case "Y": return Y;
|
||||
default: throw new LuaException("CVec does not define a member '{0}'".F(key));
|
||||
default: throw new LuaException($"CVec does not define a member '{key}'");
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
||||
}
|
||||
set => throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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-2021 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
OpenRA.Game/DefaultPlayer.cs
Normal file
20
OpenRA.Game/DefaultPlayer.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class DefaultPlayer : IGlobalModData
|
||||
{
|
||||
public readonly Color Color = Color.FromArgb(0xEE, 0xEE, 0xEE);
|
||||
}
|
||||
}
|
||||
@@ -1,97 +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)
|
||||
if (wc != null)
|
||||
wc.CancelAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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-2021 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-2021 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-2021 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-2021 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,7 +59,9 @@ namespace OpenRA
|
||||
|
||||
public ExternalMods()
|
||||
{
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
// Don't try to load mod icons if we don't have a texture to put them in
|
||||
if (Game.Renderer != null)
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
@@ -94,20 +96,24 @@ namespace OpenRA
|
||||
void LoadMod(MiniYaml yaml, string path = null, bool forceRegistration = false)
|
||||
{
|
||||
var mod = FieldLoader.Load<ExternalMod>(yaml);
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
if (sheetBuilder != null)
|
||||
{
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
}
|
||||
|
||||
// Avoid possibly overwriting a valid mod with an obviously bogus one
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
@@ -115,7 +121,7 @@ namespace OpenRA
|
||||
mods[key] = mod;
|
||||
}
|
||||
|
||||
internal void Register(Manifest mod, string launchPath, ModRegistration registration)
|
||||
internal void Register(Manifest mod, string launchPath, IEnumerable<string> launchArgs, ModRegistration registration)
|
||||
{
|
||||
if (mod.Metadata.Hidden)
|
||||
return;
|
||||
@@ -127,7 +133,7 @@ namespace OpenRA
|
||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||
new MiniYamlNode("Title", mod.Metadata.Title),
|
||||
new MiniYamlNode("LaunchPath", launchPath),
|
||||
new MiniYamlNode("LaunchArgs", "Game.Mod=" + mod.Id)
|
||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||
}));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon.png"))
|
||||
@@ -284,10 +290,15 @@ 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; } }
|
||||
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-2021 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
|
||||
@@ -80,7 +80,7 @@ namespace OpenRA
|
||||
|
||||
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
|
||||
{
|
||||
return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y);
|
||||
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
|
||||
}
|
||||
|
||||
public static bool PolygonContains(this int2[] polygon, int2 p)
|
||||
@@ -101,6 +101,16 @@ namespace OpenRA
|
||||
return windingNumber != 0;
|
||||
}
|
||||
|
||||
public static bool LinesIntersect(int2 a, int2 b, int2 c, int2 d)
|
||||
{
|
||||
// If line segments AB and CD intersect:
|
||||
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
|
||||
// - the triangles CAB and DAB must have opposite sense
|
||||
// Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other
|
||||
// Assumes that lines are not colinear
|
||||
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
|
||||
}
|
||||
|
||||
public static bool HasModifier(this Modifiers k, Modifiers mod)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
@@ -110,13 +120,19 @@ namespace OpenRA
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k)
|
||||
where V : new()
|
||||
{
|
||||
return d.GetOrAdd(k, _ => new V());
|
||||
return d.GetOrAdd(k, new V());
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
|
||||
{
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
|
||||
{
|
||||
V ret;
|
||||
if (!d.TryGetValue(k, out ret))
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = createFn(k));
|
||||
return ret;
|
||||
}
|
||||
@@ -143,7 +159,7 @@ 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);
|
||||
}
|
||||
@@ -220,7 +236,7 @@ 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);
|
||||
t = e.Current;
|
||||
@@ -262,7 +278,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);
|
||||
}
|
||||
@@ -303,7 +319,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);
|
||||
}
|
||||
@@ -343,8 +359,7 @@ namespace OpenRA
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
int remainder;
|
||||
var quotient = Math.DivRem(dividend, divisor, out remainder);
|
||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||
if (remainder == 0)
|
||||
return quotient;
|
||||
return quotient + (Math.Sign(dividend) == Math.Sign(divisor) ? 1 : -1);
|
||||
@@ -395,8 +410,7 @@ namespace OpenRA
|
||||
// Check for a key conflict:
|
||||
if (d.ContainsKey(key))
|
||||
{
|
||||
List<string> dupKeyMessages;
|
||||
if (!dupKeys.TryGetValue(key, out dupKeyMessages))
|
||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||
{
|
||||
// Log the initial conflicting value already inserted:
|
||||
dupKeyMessages = new List<string>();
|
||||
@@ -415,8 +429,8 @@ namespace OpenRA
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -497,8 +511,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)
|
||||
@@ -520,6 +533,50 @@ namespace OpenRA
|
||||
|
||||
return default(T);
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,7 +15,6 @@ using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -73,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<>))
|
||||
{
|
||||
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<,>))
|
||||
@@ -113,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-2021 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; private set; }
|
||||
public int Height { get; private set; }
|
||||
public Color[] Palette { get; private set; }
|
||||
public byte[] Data { get; private set; }
|
||||
public SpriteFrameType Type { get; private set; }
|
||||
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-2021 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-2021 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,19 +63,16 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
// Raw directories are the easiest and one of the most common cases, so try these first
|
||||
var resolvedPath = Platform.ResolvePath(filename);
|
||||
if (!filename.Contains("|") && Directory.Exists(resolvedPath))
|
||||
if (!resolvedPath.Contains("|") && Directory.Exists(resolvedPath))
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Children of another package require special handling
|
||||
IReadOnlyPackage parent;
|
||||
string subPath = null;
|
||||
if (TryGetPackageContaining(filename, out parent, out subPath))
|
||||
if (TryGetPackageContaining(filename, out var parent, out var subPath))
|
||||
return parent.OpenPackage(subPath, this);
|
||||
|
||||
// Try and open it normally
|
||||
IReadOnlyPackage package;
|
||||
var stream = Open(filename);
|
||||
if (TryParsePackage(stream, filename, out package))
|
||||
if (TryParsePackage(stream, filename, out var package))
|
||||
return package;
|
||||
|
||||
// No package loaders took ownership of the stream, so clean it up
|
||||
@@ -97,9 +94,8 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
name = name.Substring(1);
|
||||
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(name, out mod))
|
||||
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
|
||||
if (!installedMods.TryGetValue(name, out var mod))
|
||||
throw new InvalidOperationException($"Could not load mod '{name}'. Available mods: {installedMods.Keys.JoinWith(", ")}");
|
||||
|
||||
package = mod.Package;
|
||||
modPackages.Add(package);
|
||||
@@ -108,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);
|
||||
@@ -122,8 +118,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Mount(IReadOnlyPackage package, string explicitName = null)
|
||||
{
|
||||
var mountCount = 0;
|
||||
if (mountedPackages.TryGetValue(package, out mountCount))
|
||||
if (mountedPackages.TryGetValue(package, out var mountCount))
|
||||
{
|
||||
// Package is already mounted
|
||||
// Increment the mount count and bump up the file loading priority
|
||||
@@ -149,8 +144,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public bool Unmount(IReadOnlyPackage package)
|
||||
{
|
||||
var mountCount = 0;
|
||||
if (!mountedPackages.TryGetValue(package, out mountCount))
|
||||
if (!mountedPackages.TryGetValue(package, out var mountCount))
|
||||
return false;
|
||||
|
||||
if (--mountCount <= 0)
|
||||
@@ -203,17 +197,13 @@ namespace OpenRA.FileSystem
|
||||
var package = fileIndex[filename]
|
||||
.LastOrDefault(x => x.Contains(filename));
|
||||
|
||||
if (package != null)
|
||||
return package.GetStream(filename);
|
||||
|
||||
return null;
|
||||
return package?.GetStream(filename);
|
||||
}
|
||||
|
||||
public Stream Open(string filename)
|
||||
{
|
||||
Stream s;
|
||||
if (!TryOpen(filename, out s))
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
if (!TryOpen(filename, out var s))
|
||||
throw new FileNotFoundException($"File not found: {filename}", filename);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -238,8 +228,7 @@ namespace OpenRA.FileSystem
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
|
||||
if (s != null)
|
||||
@@ -274,12 +263,9 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
@@ -293,8 +279,7 @@ namespace OpenRA.FileSystem
|
||||
if (explicitSplit < 0)
|
||||
return false;
|
||||
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
return false;
|
||||
|
||||
if (installedMods[modID].Package == explicitPackage)
|
||||
@@ -310,7 +295,7 @@ namespace OpenRA.FileSystem
|
||||
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
if (explicitSplit > 0 && !path.StartsWith("^"))
|
||||
{
|
||||
var parent = path.Substring(0, explicitSplit);
|
||||
var filename = path.Substring(explicitSplit + 1);
|
||||
@@ -321,8 +306,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out mod))
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out var mod))
|
||||
return null;
|
||||
|
||||
if (!(mod.Package is Folder))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
@@ -26,16 +27,17 @@ namespace OpenRA.FileSystem
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public string Name { get { return path; } }
|
||||
public string Name => path;
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return Path.GetFileName(filename);
|
||||
foreach (var filename in Directory.GetDirectories(path))
|
||||
yield return Path.GetFileName(filename);
|
||||
// Order may vary on different file systems and it matters for hashing.
|
||||
return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)
|
||||
.Concat(Directory.GetDirectories(path))
|
||||
.Select(Path.GetFileName)
|
||||
.OrderBy(f => f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,17 +60,15 @@ namespace OpenRA.FileSystem
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Zip files loaded from Folders (and *only* from Folders) can be read-write
|
||||
IReadWritePackage readWritePackage;
|
||||
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out readWritePackage))
|
||||
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out var readWritePackage))
|
||||
return readWritePackage;
|
||||
|
||||
// Other package types can be loaded normally
|
||||
IReadOnlyPackage package;
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out package))
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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-2021 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,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
@@ -67,8 +66,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (pkg != null)
|
||||
pkg.Close();
|
||||
pkg?.Close();
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
@@ -82,12 +80,11 @@ namespace OpenRA.FileSystem
|
||||
return new ZipFolder(this, filename);
|
||||
|
||||
// Other package types can be loaded normally
|
||||
IReadOnlyPackage package;
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out package))
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
@@ -144,7 +141,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
sealed class ZipFolder : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get { return path; } }
|
||||
public string Name => path;
|
||||
public ReadOnlyZipFile Parent { get; private set; }
|
||||
readonly string path;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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-2021 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 @@ 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,7 +31,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; }
|
||||
@@ -41,6 +40,7 @@ namespace OpenRA
|
||||
public static Settings Settings;
|
||||
public static CursorManager Cursor;
|
||||
public static bool HideCursor;
|
||||
|
||||
static WorldRenderer worldRenderer;
|
||||
static string modLaunchWrapper;
|
||||
|
||||
@@ -51,68 +51,91 @@ namespace OpenRA
|
||||
|
||||
public static Renderer Renderer;
|
||||
public static Sound Sound;
|
||||
public static bool HasInputFocus = false;
|
||||
|
||||
public static string EngineVersion { get; private set; }
|
||||
public static LocalPlayerProfile LocalPlayerProfile;
|
||||
|
||||
static Task discoverNat;
|
||||
static bool takeScreenshot = false;
|
||||
static Benchmark benchmark = null;
|
||||
|
||||
public static event Action OnShellmapLoaded = () => { };
|
||||
|
||||
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
|
||||
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
|
||||
{
|
||||
var connection = new NetworkConnection(host, port);
|
||||
var newConnection = new NetworkConnection(endpoint);
|
||||
if (recordReplay)
|
||||
connection.StartRecording(() => { return TimestampedFilename(); });
|
||||
newConnection.StartRecording(() => { return TimestampedFilename(); });
|
||||
|
||||
var om = new OrderManager(host, port, password, connection);
|
||||
var om = new OrderManager(newConnection);
|
||||
JoinInner(om);
|
||||
CurrentServerSettings.Password = password;
|
||||
CurrentServerSettings.Target = endpoint;
|
||||
|
||||
lastConnectionState = ConnectionState.PreConnecting;
|
||||
ConnectionStateChanged(OrderManager, password, newConnection);
|
||||
|
||||
return om;
|
||||
}
|
||||
|
||||
static string TimestampedFilename(bool includemilliseconds = false)
|
||||
public static string TimestampedFilename(bool includemilliseconds = false, string extra = "")
|
||||
{
|
||||
var format = includemilliseconds ? "yyyy-MM-ddTHHmmssfffZ" : "yyyy-MM-ddTHHmmssZ";
|
||||
return "OpenRA-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
|
||||
return ModData.Manifest.Id + extra + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
static void JoinInner(OrderManager om)
|
||||
{
|
||||
if (OrderManager != null) OrderManager.Dispose();
|
||||
// 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("<no server>", -1, "", new ReplayConnection(replayFile)));
|
||||
JoinInner(new OrderManager(new ReplayConnection(replayFile)));
|
||||
}
|
||||
|
||||
static void JoinLocal()
|
||||
{
|
||||
JoinInner(new OrderManager("<no server>", -1, "", 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; } }
|
||||
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<string, int> OnRemoteDirectConnect = (a, b) => { };
|
||||
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
|
||||
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
|
||||
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(string host, int port)
|
||||
public static void RemoteDirectConnect(ConnectionTarget endpoint)
|
||||
{
|
||||
OnRemoteDirectConnect(host, port);
|
||||
OnRemoteDirectConnect(endpoint);
|
||||
}
|
||||
|
||||
// Hacky workaround for orderManager visibility
|
||||
@@ -155,8 +178,7 @@ namespace OpenRA
|
||||
internal static void StartGame(string mapUID, WorldType type)
|
||||
{
|
||||
// Dispose of the old world before creating a new one.
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
worldRenderer?.Dispose();
|
||||
|
||||
Cursor.SetCursor(null);
|
||||
BeforeGameStart();
|
||||
@@ -165,6 +187,7 @@ namespace OpenRA
|
||||
|
||||
using (new PerfTimer("PrepareMap"))
|
||||
map = ModData.PrepareMap(mapUID);
|
||||
|
||||
using (new PerfTimer("NewWorld"))
|
||||
OrderManager.World = new World(ModData, map, OrderManager, type);
|
||||
|
||||
@@ -172,11 +195,13 @@ namespace OpenRA
|
||||
|
||||
worldRenderer = new WorldRenderer(ModData, OrderManager.World);
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
|
||||
using (new PerfTimer("LoadComplete"))
|
||||
OrderManager.World.LoadComplete(worldRenderer);
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
|
||||
if (OrderManager.GameStarted)
|
||||
@@ -185,19 +210,25 @@ 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.
|
||||
// - Live objects are likely to live for the length of the game or longer,
|
||||
// thus promoting them into a higher generation is not an issue.
|
||||
// - We can remove any fragmentation in the LOH caused by temporary loading garbage.
|
||||
// - A loading screen is visible, so a delay won't matter to the user.
|
||||
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
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
|
||||
@@ -205,7 +236,7 @@ namespace OpenRA
|
||||
|
||||
var orders = new[]
|
||||
{
|
||||
Order.Command("sync_lobby {0}".F(lobbyInfo.Serialize())),
|
||||
Order.Command($"sync_lobby {lobbyInfo.Serialize()}"),
|
||||
Order.Command("startgame")
|
||||
};
|
||||
|
||||
@@ -234,7 +265,7 @@ namespace OpenRA
|
||||
|
||||
LobbyInfoChanged += lobbyReady;
|
||||
|
||||
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
|
||||
om = JoinServer(CreateLocalServer(mapUID), "");
|
||||
}
|
||||
|
||||
public static bool IsHost
|
||||
@@ -253,18 +284,24 @@ namespace OpenRA
|
||||
|
||||
public static void InitializeSettings(Arguments args)
|
||||
{
|
||||
Settings = new Settings(Platform.ResolvePath(Path.Combine(Platform.SupportDirPrefix, "settings.yaml")), args);
|
||||
Settings = new Settings(Path.Combine(Platform.SupportDir, "settings.yaml"), args);
|
||||
}
|
||||
|
||||
public static RunStatus InitializeAndRun(string[] args)
|
||||
{
|
||||
Initialize(new Arguments(args));
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
return Run();
|
||||
}
|
||||
|
||||
static void Initialize(Arguments args)
|
||||
{
|
||||
var engineDirArg = args.GetValue("Engine.EngineDir", null);
|
||||
if (!string.IsNullOrEmpty(engineDirArg))
|
||||
Platform.OverrideEngineDir(engineDirArg);
|
||||
|
||||
var supportDirArg = args.GetValue("Engine.SupportDir", null);
|
||||
if (!string.IsNullOrEmpty(supportDirArg))
|
||||
Platform.OverrideSupportDir(supportDirArg);
|
||||
@@ -274,7 +311,7 @@ namespace OpenRA
|
||||
// Load the engine version as early as possible so it can be written to exception logs
|
||||
try
|
||||
{
|
||||
EngineVersion = File.ReadAllText(Platform.ResolvePath(Path.Combine(".", "VERSION"))).Trim();
|
||||
EngineVersion = File.ReadAllText(Path.Combine(Platform.EngineDir, "VERSION")).Trim();
|
||||
}
|
||||
catch { }
|
||||
|
||||
@@ -282,6 +319,7 @@ namespace OpenRA
|
||||
EngineVersion = "Unknown";
|
||||
|
||||
Console.WriteLine("Engine version is {0}", EngineVersion);
|
||||
Console.WriteLine("Runtime: {0}", 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
|
||||
@@ -302,6 +340,7 @@ namespace OpenRA
|
||||
Log.AddChannel("graphics", "graphics.log");
|
||||
Log.AddChannel("geoip", "geoip.log");
|
||||
Log.AddChannel("nat", "nat.log");
|
||||
Log.AddChannel("client", "client.log");
|
||||
|
||||
var platforms = new[] { Settings.Game.Platform, "Default", null };
|
||||
foreach (var p in platforms)
|
||||
@@ -312,10 +351,17 @@ namespace OpenRA
|
||||
Settings.Game.Platform = p;
|
||||
try
|
||||
{
|
||||
var rendererPath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + p + ".dll"));
|
||||
var assembly = Assembly.LoadFile(rendererPath);
|
||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
|
||||
|
||||
#if !MONO
|
||||
var loader = new AssemblyLoader(rendererPath);
|
||||
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||
|
||||
#else
|
||||
var assembly = 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.");
|
||||
|
||||
@@ -330,21 +376,18 @@ namespace OpenRA
|
||||
Log.Write("graphics", "{0}", e);
|
||||
Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
|
||||
|
||||
if (Renderer != null)
|
||||
Renderer.Dispose();
|
||||
Renderer?.Dispose();
|
||||
|
||||
if (Sound != null)
|
||||
Sound.Dispose();
|
||||
Sound?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.Server.DiscoverNatDevices)
|
||||
discoverNat = UPnP.DiscoverNatDevices(Settings.Server.NatDiscoveryTimeout);
|
||||
Nat.Initialize();
|
||||
|
||||
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
|
||||
var modSearchPaths = modSearchArg != null ?
|
||||
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
|
||||
new[] { Path.Combine(".", "mods") };
|
||||
new[] { Path.Combine(Platform.EngineDir, "mods") };
|
||||
|
||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||
Console.WriteLine("Internal mods:");
|
||||
@@ -355,20 +398,28 @@ namespace OpenRA
|
||||
|
||||
ExternalMods = new ExternalMods();
|
||||
|
||||
Manifest currentMod;
|
||||
if (modID != null && Mods.TryGetValue(modID, out currentMod))
|
||||
if (modID != null && Mods.TryGetValue(modID, out _))
|
||||
{
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", null);
|
||||
var launchArgs = new List<string>();
|
||||
|
||||
// Sanitize input from platform-specific launchers
|
||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||
if (launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath.Substring(1, launchPath.Length - 2);
|
||||
|
||||
ExternalMods.Register(Mods[modID], launchPath, ModRegistration.User);
|
||||
if (launchPath == null)
|
||||
{
|
||||
// When launching the assembly directly we must propagate the Engine.EngineDir argument if defined
|
||||
// Platform-specific launchers are expected to manage this internally.
|
||||
launchPath = Assembly.GetEntryAssembly().Location;
|
||||
if (!string.IsNullOrEmpty(engineDirArg))
|
||||
launchArgs.Add("Engine.EngineDir=\"" + engineDirArg + "\"");
|
||||
}
|
||||
|
||||
ExternalMod activeMod;
|
||||
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out activeMod))
|
||||
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
|
||||
|
||||
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
|
||||
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
|
||||
}
|
||||
|
||||
@@ -377,26 +428,24 @@ namespace OpenRA
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
||||
|
||||
InitializeMod(modID, args);
|
||||
Ui.InitializeTranslation();
|
||||
}
|
||||
|
||||
public static void InitializeMod(string mod, Arguments args)
|
||||
{
|
||||
// Clear static state if we have switched mods
|
||||
LobbyInfoChanged = () => { };
|
||||
ConnectionStateChanged = om => { };
|
||||
ConnectionStateChanged = (om, p, conn) => { };
|
||||
BeforeGameStart = () => { };
|
||||
OnRemoteDirectConnect = (a, b) => { };
|
||||
OnRemoteDirectConnect = endpoint => { };
|
||||
delayedActions = new ActionQueue();
|
||||
|
||||
Ui.ResetAll();
|
||||
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
worldRenderer?.Dispose();
|
||||
worldRenderer = null;
|
||||
if (server != null)
|
||||
server.Shutdown();
|
||||
if (OrderManager != null)
|
||||
OrderManager.Dispose();
|
||||
server?.Shutdown();
|
||||
OrderManager?.Dispose();
|
||||
|
||||
if (ModData != null)
|
||||
{
|
||||
@@ -410,7 +459,7 @@ 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);
|
||||
|
||||
@@ -418,59 +467,48 @@ namespace OpenRA
|
||||
|
||||
ModData = new ModData(Mods[mod], Mods, true);
|
||||
|
||||
LocalPlayerProfile = new LocalPlayerProfile(Platform.ResolvePath(Path.Combine("^", Settings.Game.AuthProfile)), ModData.Manifest.Get<PlayerDatabase>());
|
||||
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.Manifest.Get<PlayerDatabase>());
|
||||
|
||||
if (!ModData.LoadScreen.BeforeLoad())
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
if (Cursor != null)
|
||||
Cursor.Dispose();
|
||||
|
||||
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;
|
||||
PerfHistory.Items["render_widgets"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_flip"].HasNormalTick = false;
|
||||
PerfHistory.Items["terrain_lighting"].HasNormalTick = false;
|
||||
|
||||
JoinLocal();
|
||||
|
||||
try
|
||||
{
|
||||
if (discoverNat != null)
|
||||
discoverNat.Wait();
|
||||
}
|
||||
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);
|
||||
@@ -525,8 +563,7 @@ 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); }
|
||||
|
||||
@@ -535,7 +572,7 @@ namespace OpenRA
|
||||
using (new PerfTimer("Renderer.SaveScreenshot"))
|
||||
{
|
||||
var mod = ModData.Manifest.Metadata;
|
||||
var directory = Platform.ResolvePath(Platform.SupportDirPrefix, "Screenshots", ModData.Manifest.Id, mod.Version);
|
||||
var directory = Path.Combine(Platform.SupportDir, "Screenshots", ModData.Manifest.Id, mod.Version);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var filename = TimestampedFilename(true);
|
||||
@@ -543,7 +580,7 @@ namespace OpenRA
|
||||
Log.Write("debug", "Taking screenshot " + path);
|
||||
|
||||
Renderer.SaveScreenshot(path);
|
||||
Debug("Saved screenshot " + filename);
|
||||
TextNotificationsManager.Debug("Saved screenshot " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,48 +590,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);
|
||||
});
|
||||
@@ -603,16 +621,13 @@ 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));
|
||||
}
|
||||
|
||||
if (benchmark != null)
|
||||
benchmark.Tick(LocalTick);
|
||||
benchmark?.Tick(LocalTick);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,10 +635,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);
|
||||
@@ -710,6 +725,7 @@ namespace OpenRA
|
||||
PerfHistory.Items["render_world"].Tick();
|
||||
PerfHistory.Items["render_widgets"].Tick();
|
||||
PerfHistory.Items["render_flip"].Tick();
|
||||
PerfHistory.Items["terrain_lighting"].Tick();
|
||||
}
|
||||
|
||||
static void Loop()
|
||||
@@ -759,9 +775,14 @@ 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.IsLoadingGameSave ? 1 :
|
||||
logicWorld.IsReplay ? logicWorld.ReplayTimestep :
|
||||
logicWorld.Timestep;
|
||||
|
||||
// Ideal time between screen updates
|
||||
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
|
||||
@@ -799,8 +820,7 @@ namespace OpenRA
|
||||
|
||||
var haveSomeTimeUntilNextLogic = now < nextLogic;
|
||||
var isTimeToRender = now >= nextRender;
|
||||
|
||||
if ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender)
|
||||
if (!Renderer.WindowIsSuspended && ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender))
|
||||
{
|
||||
nextRender = now + renderInterval;
|
||||
|
||||
@@ -815,6 +835,19 @@ namespace OpenRA
|
||||
RenderTick();
|
||||
renderBeforeNextTick = false;
|
||||
}
|
||||
|
||||
// Simulate a render tick if it was time to render but we skip actually rendering
|
||||
if (Renderer.WindowIsSuspended && isTimeToRender)
|
||||
{
|
||||
// Make sure that nextUpdate is set to a proper minimum interval
|
||||
nextRender = now + renderInterval;
|
||||
|
||||
// Still process SDL events to allow a restore to come through
|
||||
Renderer.Window.PumpInput(new NullInputHandler());
|
||||
|
||||
// Ensure that we still logic tick despite not rendering
|
||||
renderBeforeNextTick = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep((int)(nextUpdate - now));
|
||||
@@ -836,12 +869,10 @@ namespace OpenRA
|
||||
finally
|
||||
{
|
||||
// Ensure that the active replay is properly saved
|
||||
if (OrderManager != null)
|
||||
OrderManager.Dispose();
|
||||
OrderManager?.Dispose();
|
||||
}
|
||||
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
worldRenderer?.Dispose();
|
||||
ModData.Dispose();
|
||||
ChromeProvider.Deinitialize();
|
||||
|
||||
@@ -858,30 +889,9 @@ 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()
|
||||
{
|
||||
if (OrderManager.World != null)
|
||||
OrderManager.World.TraitDict.PrintReport();
|
||||
OrderManager.World?.TraitDict.PrintReport();
|
||||
|
||||
OrderManager.Dispose();
|
||||
CloseServer();
|
||||
@@ -890,8 +900,7 @@ namespace OpenRA
|
||||
|
||||
public static void CloseServer()
|
||||
{
|
||||
if (server != null)
|
||||
server.Shutdown();
|
||||
server?.Shutdown();
|
||||
}
|
||||
|
||||
public static T CreateObject<T>(string name)
|
||||
@@ -899,12 +908,19 @@ namespace OpenRA
|
||||
return ModData.ObjectCreator.CreateObject<T>(name);
|
||||
}
|
||||
|
||||
public static void CreateServer(ServerSettings settings)
|
||||
public static ConnectionTarget CreateServer(ServerSettings settings)
|
||||
{
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
|
||||
new IPEndPoint(IPAddress.Any, settings.ListenPort)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
||||
|
||||
return server.GetEndpointForLocalConnection();
|
||||
}
|
||||
|
||||
public static int CreateLocalServer(string map)
|
||||
public static ConnectionTarget CreateLocalServer(string map)
|
||||
{
|
||||
var settings = new ServerSettings()
|
||||
{
|
||||
@@ -913,9 +929,16 @@ namespace OpenRA
|
||||
AdvertiseOnline = false
|
||||
};
|
||||
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
|
||||
// Always connect to local games using the same loopback connection
|
||||
// Exposing multiple endpoints introduces a race condition on the client's PlayerIndex (sometimes 0, sometimes 1)
|
||||
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.Loopback, 0)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
|
||||
|
||||
return server.Port;
|
||||
return server.GetEndpointForLocalConnection();
|
||||
}
|
||||
|
||||
public static bool IsCurrentWorld(World world)
|
||||
@@ -937,8 +960,8 @@ 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);
|
||||
@@ -946,7 +969,7 @@ namespace OpenRA
|
||||
ModData.MapCache.SingleOrDefault(m => m.Package.Name == path);
|
||||
|
||||
if (map == null)
|
||||
throw new InvalidOperationException("Could not find map '{0}'.".F(launchMap));
|
||||
throw new InvalidOperationException($"Could not find map '{launchMap}'.");
|
||||
|
||||
CreateAndStartLocalServer(map.Uid, orders);
|
||||
}
|
||||
@@ -960,4 +983,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-2021 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,11 +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 TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero;
|
||||
|
||||
public IList<Player> Players { get; private set; }
|
||||
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
|
||||
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
|
||||
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;
|
||||
|
||||
@@ -74,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;
|
||||
}
|
||||
}
|
||||
@@ -87,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();
|
||||
}
|
||||
@@ -96,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)
|
||||
@@ -118,11 +120,14 @@ namespace OpenRA
|
||||
IsBot = runtimePlayer.IsBot,
|
||||
FactionName = runtimePlayer.Faction.Name,
|
||||
FactionId = runtimePlayer.Faction.InternalName,
|
||||
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||
Color = runtimePlayer.Color,
|
||||
Team = client.Team,
|
||||
Handicap = client.Handicap,
|
||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
|
||||
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint,
|
||||
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
|
||||
Fingerprint = client.Fingerprint
|
||||
};
|
||||
|
||||
@@ -133,9 +138,7 @@ namespace OpenRA
|
||||
/// <summary>Gets the player information for the specified runtime player instance.</summary>
|
||||
public Player GetPlayer(OpenRA.Player runtimePlayer)
|
||||
{
|
||||
Player player;
|
||||
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out player);
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out var player);
|
||||
|
||||
return player;
|
||||
}
|
||||
@@ -158,9 +161,14 @@ namespace OpenRA
|
||||
public string FactionId;
|
||||
public Color Color;
|
||||
|
||||
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
|
||||
public string DisplayFactionName;
|
||||
public string DisplayFactionId;
|
||||
|
||||
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
|
||||
public int Team;
|
||||
public int SpawnPoint;
|
||||
public int Handicap;
|
||||
|
||||
/// <summary>True if the faction was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomFaction;
|
||||
@@ -181,6 +189,9 @@ namespace OpenRA
|
||||
/// <summary>The time when this player won or lost the game.</summary>
|
||||
public DateTime OutcomeTimestampUtc;
|
||||
|
||||
/// <summary>The frame at which this player disconnected.</summary>
|
||||
public int DisconnectFrame;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,6 +23,7 @@ namespace OpenRA
|
||||
public class ActorInfo
|
||||
{
|
||||
public const string AbstractActorPrefix = "^";
|
||||
public const char TraitInstanceSeparator = '@';
|
||||
|
||||
/// <summary>
|
||||
/// The actor name can be anything, but the sprites used in the Render*: traits default to this one.
|
||||
@@ -32,7 +33,7 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
readonly TypeDictionary traits = new TypeDictionary();
|
||||
List<ITraitInfo> constructOrderCache = null;
|
||||
List<TraitInfo> constructOrderCache = null;
|
||||
|
||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||
{
|
||||
@@ -46,7 +47,7 @@ namespace OpenRA
|
||||
{
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// LoadTraitInfo will only return null to signal us to abort here if the linter is running
|
||||
var trait = LoadTraitInfo(creator, t.Key.Split('@')[0], t.Value);
|
||||
var trait = LoadTraitInfo(creator, t.Key, t.Value);
|
||||
if (trait != null)
|
||||
traits.Add(trait);
|
||||
}
|
||||
@@ -60,11 +61,11 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException("Actor type {0}: {1}".F(name, e.Message));
|
||||
throw new YamlException($"Actor type {name}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public ActorInfo(string name, params ITraitInfo[] traitInfos)
|
||||
public ActorInfo(string name, params TraitInfo[] traitInfos)
|
||||
{
|
||||
Name = name;
|
||||
foreach (var t in traitInfos)
|
||||
@@ -72,20 +73,23 @@ namespace OpenRA
|
||||
traits.TrimExcess();
|
||||
}
|
||||
|
||||
static ITraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException("Junk value `{0}` on trait node {1}"
|
||||
.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
|
||||
var info = creator.CreateObject<ITraitInfo>(traitName + "Info");
|
||||
var traitInstance = traitName.Split(TraitInstanceSeparator);
|
||||
var info = creator.CreateObject<TraitInfo>(traitInstance[0] + "Info");
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (traitInstance.Length > 1)
|
||||
info.GetType().GetField(nameof(info.InstanceName)).SetValue(info, traitInstance[1]);
|
||||
|
||||
FieldLoader.Load(info, my);
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
@@ -97,12 +101,12 @@ namespace OpenRA
|
||||
return info;
|
||||
}
|
||||
|
||||
public IEnumerable<ITraitInfo> TraitsInConstructOrder()
|
||||
public IEnumerable<TraitInfo> TraitsInConstructOrder()
|
||||
{
|
||||
if (constructOrderCache != null)
|
||||
return constructOrderCache;
|
||||
|
||||
var source = traits.WithInterface<ITraitInfo>().Select(i => new
|
||||
var source = traits.WithInterface<TraitInfo>().Select(i => new
|
||||
{
|
||||
Trait = i,
|
||||
Type = i.GetType(),
|
||||
@@ -148,7 +152,7 @@ namespace OpenRA
|
||||
return constructOrderCache;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> PrerequisitesOf(ITraitInfo info)
|
||||
public static IEnumerable<Type> PrerequisitesOf(TraitInfo info)
|
||||
{
|
||||
return info
|
||||
.GetType()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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
|
||||
@@ -41,8 +41,7 @@ namespace OpenRA.GameRules
|
||||
|
||||
public void Load(IReadOnlyFileSystem fileSystem)
|
||||
{
|
||||
Stream stream;
|
||||
if (!fileSystem.TryOpen(Filename, out stream))
|
||||
if (!fileSystem.TryOpen(Filename, out var stream))
|
||||
return;
|
||||
|
||||
try
|
||||
@@ -50,8 +49,7 @@ namespace OpenRA.GameRules
|
||||
Exists = true;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
ISoundFormat soundFormat;
|
||||
if (loader.TryParseSound(stream, out soundFormat))
|
||||
if (loader.TryParseSound(stream, out var soundFormat))
|
||||
{
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
soundFormat.Dispose();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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)
|
||||
@@ -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,
|
||||
@@ -203,19 +201,19 @@ 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] :
|
||||
new SequenceProvider(fileSystem, modData, ts, mapSequences);
|
||||
new SequenceProvider(fileSystem, modData, tileSet, mapSequences);
|
||||
|
||||
var modelSequences = dr.ModelSequences;
|
||||
if (mapModelSequences != null)
|
||||
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
|
||||
k => k);
|
||||
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences, modelSequences);
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, sequences, modelSequences);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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-2021 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
|
||||
@@ -24,8 +24,8 @@ namespace OpenRA.GameRules
|
||||
public int[] DamageModifiers;
|
||||
public int[] InaccuracyModifiers;
|
||||
public int[] RangeModifiers;
|
||||
public int Facing;
|
||||
public Func<int> CurrentMuzzleFacing;
|
||||
public WAngle Facing;
|
||||
public Func<WAngle> CurrentMuzzleFacing;
|
||||
public WPos Source;
|
||||
public Func<WPos> CurrentSource;
|
||||
public Actor SourceActor;
|
||||
@@ -38,6 +38,8 @@ namespace OpenRA.GameRules
|
||||
public WeaponInfo Weapon;
|
||||
public int[] DamageModifiers = { };
|
||||
public WPos? Source;
|
||||
public WRot ImpactOrientation;
|
||||
public WPos ImpactPosition;
|
||||
public Actor SourceActor;
|
||||
public Target WeaponTarget;
|
||||
|
||||
@@ -45,11 +47,22 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
ImpactPosition = args.PassiveTarget;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.GuidedTarget;
|
||||
}
|
||||
|
||||
// For places that only want to update some of the fields (usually DamageModifiers)
|
||||
public WarheadArgs(WarheadArgs args)
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.WeaponTarget;
|
||||
}
|
||||
|
||||
// Default empty constructor for callers that want to initialize fields themselves
|
||||
public WarheadArgs() { }
|
||||
}
|
||||
@@ -92,6 +105,12 @@ namespace OpenRA.GameRules
|
||||
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
|
||||
public readonly BitSet<TargetableType> InvalidTargets;
|
||||
|
||||
static readonly BitSet<TargetableType> TargetTypeAir = new BitSet<TargetableType>("Air");
|
||||
|
||||
[Desc("If weapon is not directly targeting an actor and targeted position is above this altitude,",
|
||||
"the weapon will ignore terrain target types and only check TargetTypeAir for validity.")]
|
||||
public readonly WDist AirThreshold = new WDist(128);
|
||||
|
||||
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
|
||||
"If multiple entries, their number needs to match Burst - 1.")]
|
||||
public readonly int[] BurstDelays = { 5 };
|
||||
@@ -102,10 +121,10 @@ 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)
|
||||
@@ -118,8 +137,7 @@ namespace OpenRA.GameRules
|
||||
|
||||
static object LoadProjectile(MiniYaml yaml)
|
||||
{
|
||||
MiniYaml proj;
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
||||
return null;
|
||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||
FieldLoader.Load(ret, proj);
|
||||
@@ -145,7 +163,7 @@ namespace OpenRA.GameRules
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
|
||||
public bool IsValidAgainst(Target target, World world, Actor firedBy)
|
||||
public bool IsValidAgainst(in Target target, World world, Actor firedBy)
|
||||
{
|
||||
if (target.Type == TargetType.Actor)
|
||||
return IsValidAgainst(target.Actor, firedBy);
|
||||
@@ -155,6 +173,10 @@ namespace OpenRA.GameRules
|
||||
|
||||
if (target.Type == TargetType.Terrain)
|
||||
{
|
||||
var dat = world.Map.DistanceAboveTerrain(target.CenterPosition);
|
||||
if (dat > AirThreshold)
|
||||
return IsValidTarget(TargetTypeAir);
|
||||
|
||||
var cell = world.Map.CellContaining(target.CenterPosition);
|
||||
if (!world.Map.Contains(cell))
|
||||
return false;
|
||||
@@ -198,20 +220,24 @@ namespace OpenRA.GameRules
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target.</summary>
|
||||
public void Impact(Target target, WarheadArgs args)
|
||||
public void Impact(in Target target, WarheadArgs args)
|
||||
{
|
||||
var world = args.SourceActor.World;
|
||||
foreach (var warhead in Warheads)
|
||||
{
|
||||
if (warhead.Delay > 0)
|
||||
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, target, args)));
|
||||
{
|
||||
// Lambdas can't use 'in' variables, so capture a copy for later
|
||||
var delayedTarget = target;
|
||||
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, delayedTarget, args)));
|
||||
}
|
||||
else
|
||||
warhead.DoImpact(target, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target. Only use for projectile-less, special-case impacts.</summary>
|
||||
public void Impact(Target target, Actor firedBy)
|
||||
public void Impact(in Target target, Actor firedBy)
|
||||
{
|
||||
// The impact will happen immediately at target.CenterPosition.
|
||||
var args = new WarheadArgs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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-2021 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,7 @@ namespace OpenRA.Graphics
|
||||
public bool IsDecoration { get; set; }
|
||||
|
||||
readonly SequenceProvider sequenceProvider;
|
||||
readonly Func<int> facingFunc;
|
||||
readonly Func<WAngle> facingFunc;
|
||||
readonly Func<bool> paused;
|
||||
|
||||
int frame;
|
||||
@@ -33,15 +33,15 @@ namespace OpenRA.Graphics
|
||||
Action tickFunc = () => { };
|
||||
|
||||
public Animation(World world, string name)
|
||||
: this(world, name, () => 0) { }
|
||||
: this(world, name, () => WAngle.Zero) { }
|
||||
|
||||
public Animation(World world, string name, Func<int> facingFunc)
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc)
|
||||
: this(world, name, facingFunc, null) { }
|
||||
|
||||
public Animation(World world, string name, Func<bool> paused)
|
||||
: this(world, name, () => 0, paused) { }
|
||||
: this(world, name, () => WAngle.Zero, paused) { }
|
||||
|
||||
public Animation(World world, string name, Func<int> facingFunc, Func<bool> paused)
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||
{
|
||||
sequenceProvider = world.Map.Rules.Sequences;
|
||||
Name = name.ToLowerInvariant();
|
||||
@@ -49,28 +49,32 @@ 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 Sprite Image => CurrentSequence.GetSprite(CurrentFrame, facingFunc());
|
||||
|
||||
public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
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);
|
||||
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
|
||||
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
||||
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
{
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true);
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers, true);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
{
|
||||
@@ -83,8 +87,9 @@ namespace OpenRA.Graphics
|
||||
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 +101,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 +112,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)
|
||||
@@ -156,7 +161,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = () => { };
|
||||
if (after != null) after();
|
||||
after?.Invoke();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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, WorldRenderer wr, 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-2021 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,12 +52,12 @@ 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, Pair<Sheet, int>> cachedSheets;
|
||||
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
|
||||
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
|
||||
static Dictionary<string, Sprite[]> cachedPanelSprites;
|
||||
static Dictionary<Collection, Pair<Sheet, int>> cachedCollectionSheets;
|
||||
static Dictionary<Collection, (Sheet Sheet, int)> cachedCollectionSheets;
|
||||
|
||||
static IReadOnlyFileSystem fileSystem;
|
||||
static float dpiScale = 1;
|
||||
@@ -72,12 +72,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
fileSystem = modData.DefaultFileSystem;
|
||||
collections = new Dictionary<string, Collection>();
|
||||
cachedSheets = new Dictionary<string, Pair<Sheet, int>>();
|
||||
cachedSheets = new Dictionary<string, (Sheet, int)>();
|
||||
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
|
||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||
cachedCollectionSheets = new Dictionary<Collection, Pair<Sheet, int>>();
|
||||
|
||||
Collections = new ReadOnlyDictionary<string, Collection>(collections);
|
||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||
|
||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
@@ -91,7 +89,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
if (cachedSheets != null)
|
||||
foreach (var sheet in cachedSheets.Values)
|
||||
sheet.First.Dispose();
|
||||
sheet.Sheet.Dispose();
|
||||
|
||||
collections = null;
|
||||
cachedSheets = null;
|
||||
@@ -108,12 +106,10 @@ namespace OpenRA.Graphics
|
||||
collections.Add(name, FieldLoader.Load<Collection>(yaml));
|
||||
}
|
||||
|
||||
static Pair<Sheet, int> SheetForCollection(Collection c)
|
||||
static (Sheet Sheet, int Density) SheetForCollection(Collection c)
|
||||
{
|
||||
Pair<Sheet, int> sheetDensity;
|
||||
|
||||
// Outer cache avoids recalculating image names
|
||||
if (!cachedCollectionSheets.TryGetValue(c, out sheetDensity))
|
||||
if (!cachedCollectionSheets.TryGetValue(c, out (Sheet, int) sheetDensity))
|
||||
{
|
||||
var image = c.Image;
|
||||
var density = 1;
|
||||
@@ -137,7 +133,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||
|
||||
sheetDensity = Pair.New(sheet, density);
|
||||
sheetDensity = (sheet, density);
|
||||
cachedSheets.Add(image, sheetDensity);
|
||||
}
|
||||
|
||||
@@ -153,20 +149,16 @@ namespace OpenRA.Graphics
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
Dictionary<string, Sprite> cachedCollection;
|
||||
Sprite sprite;
|
||||
if (cachedSprites.TryGetValue(collectionName, out cachedCollection) && cachedCollection.TryGetValue(imageName, out sprite))
|
||||
if (cachedSprites.TryGetValue(collectionName, out var cachedCollection) && cachedCollection.TryGetValue(imageName, out var sprite))
|
||||
return sprite;
|
||||
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Rectangle mi;
|
||||
if (!collection.Regions.TryGetValue(imageName, out mi))
|
||||
if (!collection.Regions.TryGetValue(imageName, out var mi))
|
||||
return null;
|
||||
|
||||
// Cache the sprite
|
||||
@@ -177,7 +169,7 @@ namespace OpenRA.Graphics
|
||||
cachedSprites.Add(collectionName, cachedCollection);
|
||||
}
|
||||
|
||||
var image = new Sprite(sheetDensity.First, sheetDensity.Second * mi, TextureChannel.RGBA, 1f / sheetDensity.Second);
|
||||
var image = new Sprite(sheetDensity.Sheet, sheetDensity.Density * mi, TextureChannel.RGBA, 1f / sheetDensity.Density);
|
||||
cachedCollection.Add(imageName, image);
|
||||
|
||||
return image;
|
||||
@@ -189,12 +181,10 @@ namespace OpenRA.Graphics
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
Sprite[] cachedSprites;
|
||||
if (cachedPanelSprites.TryGetValue(collectionName, out cachedSprites))
|
||||
if (cachedPanelSprites.TryGetValue(collectionName, out var cachedSprites))
|
||||
return cachedSprites;
|
||||
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
@@ -214,20 +204,20 @@ namespace OpenRA.Graphics
|
||||
var pr = collection.PanelRegion;
|
||||
var ps = collection.PanelSides;
|
||||
|
||||
var sides = new[]
|
||||
var sides = new (PanelSides PanelSides, Rectangle Bounds)[]
|
||||
{
|
||||
Pair.New(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
|
||||
Pair.New(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
|
||||
Pair.New(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
|
||||
Pair.New(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
|
||||
Pair.New(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
|
||||
Pair.New(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
|
||||
Pair.New(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
|
||||
Pair.New(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
|
||||
Pair.New(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
||||
(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
|
||||
(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
|
||||
(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
|
||||
(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
|
||||
(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
|
||||
(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
|
||||
(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
|
||||
(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
|
||||
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
||||
};
|
||||
|
||||
sprites = sides.Select(x => ps.HasSide(x.First) ? new Sprite(sheetDensity.First, sheetDensity.Second * x.Second, TextureChannel.RGBA, 1f / sheetDensity.Second) : null)
|
||||
sprites = sides.Select(x => ps.HasSide(x.PanelSides) ? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density) : null)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
@@ -256,8 +246,7 @@ namespace OpenRA.Graphics
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return new Size(0, 0);
|
||||
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return new Size(0, 0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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
|
||||
@@ -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,34 +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 bytes = BitConverter.GetBytes(palette[frame.Data[j * width + i]]);
|
||||
var c = palette[frame.Data[j * width + i]];
|
||||
var k = 4 * (j * width + i);
|
||||
|
||||
// Convert RGBA to BGRA
|
||||
data[k] = bytes[2];
|
||||
data[k + 1] = bytes[1];
|
||||
data[k + 2] = bytes[0];
|
||||
data[k + 3] = bytes[3];
|
||||
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-2021 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-2021 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,8 +31,10 @@ namespace OpenRA.Graphics
|
||||
Palette = palette;
|
||||
Name = name;
|
||||
|
||||
Frames = cache[cursorSrc].Skip(Start).ToArray();
|
||||
|
||||
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
|
||||
Length = Frames.Length - Start;
|
||||
Length = Frames.Length;
|
||||
else if (d.ContainsKey("Length"))
|
||||
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
|
||||
else if (d.ContainsKey("End"))
|
||||
@@ -40,22 +42,17 @@ namespace OpenRA.Graphics
|
||||
else
|
||||
Length = 1;
|
||||
|
||||
Frames = cache[cursorSrc]
|
||||
.Skip(Start)
|
||||
.Take(Length)
|
||||
.ToArray();
|
||||
Frames = Frames.Take(Length).ToArray();
|
||||
|
||||
if (d.ContainsKey("X"))
|
||||
{
|
||||
int x;
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out x);
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
|
||||
Hotspot = Hotspot.WithX(x);
|
||||
}
|
||||
|
||||
if (d.ContainsKey("Y"))
|
||||
{
|
||||
int y;
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out y);
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
|
||||
Hotspot = Hotspot.WithY(y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,75 +18,93 @@ namespace OpenRA.Graphics
|
||||
public sealed class HardwarePalette : IDisposable
|
||||
{
|
||||
public ITexture Texture { get; private set; }
|
||||
public ITexture ColorShifts { get; private set; }
|
||||
|
||||
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];
|
||||
float[] colorShiftBuffer = new float[0];
|
||||
|
||||
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)
|
||||
{
|
||||
MutablePalette mutable;
|
||||
if (modifiablePalettes.TryGetValue(name, out mutable))
|
||||
if (mutablePalettes.TryGetValue(name, out var mutable))
|
||||
return mutable.AsReadOnly();
|
||||
ImmutablePalette immutable;
|
||||
if (palettes.TryGetValue(name, out immutable))
|
||||
if (palettes.TryGetValue(name, out var immutable))
|
||||
return immutable;
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
}
|
||||
|
||||
public int GetPaletteIndex(string name)
|
||||
{
|
||||
int ret;
|
||||
if (!indices.TryGetValue(name, out ret))
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
if (!indices.TryGetValue(name, out var ret))
|
||||
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();
|
||||
@@ -100,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;
|
||||
@@ -130,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-2021 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();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,21 +10,20 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct ModelAnimation
|
||||
public readonly struct ModelAnimation
|
||||
{
|
||||
public readonly IModel Model;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<IEnumerable<WRot>> RotationFunc;
|
||||
public readonly Func<WRot> RotationFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<uint> FrameFunc;
|
||||
public readonly bool ShowShadow;
|
||||
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<IEnumerable<WRot>> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<WRot> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
{
|
||||
Model = model;
|
||||
OffsetFunc = offset;
|
||||
@@ -47,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-2021 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,13 +42,14 @@ 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;
|
||||
|
||||
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
|
||||
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
|
||||
readonly List<Pair<Sheet, Action>> doRender = new List<Pair<Sheet, Action>>();
|
||||
readonly List<(Sheet Sheet, Action Func)> doRender = new List<(Sheet, Action)>();
|
||||
|
||||
SheetBuilder sheetBuilderForFrame;
|
||||
bool isInFrame;
|
||||
@@ -79,8 +80,8 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
public ModelRenderProxy RenderAsync(
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, WRot camera, float scale,
|
||||
float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
|
||||
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());
|
||||
@@ -114,8 +118,7 @@ namespace OpenRA.Graphics
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var worldTransform = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
var worldTransform = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
|
||||
worldTransform = Util.MatrixMultiply(scaleTransform, worldTransform);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
@@ -161,10 +164,8 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// Shadows are rendered at twice the resolution to reduce artifacts
|
||||
Size spriteSize, shadowSpriteSize;
|
||||
int2 spriteOffset, shadowSpriteOffset;
|
||||
CalculateSpriteGeometry(tl, br, 1, out spriteSize, out spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out shadowSpriteSize, out shadowSpriteOffset);
|
||||
CalculateSpriteGeometry(tl, br, 1, out var spriteSize, out var spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out var shadowSpriteSize, out var shadowSpriteOffset);
|
||||
|
||||
if (sheetBuilderForFrame == null)
|
||||
sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
|
||||
@@ -181,7 +182,7 @@ namespace OpenRA.Graphics
|
||||
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
|
||||
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
|
||||
|
||||
doRender.Add(Pair.New<Sheet, Action>(sprite.Sheet, () =>
|
||||
doRender.Add((sprite.Sheet, () =>
|
||||
{
|
||||
foreach (var m in models)
|
||||
{
|
||||
@@ -189,8 +190,7 @@ namespace OpenRA.Graphics
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var rotations = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
var rotations = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
|
||||
var worldTransform = Util.MatrixMultiply(scaleTransform, rotations);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
@@ -209,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));
|
||||
@@ -326,16 +326,16 @@ namespace OpenRA.Graphics
|
||||
foreach (var v in doRender)
|
||||
{
|
||||
// Change sheet
|
||||
if (v.First != currentSheet)
|
||||
if (v.Sheet != currentSheet)
|
||||
{
|
||||
if (fbo != null)
|
||||
DisableFrameBuffer(fbo);
|
||||
|
||||
currentSheet = v.First;
|
||||
currentSheet = v.Sheet;
|
||||
fbo = EnableFrameBuffer(currentSheet);
|
||||
}
|
||||
|
||||
v.Second();
|
||||
v.Func();
|
||||
}
|
||||
|
||||
if (fbo != null)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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
|
||||
@@ -44,7 +44,8 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
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-2021 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-2021 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,9 +15,18 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public enum GLProfile
|
||||
{
|
||||
Automatic,
|
||||
ANGLE,
|
||||
Modern,
|
||||
Embedded,
|
||||
Legacy
|
||||
}
|
||||
|
||||
public interface IPlatform
|
||||
{
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay);
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
|
||||
ISoundEngine CreateSound(string device);
|
||||
IFont CreateFont(byte[] data);
|
||||
}
|
||||
@@ -32,7 +41,10 @@ namespace OpenRA
|
||||
Subtractive,
|
||||
Multiply,
|
||||
Multiplicative,
|
||||
DoubleMultiplicative
|
||||
DoubleMultiplicative,
|
||||
LowAdditive,
|
||||
Screen,
|
||||
Translucent
|
||||
}
|
||||
|
||||
public interface IPlatformWindow : IDisposable
|
||||
@@ -46,6 +58,8 @@ namespace OpenRA
|
||||
Size SurfaceSize { get; }
|
||||
int DisplayCount { get; }
|
||||
int CurrentDisplay { get; }
|
||||
bool HasInputFocus { get; }
|
||||
bool IsSuspended { get; }
|
||||
|
||||
event Action<float, float, float, float> OnWindowScaleChanged;
|
||||
|
||||
@@ -58,8 +72,13 @@ 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);
|
||||
|
||||
GLProfile GLProfile { get; }
|
||||
|
||||
GLProfile[] SupportedGLProfiles { get; }
|
||||
}
|
||||
|
||||
public interface IGraphicsContext : IDisposable
|
||||
@@ -86,8 +105,7 @@ namespace OpenRA
|
||||
{
|
||||
void Bind();
|
||||
void SetData(T[] vertices, int length);
|
||||
void SetData(T[] vertices, int start, int length);
|
||||
void SetData(IntPtr data, int start, int length);
|
||||
void SetData(T[] vertices, int offset, int start, int length);
|
||||
}
|
||||
|
||||
public interface IShader
|
||||
@@ -108,6 +126,7 @@ namespace OpenRA
|
||||
{
|
||||
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; }
|
||||
@@ -129,7 +148,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-2021 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,44 +18,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) => Pair.New(baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
|
||||
.ToDictionary(u => u.First, u => u.Second);
|
||||
this.remapIndices = remapIndices;
|
||||
this.hue = hue;
|
||||
this.saturation = saturation;
|
||||
}
|
||||
|
||||
public Color GetRemappedColor(Color original, int index)
|
||||
{
|
||||
Color c;
|
||||
return remapColors.TryGetValue(index, out 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-2021 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,18 +17,40 @@ 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 IPalettedRenderable : IRenderable
|
||||
{
|
||||
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
|
||||
{
|
||||
void Render(WorldRenderer wr);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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(float3 start, float3 end, float width, Color startColor, Color endColor)
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor, 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(float3 start, 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>
|
||||
@@ -80,7 +80,7 @@ namespace OpenRA.Graphics
|
||||
/// Will behave badly if the lines are parallel.
|
||||
/// Z position is the average of a and b (ignores actual intersection point if it exists)
|
||||
/// </summary>
|
||||
float3 IntersectionOf(float3 a, float3 da, float3 b, float3 db)
|
||||
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
|
||||
{
|
||||
var crossA = a.X * (a.Y + da.Y) - a.Y * (a.X + da.X);
|
||||
var crossB = b.X * (b.Y + db.Y) - b.Y * (b.X + db.X);
|
||||
@@ -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(float3 tl, 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(float3 a, float3 b, 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(float3 tl, 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(float3 a, float3 b, float3 c, float3 d, Color color)
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color, 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(float3 a, float3 b, float3 c, float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
||||
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
||||
@@ -247,10 +247,10 @@ 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(float3 xyz, Color color)
|
||||
static Vertex VertexWithColor(in float3 xyz, Color color)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
|
||||
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
|
||||
}
|
||||
|
||||
public void FillEllipse(float3 tl, float3 br, Color color, int vertices = 32)
|
||||
public void FillEllipse(in float3 tl, in float3 br, Color color, int vertices = 32, 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-2021 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,28 +22,36 @@ namespace OpenRA.Graphics
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location, float3 size)
|
||||
public void DrawSprite(Sprite s, in float3 location, in float3 scale)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location)
|
||||
public void DrawSprite(Sprite s, in float3 location, float scale = 1f)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
|
||||
public void DrawSprite(Sprite s, in float3 location, float scale, in float3 tint, float alpha)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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.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-2021 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,29 +32,31 @@ namespace OpenRA.Graphics
|
||||
int ShadowZOffset { get; }
|
||||
int[] Frames { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
float Scale { get; }
|
||||
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, int facing);
|
||||
Sprite GetShadow(int frame, int facing);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
float GetAlpha(int frame);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
Action<string> OnMissingSpriteError { get; set; }
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public class SequenceProvider : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly TileSet tileSet;
|
||||
readonly string tileSet;
|
||||
readonly Lazy<Sequences> sequences;
|
||||
readonly Lazy<SpriteCache> spriteCache;
|
||||
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
|
||||
public SpriteCache SpriteCache => spriteCache.Value;
|
||||
|
||||
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
|
||||
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, MiniYaml additionalSequences)
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
@@ -69,17 +71,17 @@ namespace OpenRA.Graphics
|
||||
|
||||
public ISpriteSequence GetSequence(string unitName, string sequenceName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
ISpriteSequence seq;
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out seq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have a sequence named `{sequenceName}`");
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images => sequences.Value.Keys;
|
||||
|
||||
public bool HasSequence(string unitName)
|
||||
{
|
||||
return sequences.Value.ContainsKey(unitName);
|
||||
@@ -87,18 +89,16 @@ namespace OpenRA.Graphics
|
||||
|
||||
public bool HasSequence(string unitName, string sequenceName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
return unitSeq.Value.ContainsKey(sequenceName);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Sequences(string unitName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
|
||||
return unitSeq.Value.Keys;
|
||||
}
|
||||
@@ -107,15 +107,15 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var items = new Dictionary<string, UnitSequences>();
|
||||
foreach (var n in nodes)
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Work around the loop closure issue in older versions of C#
|
||||
var node = n;
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var key = node.Value.ToLines(node.Key).JoinWith("|");
|
||||
|
||||
UnitSequences t;
|
||||
if (sequenceCache.TryGetValue(key, out t))
|
||||
if (sequenceCache.TryGetValue(key, out var t))
|
||||
items.Add(node.Key, t);
|
||||
else
|
||||
{
|
||||
@@ -125,7 +125,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-2021 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()
|
||||
@@ -146,8 +142,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (texture != null)
|
||||
texture.Dispose();
|
||||
texture?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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, 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;
|
||||
@@ -115,7 +113,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
|
||||
public Sprite Allocate(Size imageSize, float zRamp, float3 spriteOffset, float scale = 1f)
|
||||
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
|
||||
{
|
||||
if (imageSize.Width + p.X + margin > current.Size.Width)
|
||||
{
|
||||
@@ -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-2021 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,13 +23,12 @@ 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)
|
||||
: this(sheet, bounds, 0, float2.Zero, channel, BlendMode.Alpha, scale) { }
|
||||
|
||||
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
|
||||
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, in float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
|
||||
{
|
||||
Sheet = sheet;
|
||||
Bounds = bounds;
|
||||
@@ -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-2021 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;
|
||||
|
||||
@@ -21,10 +20,9 @@ namespace OpenRA.Graphics
|
||||
public int TopOffset { get; private set; }
|
||||
readonly int size;
|
||||
readonly SheetBuilder builder;
|
||||
readonly Func<string, float> lineWidth;
|
||||
readonly IFont font;
|
||||
readonly Cache<Pair<char, Color>, GlyphInfo> glyphs;
|
||||
readonly Cache<Tuple<char, Color, int>, Sprite> contrastGlyphs;
|
||||
readonly Cache<char, GlyphInfo> glyphs;
|
||||
readonly Cache<(char C, int Radius), Sprite> contrastGlyphs;
|
||||
readonly Cache<int, float[]> dilationElements;
|
||||
|
||||
float deviceScale;
|
||||
@@ -32,24 +30,23 @@ 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;
|
||||
this.builder = builder;
|
||||
|
||||
font = Game.Renderer.CreateFont(data);
|
||||
|
||||
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer);
|
||||
contrastGlyphs = new Cache<Tuple<char, Color, int>, Sprite>(CreateContrastGlyph);
|
||||
glyphs = new Cache<char, GlyphInfo>(CreateGlyph);
|
||||
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
|
||||
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
|
||||
|
||||
// PERF: Cache these delegates for Measure calls.
|
||||
Func<char, float> characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance;
|
||||
lineWidth = line => line.Sum(characterWidth) / deviceScale;
|
||||
|
||||
// Pre-cache small font sizes so glyphs are immediately available when we need them
|
||||
if (size <= 24)
|
||||
PrecacheColor(Color.White, name);
|
||||
using (new PerfTimer($"Precache {name} {size}px"))
|
||||
for (var n = (char)0x20; n < (char)0x7f; n++)
|
||||
if (glyphs[n] == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
TopOffset = size - ascender;
|
||||
}
|
||||
@@ -61,14 +58,6 @@ namespace OpenRA.Graphics
|
||||
contrastGlyphs.Clear();
|
||||
}
|
||||
|
||||
void PrecacheColor(Color c, string name)
|
||||
{
|
||||
using (new PerfTimer("PrecacheColor {0} {1}px {2}".F(name, size, c)))
|
||||
for (var n = (char)0x20; n < (char)0x7f; n++)
|
||||
if (glyphs[Pair.New(n, c)] == null)
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
void DrawTextContrast(string text, float2 location, Color contrastColor, int contrastOffset)
|
||||
{
|
||||
// Offset from the baseline position to the top-left of the glyph for rendering
|
||||
@@ -78,6 +67,7 @@ namespace OpenRA.Graphics
|
||||
var screenContrast = (int)(contrastOffset * deviceScale);
|
||||
var screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
|
||||
var contrastVector = new float2(screenContrast, screenContrast);
|
||||
var tint = new float3(contrastColor.R / 255f, contrastColor.G / 255f, contrastColor.B / 255f);
|
||||
foreach (var s in text)
|
||||
{
|
||||
if (s == '\n')
|
||||
@@ -87,15 +77,16 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[Pair.New(s, Color.Black)];
|
||||
var g = glyphs[s];
|
||||
|
||||
// Convert screen coordinates back to UI coordinates for drawing
|
||||
if (g.Sprite != null)
|
||||
{
|
||||
var contrastSprite = contrastGlyphs[Tuple.Create(s, contrastColor, screenContrast)];
|
||||
var contrastSprite = contrastGlyphs[(s, screenContrast)];
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(contrastSprite,
|
||||
(screen + g.Offset - contrastVector) / deviceScale,
|
||||
contrastSprite.Size / deviceScale);
|
||||
1f / deviceScale,
|
||||
tint, 1f);
|
||||
}
|
||||
|
||||
screen += new int2((int)(g.Advance + 0.5f), 0);
|
||||
@@ -109,6 +100,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
// Calculate positions in screen pixel coordinates
|
||||
var screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
|
||||
var tint = new float3(c.R / 255f, c.G / 255f, c.B / 255f);
|
||||
foreach (var s in text)
|
||||
{
|
||||
if (s == '\n')
|
||||
@@ -118,13 +110,14 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[Pair.New(s, c)];
|
||||
var g = glyphs[s];
|
||||
|
||||
// Convert screen coordinates back to UI coordinates for drawing
|
||||
if (g.Sprite != null)
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
|
||||
(screen + g.Offset).ToFloat2() / deviceScale,
|
||||
g.Sprite.Size / deviceScale);
|
||||
1f / deviceScale,
|
||||
tint, 1f);
|
||||
|
||||
screen += new int2((int)(g.Advance + 0.5f), 0);
|
||||
}
|
||||
@@ -144,6 +137,7 @@ namespace OpenRA.Graphics
|
||||
var offset = new float2(0, size);
|
||||
var cosa = (float)Math.Cos(-angle);
|
||||
var sina = (float)Math.Sin(-angle);
|
||||
var tint = new float3(c.R / 255f, c.G / 255f, c.B / 255f);
|
||||
|
||||
var p = offset;
|
||||
foreach (var s in text)
|
||||
@@ -155,7 +149,7 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[Pair.New(s, c)];
|
||||
var g = glyphs[s];
|
||||
if (g.Sprite != null)
|
||||
{
|
||||
var tl = new float2(
|
||||
@@ -176,7 +170,8 @@ namespace OpenRA.Graphics
|
||||
ra + screenOffset,
|
||||
rb + screenOffset,
|
||||
rc + screenOffset,
|
||||
rd + screenOffset);
|
||||
rd + screenOffset,
|
||||
tint, 1f);
|
||||
}
|
||||
|
||||
p += new float2(g.Advance / deviceScale, 0);
|
||||
@@ -237,14 +232,31 @@ namespace OpenRA.Graphics
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return new int2(0, size);
|
||||
|
||||
var lines = text.Split('\n');
|
||||
return new int2((int)Math.Ceiling(lines.Max(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);
|
||||
}
|
||||
|
||||
GlyphInfo CreateGlyph(Pair<char, Color> c)
|
||||
float LineWidth(ReadOnlySpan<char> line)
|
||||
{
|
||||
var glyph = font.CreateGlyph(c.First, size, deviceScale);
|
||||
var result = 0f;
|
||||
foreach (var c in line)
|
||||
result += glyphs[c].Advance;
|
||||
|
||||
return result / deviceScale;
|
||||
}
|
||||
|
||||
GlyphInfo CreateGlyph(char c)
|
||||
{
|
||||
var glyph = font.CreateGlyph(c, size, deviceScale);
|
||||
if (glyph.Data == null)
|
||||
{
|
||||
return new GlyphInfo
|
||||
@@ -274,12 +286,10 @@ namespace OpenRA.Graphics
|
||||
if (p != 0)
|
||||
{
|
||||
var q = destStride * (j + s.Bounds.Top) + 4 * (i + s.Bounds.Left);
|
||||
var pmc = Util.PremultiplyAlpha(Color.FromArgb(p, c.Second));
|
||||
|
||||
dest[q] = pmc.B;
|
||||
dest[q + 1] = pmc.G;
|
||||
dest[q + 2] = pmc.R;
|
||||
dest[q + 3] = pmc.A;
|
||||
dest[q] = p;
|
||||
dest[q + 1] = p;
|
||||
dest[q + 2] = p;
|
||||
dest[q + 3] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,16 +357,12 @@ namespace OpenRA.Graphics
|
||||
return elem;
|
||||
}
|
||||
|
||||
Sprite CreateContrastGlyph(Tuple<char, Color, int> c)
|
||||
Sprite CreateContrastGlyph((char C, int Radius) c)
|
||||
{
|
||||
// Source glyph color doesn't matter, so use black
|
||||
var glyph = glyphs[Pair.New(c.Item1, Color.Black)];
|
||||
var color = c.Item2;
|
||||
var r = c.Item3;
|
||||
var glyph = glyphs[c.C];
|
||||
var r = c.Radius;
|
||||
|
||||
var size = new Size(glyph.Sprite.Bounds.Width + 2 * r, glyph.Sprite.Bounds.Height + 2 * r);
|
||||
|
||||
var s = builder.Allocate(size);
|
||||
var s = builder.Allocate(new Size(glyph.Sprite.Bounds.Width + 2 * r, glyph.Sprite.Bounds.Height + 2 * r));
|
||||
var dest = s.Sheet.GetData();
|
||||
var destStride = s.Sheet.Size.Width * 4;
|
||||
|
||||
@@ -398,11 +404,10 @@ namespace OpenRA.Graphics
|
||||
if (alpha > 0)
|
||||
{
|
||||
var q = destStride * (j + s.Bounds.Top) + 4 * (i + s.Bounds.Left);
|
||||
var pmc = Util.PremultiplyAlpha(Color.FromArgb(alpha, color));
|
||||
dest[q] = pmc.B;
|
||||
dest[q + 1] = pmc.G;
|
||||
dest[q + 2] = pmc.R;
|
||||
dest[q + 3] = pmc.A;
|
||||
dest[q] = alpha;
|
||||
dest[q + 1] = alpha;
|
||||
dest[q + 2] = alpha;
|
||||
dest[q + 3] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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;
|
||||
@@ -76,8 +98,7 @@ namespace OpenRA.Graphics
|
||||
var allSprites = sprites.GetOrAdd(filename);
|
||||
var sprite = allSprites.FirstOrDefault();
|
||||
|
||||
ISpriteFrame[] unloaded;
|
||||
if (!unloadedFrames.TryGetValue(filename, out unloaded))
|
||||
if (!unloadedFrames.TryGetValue(filename, out var unloaded))
|
||||
unloaded = null;
|
||||
|
||||
// This is the first time that the file has been requested
|
||||
@@ -85,8 +106,7 @@ namespace OpenRA.Graphics
|
||||
// the loaded cache (initially empty)
|
||||
if (sprite == null)
|
||||
{
|
||||
TypeDictionary fileMetadata = null;
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata);
|
||||
unloadedFrames[filename] = unloaded;
|
||||
metadata[filename] = fileMetadata;
|
||||
|
||||
@@ -105,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;
|
||||
}
|
||||
}
|
||||
@@ -125,8 +145,7 @@ namespace OpenRA.Graphics
|
||||
/// </summary>
|
||||
public TypeDictionary FrameMetadata(string filename)
|
||||
{
|
||||
TypeDictionary fileMetadata;
|
||||
if (!metadata.TryGetValue(filename, out fileMetadata))
|
||||
if (!metadata.TryGetValue(filename, out var fileMetadata))
|
||||
{
|
||||
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
metadata[filename] = fileMetadata;
|
||||
@@ -142,11 +161,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
public FrameCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
{
|
||||
TypeDictionary metadata;
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out metadata));
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _));
|
||||
}
|
||||
|
||||
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
|
||||
public ISpriteFrame[] this[string filename] => frames[filename];
|
||||
}
|
||||
|
||||
public static class FrameLoader
|
||||
@@ -155,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!");
|
||||
|
||||
@@ -163,13 +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)
|
||||
{
|
||||
ISpriteFrame[] frames;
|
||||
metadata = null;
|
||||
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSprite(stream, out 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-2021 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 SpriteRenderable : IRenderable, IFinalizedRenderable
|
||||
public class SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable
|
||||
{
|
||||
public static readonly IEnumerable<IRenderable> None = new IRenderable[0];
|
||||
|
||||
@@ -24,9 +24,12 @@ namespace OpenRA.Graphics
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
readonly float scale;
|
||||
readonly float3 tint;
|
||||
readonly TintModifiers tintModifiers;
|
||||
readonly float alpha;
|
||||
readonly bool isDecoration;
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool 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 = sprite;
|
||||
this.pos = pos;
|
||||
@@ -34,32 +37,63 @@ namespace OpenRA.Graphics
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
this.scale = scale;
|
||||
this.tint = tint;
|
||||
this.isDecoration = isDecoration;
|
||||
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 WPos Pos => pos + offset;
|
||||
public WVec Offset => offset;
|
||||
public PaletteReference Palette => palette;
|
||||
public int ZOffset => zOffset;
|
||||
public bool IsDecoration => isDecoration;
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, isDecoration); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, isDecoration); }
|
||||
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, isDecoration); }
|
||||
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, true); }
|
||||
public 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); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, alpha, tint, tintModifiers, isDecoration); }
|
||||
public IRenderable OffsetBy(in WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration); }
|
||||
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, true); }
|
||||
|
||||
public IModifyableRenderable WithAlpha(float newAlpha)
|
||||
{
|
||||
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newAlpha, tint, tintModifiers, isDecoration);
|
||||
}
|
||||
|
||||
public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers)
|
||||
{
|
||||
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, newTint, newTintModifiers, isDecoration);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
|
||||
var wsr = Game.Renderer.WorldSpriteRenderer;
|
||||
var t = alpha * tint;
|
||||
if (wr.TerrainLighting != null && (tintModifiers & TintModifiers.IgnoreWorldTint) == 0)
|
||||
t *= wr.TerrainLighting.TintAt(pos);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,17 +10,22 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class SpriteRenderer : Renderer.IBatchRenderer
|
||||
{
|
||||
public const int SheetCount = 8;
|
||||
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
|
||||
readonly Vertex[] vertices;
|
||||
readonly Sheet[] sheets = new Sheet[7];
|
||||
readonly Sheet[] sheets = new Sheet[SheetCount];
|
||||
|
||||
BlendMode currentBlend = BlendMode.Alpha;
|
||||
int nv = 0;
|
||||
@@ -39,7 +44,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
for (var i = 0; i < ns; i++)
|
||||
{
|
||||
shader.SetTexture("Texture{0}".F(i), sheets[i].GetTexture());
|
||||
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
|
||||
sheets[i] = null;
|
||||
}
|
||||
|
||||
@@ -77,100 +82,165 @@ 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, float3 location, float paletteTextureIndex, 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)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones, 1f);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location, PaletteReference pal)
|
||||
{
|
||||
DrawSprite(s, location, pal.TextureIndex, s.Size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location, PaletteReference pal, float3 size)
|
||||
{
|
||||
DrawSprite(s, location, pal.TextureIndex, size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, nv);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones, 1f);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode)
|
||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f)
|
||||
{
|
||||
shader.SetTexture("Texture0", sheet.GetTexture());
|
||||
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale);
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha)
|
||||
{
|
||||
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha);
|
||||
}
|
||||
|
||||
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, paletteTextureIndex, tint, alpha, nv);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, IEnumerable<Sheet> sheets, BlendMode blendMode)
|
||||
{
|
||||
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-2021 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,7 +15,7 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct TargetLineRenderable : IRenderable, IFinalizedRenderable
|
||||
public class TargetLineRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly IEnumerable<WPos> waypoints;
|
||||
readonly 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 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 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);
|
||||
}
|
||||
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,13 +18,16 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class TerrainSpriteLayer : IDisposable
|
||||
{
|
||||
public readonly Sheet Sheet;
|
||||
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
public readonly BlendMode BlendMode;
|
||||
|
||||
readonly Sheet[] sheets;
|
||||
readonly Sprite emptySprite;
|
||||
|
||||
readonly IVertexBuffer<Vertex> vertexBuffer;
|
||||
readonly Vertex[] vertices;
|
||||
readonly bool[] ignoreTint;
|
||||
readonly HashSet<int> dirtyRows = new HashSet<int>();
|
||||
readonly int rowStride;
|
||||
readonly bool restrictToBounds;
|
||||
@@ -32,67 +35,161 @@ 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;
|
||||
|
||||
if (wr.TerrainLighting != null)
|
||||
{
|
||||
ignoreTint = new bool[rowStride * map.MapSize.Y];
|
||||
wr.TerrainLighting.CellChanged += UpdateTint;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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++)
|
||||
dirtyRows.Add(row);
|
||||
}
|
||||
|
||||
public void Update(CPos cell, Sprite sprite)
|
||||
public void Clear(CPos cell)
|
||||
{
|
||||
var xyz = sprite == null ? float3.Zero :
|
||||
worldRenderer.Screen3DPosition(map.CenterOfCell(cell)) + sprite.Offset - 0.5f * sprite.Size;
|
||||
|
||||
Update(cell.ToMPos(map.Grid.Type), sprite, xyz);
|
||||
Update(cell, null, null, 1f, 1f, true);
|
||||
}
|
||||
|
||||
public void Update(MPos uv, Sprite sprite, float3 pos)
|
||||
public void Update(CPos cell, ISpriteSequence sequence, PaletteReference palette, int frame)
|
||||
{
|
||||
Update(cell, sequence.GetSprite(frame), palette, sequence.Scale, sequence.GetAlpha(frame), sequence.IgnoreWorldTint);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (sprite.Sheet != Sheet)
|
||||
throw new InvalidDataException("Attempted to add sprite from a different sheet");
|
||||
var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset);
|
||||
xyz = worldRenderer.Screen3DPosition(cellOrigin) + scale * (sprite.Offset - 0.5f * sprite.Size);
|
||||
}
|
||||
|
||||
Update(cell.ToMPos(map.Grid.Type), sprite, palette, xyz, scale, alpha, ignoreTint);
|
||||
}
|
||||
|
||||
void UpdateTint(MPos uv)
|
||||
{
|
||||
var offset = rowStride * uv.V + 6 * uv.U;
|
||||
if (ignoreTint[offset])
|
||||
{
|
||||
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, v.P, v.C, v.A * float3.Ones, v.A);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow the terrain tint to vary linearly across the cell to smooth out the staircase effect
|
||||
// This is done by sampling the lighting the corners of the sprite, even though those pixels are
|
||||
// transparent for isometric tiles
|
||||
var tl = worldRenderer.TerrainLighting;
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map));
|
||||
var step = map.Grid.Type == MapGridType.RectangularIsometric ? 724 : 512;
|
||||
var weights = new[]
|
||||
{
|
||||
tl.TintAt(pos + new WVec(-step, -step, 0)),
|
||||
tl.TintAt(pos + new WVec(step, -step, 0)),
|
||||
tl.TintAt(pos + new WVec(step, step, 0)),
|
||||
tl.TintAt(pos + new WVec(-step, step, 0))
|
||||
};
|
||||
|
||||
// Apply tint directly to the underlying vertices
|
||||
// This saves us from having to re-query the sprite information, which has not changed
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var v = vertices[offset + i];
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * weights[CornerVertexMap[i]], v.A);
|
||||
}
|
||||
|
||||
dirtyRows.Add(uv.V);
|
||||
}
|
||||
|
||||
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.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);
|
||||
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)
|
||||
{
|
||||
this.ignoreTint[offset] = ignoreTint;
|
||||
UpdateTint(uv);
|
||||
}
|
||||
|
||||
dirtyRows.Add(uv.V);
|
||||
}
|
||||
@@ -114,20 +211,12 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
|
||||
var rowOffset = rowStride * row;
|
||||
|
||||
unsafe
|
||||
{
|
||||
// The compiler / language spec won't let us calculate a pointer to
|
||||
// an offset inside a generic array T[], and so we are forced to
|
||||
// calculate the start-of-row pointer here to pass in to SetData.
|
||||
fixed (Vertex* vPtr = &vertices[0])
|
||||
vertexBuffer.SetData((IntPtr)(vPtr + rowOffset), rowOffset, rowStride);
|
||||
}
|
||||
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
|
||||
}
|
||||
|
||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
|
||||
PrimitiveType.TriangleList, Sheet, BlendMode);
|
||||
PrimitiveType.TriangleList, sheets, BlendMode);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
}
|
||||
@@ -135,6 +224,9 @@ namespace OpenRA.Graphics
|
||||
public void Dispose()
|
||||
{
|
||||
worldRenderer.PaletteInvalidated -= UpdatePaletteIndices;
|
||||
if (worldRenderer.TerrainLighting != null)
|
||||
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
|
||||
|
||||
vertexBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,168 +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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var allFrames = frameCache[i];
|
||||
var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length;
|
||||
var indices = t.Value.Frames != null ? t.Value.Frames : Enumerable.Range(0, frameCount);
|
||||
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 InvalidDataException("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));
|
||||
|
||||
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 Sprite TileSprite(TerrainTile r, int? variant = null)
|
||||
{
|
||||
TheaterTemplate template;
|
||||
if (!templates.TryGetValue(r.Type, out template))
|
||||
return missingTile;
|
||||
|
||||
if (r.Index >= template.Stride)
|
||||
return missingTile;
|
||||
|
||||
var start = template.Variants > 1 ? variant.HasValue ? variant.Value : random.Next(template.Variants) : 0;
|
||||
return template.Sprites[start * template.Stride + r.Index];
|
||||
}
|
||||
|
||||
public Rectangle TemplateBounds(TerrainTemplateInfo template, Size tileSize, MapGridType mapGrid)
|
||||
{
|
||||
Rectangle? templateRect = null;
|
||||
|
||||
var i = 0;
|
||||
for (var y = 0; y < template.Size.Y; y++)
|
||||
{
|
||||
for (var x = 0; x < template.Size.X; x++)
|
||||
{
|
||||
var tile = new TerrainTile(template.Id, (byte)(i++));
|
||||
var tileInfo = tileset.GetTileInfo(tile);
|
||||
|
||||
// Empty tile
|
||||
if (tileInfo == null)
|
||||
continue;
|
||||
|
||||
var sprite = TileSprite(tile);
|
||||
var u = mapGrid == MapGridType.Rectangular ? x : (x - y) / 2f;
|
||||
var v = mapGrid == MapGridType.Rectangular ? y : (x + y) / 2f;
|
||||
|
||||
var tl = new float2(u * tileSize.Width, (v - 0.5f * tileInfo.Height) * tileSize.Height) - 0.5f * sprite.Size;
|
||||
var rect = new Rectangle((int)(tl.X + sprite.Offset.X), (int)(tl.Y + sprite.Offset.Y), (int)sprite.Size.X, (int)sprite.Size.Y);
|
||||
templateRect = templateRect.HasValue ? Rectangle.Union(templateRect.Value, rect) : rect;
|
||||
}
|
||||
}
|
||||
|
||||
return templateRect.HasValue ? templateRect.Value : Rectangle.Empty;
|
||||
}
|
||||
|
||||
public Sheet Sheet { get { return sheetBuilder.Current; } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,9 @@ namespace OpenRA.Graphics
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
readonly float scale;
|
||||
readonly float alpha;
|
||||
|
||||
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)
|
||||
{
|
||||
this.sprite = sprite;
|
||||
this.effectiveWorldPos = effectiveWorldPos;
|
||||
@@ -30,25 +31,32 @@ namespace OpenRA.Graphics
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
this.scale = scale;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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); }
|
||||
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);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,26 +20,28 @@ namespace OpenRA.Graphics
|
||||
// yes, our channel order is nuts.
|
||||
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices, float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, float3 size)
|
||||
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, in float3 size, in float3 tint, float alpha)
|
||||
{
|
||||
var b = new float3(o.X + size.X, o.Y, o.Z);
|
||||
var c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
|
||||
var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
|
||||
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, nv);
|
||||
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, alpha, nv);
|
||||
}
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices, float3 a, float3 b, float3 c, float3 d, Sprite r, int2 samplers, float paletteTextureIndex, int nv)
|
||||
public static void FastCreateQuad(Vertex[] vertices,
|
||||
in float3 a, in float3 b, in float3 c, in float3 d,
|
||||
Sprite r, int2 samplers, float paletteTextureIndex,
|
||||
in float3 tint, 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;
|
||||
@@ -51,15 +53,15 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
var fAttribC = (float)attribC;
|
||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC);
|
||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, 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;
|
||||
@@ -82,12 +84,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();
|
||||
}
|
||||
}
|
||||
@@ -136,16 +160,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();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,19 +14,36 @@ using System.Runtime.InteropServices;
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex
|
||||
public readonly struct Vertex
|
||||
{
|
||||
public readonly float X, Y, Z, S, T, U, V, P, C;
|
||||
// 3d position
|
||||
public readonly float X, Y, Z;
|
||||
|
||||
public Vertex(float3 xyz, float s, float t, float u, float v, float p, float c)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c) { }
|
||||
// Primary and secondary texture coordinates or RGBA color
|
||||
public readonly float S, T, U, V;
|
||||
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c)
|
||||
// Palette and channel flags
|
||||
public readonly float P, C;
|
||||
|
||||
// Color tint
|
||||
public readonly float R, G, B, 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, 1f) { }
|
||||
|
||||
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, 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, 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; A = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
OpenRA.Game/Graphics/Video.cs
Normal file
37
OpenRA.Game/Graphics/Video.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2021 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 OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Video
|
||||
{
|
||||
public interface IVideo
|
||||
{
|
||||
ushort Frames { get; }
|
||||
byte Framerate { get; }
|
||||
ushort Width { get; }
|
||||
ushort Height { get; }
|
||||
uint[,] FrameData { get; }
|
||||
|
||||
int CurrentFrame { 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-2021 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, out IVideo video);
|
||||
}
|
||||
|
||||
public static class VideoLoader
|
||||
{
|
||||
public static IVideo GetVideo(Stream stream, IVideoLoader[] loaders)
|
||||
{
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseVideo(stream, 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-2021 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
|
||||
@@ -19,6 +19,11 @@ namespace OpenRA.Graphics
|
||||
[Flags]
|
||||
public enum ScrollDirection { None = 0, Up = 1, Left = 2, Down = 4, Right = 8 }
|
||||
|
||||
public interface INotifyViewportZoomExtentsChanged
|
||||
{
|
||||
void ViewportZoomExtentsChanged(float minZoom, float maxZoom);
|
||||
}
|
||||
|
||||
public static class ViewportExts
|
||||
{
|
||||
public static bool Includes(this ScrollDirection d, ScrollDirection s)
|
||||
@@ -46,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;
|
||||
@@ -70,10 +75,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get
|
||||
{
|
||||
return zoom;
|
||||
}
|
||||
get => zoom;
|
||||
|
||||
private set
|
||||
{
|
||||
@@ -84,12 +86,22 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public float MinZoom => minZoom;
|
||||
|
||||
public void AdjustZoom(float dz)
|
||||
{
|
||||
// Exponential ensures that equal positive and negative steps have the same effect
|
||||
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : minZoom, maxZoom);
|
||||
}
|
||||
|
||||
public void AdjustZoom(float dz, int2 center)
|
||||
{
|
||||
var oldCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
||||
AdjustZoom(dz);
|
||||
var newCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
||||
CenterLocation += oldCenter - newCenter;
|
||||
}
|
||||
|
||||
public void ToggleZoom()
|
||||
{
|
||||
// Unlocked zooms always reset to the default zoom
|
||||
@@ -237,6 +249,12 @@ namespace OpenRA.Graphics
|
||||
Zoom = minZoom;
|
||||
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);
|
||||
}
|
||||
|
||||
public CPos ViewToWorld(int2 view)
|
||||
@@ -244,7 +262,6 @@ namespace OpenRA.Graphics
|
||||
var world = worldRenderer.Viewport.ViewToWorldPx(view);
|
||||
var map = worldRenderer.World.Map;
|
||||
var candidates = CandidateMouseoverCells(world).ToList();
|
||||
var tileSet = worldRenderer.World.Map.Rules.TileSet;
|
||||
|
||||
foreach (var uv in candidates)
|
||||
{
|
||||
@@ -253,18 +270,9 @@ namespace OpenRA.Graphics
|
||||
var s = worldRenderer.ScreenPxPosition(p);
|
||||
if (Math.Abs(s.X - world.X) <= tileSize.Width && Math.Abs(s.Y - world.Y) <= tileSize.Height)
|
||||
{
|
||||
var ramp = 0;
|
||||
if (map.Contains(uv))
|
||||
{
|
||||
var ti = tileSet.GetTileInfo(map.Tiles[uv]);
|
||||
if (ti != null)
|
||||
ramp = ti.RampType;
|
||||
}
|
||||
|
||||
var corners = map.Grid.CellCorners[ramp];
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map));
|
||||
var screen = corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
|
||||
|
||||
var ramp = map.Grid.Ramps[map.Ramp.Contains(uv) ? map.Ramp[uv] : 0];
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, ramp.CenterHeightOffset);
|
||||
var screen = ramp.Corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
|
||||
if (screen.PolygonContains(world))
|
||||
return uv.ToCPos(map);
|
||||
}
|
||||
@@ -274,7 +282,7 @@ namespace OpenRA.Graphics
|
||||
// Try and find the closest cell
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
return candidates.OrderBy(uv =>
|
||||
return candidates.MinBy(uv =>
|
||||
{
|
||||
var p = map.CenterOfCell(uv.ToCPos(map.Grid.Type));
|
||||
var s = worldRenderer.ScreenPxPosition(p);
|
||||
@@ -282,7 +290,7 @@ namespace OpenRA.Graphics
|
||||
var dy = Math.Abs(s.Y - world.Y);
|
||||
|
||||
return dx * dx + dy * dy;
|
||||
}).First().ToCPos(map);
|
||||
}).ToCPos(map);
|
||||
}
|
||||
|
||||
// Something is very wrong, but lets return something that isn't completely bogus and hope the caller can recover
|
||||
@@ -306,7 +314,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public int2 ViewToWorldPx(int2 view) { return (graphicSettings.UIScale / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
|
||||
public int2 WorldToViewPx(int2 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).ToFloat2()).ToInt2(); }
|
||||
public int2 WorldToViewPx(float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
|
||||
public int2 WorldToViewPx(in float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
|
||||
|
||||
public void Center(IEnumerable<Actor> actors)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,14 +20,14 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class WorldRenderer : IDisposable
|
||||
{
|
||||
public static readonly Func<IRenderable, int> RenderableScreenZPositionComparisonKey =
|
||||
r => ZPosition(r.Pos, r.ZOffset);
|
||||
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
|
||||
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 readonly ITerrainLighting TerrainLighting;
|
||||
|
||||
public event Action PaletteInvalidated = null;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace OpenRA.Graphics
|
||||
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new List<IFinalizedRenderable>();
|
||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new List<IFinalizedRenderable>();
|
||||
|
||||
bool lastDepthPreviewEnabled;
|
||||
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
|
||||
|
||||
internal WorldRenderer(ModData modData, World world)
|
||||
{
|
||||
@@ -65,7 +65,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
palette.Initialize();
|
||||
|
||||
Theater = new Theater(world.Map.Rules.TileSet);
|
||||
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||
|
||||
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
||||
@@ -83,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))
|
||||
@@ -93,8 +99,8 @@ namespace OpenRA.Graphics
|
||||
var oldHeight = palette.Height;
|
||||
palette.AddPalette(name, pal, allowModifiers);
|
||||
|
||||
if (oldHeight != palette.Height && PaletteInvalidated != null)
|
||||
PaletteInvalidated();
|
||||
if (oldHeight != palette.Height)
|
||||
PaletteInvalidated?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,78 +113,123 @@ namespace OpenRA.Graphics
|
||||
palettes[name].Palette = pal;
|
||||
}
|
||||
|
||||
IEnumerable<IFinalizedRenderable> GenerateRenderables()
|
||||
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
|
||||
{
|
||||
var actors = onScreenActors.Append(World.WorldActor);
|
||||
if (World.RenderPlayer != null)
|
||||
actors = actors.Append(World.RenderPlayer.PlayerActor);
|
||||
palette.SetColorShift(name, hueOffset, satOffset, minHue, maxHue);
|
||||
}
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateRenderables()
|
||||
{
|
||||
foreach (var actor in onScreenActors)
|
||||
renderablesBuffer.AddRange(actor.Render(this));
|
||||
|
||||
renderablesBuffer.AddRange(World.WorldActor.Render(this));
|
||||
|
||||
if (World.RenderPlayer != null)
|
||||
renderablesBuffer.AddRange(World.RenderPlayer.PlayerActor.Render(this));
|
||||
|
||||
var worldRenderables = actors.SelectMany(a => a.Render(this));
|
||||
if (World.OrderGenerator != null)
|
||||
worldRenderables = worldRenderables.Concat(World.OrderGenerator.Render(this, World));
|
||||
renderablesBuffer.AddRange(World.OrderGenerator.Render(this, World));
|
||||
|
||||
// Unpartitioned effects
|
||||
worldRenderables = worldRenderables.Concat(World.UnpartitionedEffects.SelectMany(e => e.Render(this)));
|
||||
foreach (var e in World.UnpartitionedEffects)
|
||||
renderablesBuffer.AddRange(e.Render(this));
|
||||
|
||||
// Partitioned, currently on-screen effects
|
||||
var effectRenderables = World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight);
|
||||
worldRenderables = worldRenderables.Concat(effectRenderables.SelectMany(e => e.Render(this)));
|
||||
foreach (var e in World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight))
|
||||
renderablesBuffer.AddRange(e.Render(this));
|
||||
|
||||
worldRenderables = worldRenderables.OrderBy(RenderableScreenZPositionComparisonKey);
|
||||
// Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts
|
||||
foreach (var renderable in renderablesBuffer.OrderBy(RenderableZPositionComparisonKey))
|
||||
preparedRenderables.Add(renderable.PrepareRender(this));
|
||||
|
||||
return worldRenderables.Select(r => r.PrepareRender(this));
|
||||
// PERF: Reuse collection to avoid allocations.
|
||||
renderablesBuffer.Clear();
|
||||
}
|
||||
|
||||
IEnumerable<IFinalizedRenderable> GenerateOverlayRenderables()
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateOverlayRenderables()
|
||||
{
|
||||
var actors = World.ActorsWithTrait<IRenderAboveShroud>()
|
||||
.Where(a => a.Actor.IsInWorld && !a.Actor.Disposed && (!a.Trait.SpatiallyPartitionable || onScreenActors.Contains(a.Actor)))
|
||||
.SelectMany(a => a.Trait.RenderAboveShroud(a.Actor, this));
|
||||
foreach (var a in World.ActorsWithTrait<IRenderAboveShroud>())
|
||||
{
|
||||
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
|
||||
continue;
|
||||
|
||||
var selected = World.Selection.Actors.Where(a => a.IsInWorld && !a.Disposed)
|
||||
.SelectMany(a => a.TraitsImplementing<IRenderAboveShroudWhenSelected>()
|
||||
.Where(t => !t.SpatiallyPartitionable || onScreenActors.Contains(a))
|
||||
.SelectMany(t => t.RenderAboveShroud(a, this)));
|
||||
foreach (var renderable in a.Trait.RenderAboveShroud(a.Actor, this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
|
||||
var effects = World.Effects.Select(e => e as IEffectAboveShroud)
|
||||
.Where(e => e != null)
|
||||
.SelectMany(e => e.RenderAboveShroud(this));
|
||||
foreach (var a in World.Selection.Actors)
|
||||
{
|
||||
if (!a.IsInWorld || a.Disposed)
|
||||
continue;
|
||||
|
||||
foreach (var t in a.TraitsImplementing<IRenderAboveShroudWhenSelected>())
|
||||
{
|
||||
if (t.SpatiallyPartitionable && !onScreenActors.Contains(a))
|
||||
continue;
|
||||
|
||||
foreach (var renderable in t.RenderAboveShroud(a, this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in World.Effects)
|
||||
{
|
||||
var ea = e as IEffectAboveShroud;
|
||||
if (ea == null)
|
||||
continue;
|
||||
|
||||
foreach (var renderable in ea.RenderAboveShroud(this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
|
||||
var orderGenerator = SpriteRenderable.None;
|
||||
if (World.OrderGenerator != null)
|
||||
orderGenerator = World.OrderGenerator.RenderAboveShroud(this, World);
|
||||
|
||||
return actors
|
||||
.Concat(selected)
|
||||
.Concat(effects)
|
||||
.Concat(orderGenerator)
|
||||
.Select(r => r.PrepareRender(this));
|
||||
foreach (var renderable in World.OrderGenerator.RenderAboveShroud(this, World))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
|
||||
IEnumerable<IFinalizedRenderable> GenerateAnnotationRenderables()
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateAnnotationRenderables()
|
||||
{
|
||||
var actors = World.ActorsWithTrait<IRenderAnnotations>()
|
||||
.Where(a => a.Actor.IsInWorld && !a.Actor.Disposed && (!a.Trait.SpatiallyPartitionable || onScreenActors.Contains(a.Actor)))
|
||||
.SelectMany(a => a.Trait.RenderAnnotations(a.Actor, this));
|
||||
foreach (var a in World.ActorsWithTrait<IRenderAnnotations>())
|
||||
{
|
||||
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
|
||||
continue;
|
||||
|
||||
var selected = World.Selection.Actors.Where(a => a.IsInWorld && !a.Disposed)
|
||||
.SelectMany(a => a.TraitsImplementing<IRenderAnnotationsWhenSelected>()
|
||||
.Where(t => !t.SpatiallyPartitionable || onScreenActors.Contains(a))
|
||||
.SelectMany(t => t.RenderAnnotations(a, this)));
|
||||
foreach (var renderAnnotation in a.Trait.RenderAnnotations(a.Actor, this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
|
||||
var effects = World.Effects.Select(e => e as IEffectAnnotation)
|
||||
.Where(e => e != null)
|
||||
.SelectMany(e => e.RenderAnnotation(this));
|
||||
foreach (var a in World.Selection.Actors)
|
||||
{
|
||||
if (!a.IsInWorld || a.Disposed)
|
||||
continue;
|
||||
|
||||
foreach (var t in a.TraitsImplementing<IRenderAnnotationsWhenSelected>())
|
||||
{
|
||||
if (t.SpatiallyPartitionable && !onScreenActors.Contains(a))
|
||||
continue;
|
||||
|
||||
foreach (var renderAnnotation in t.RenderAnnotations(a, this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in World.Effects)
|
||||
{
|
||||
var ea = e as IEffectAnnotation;
|
||||
if (ea == null)
|
||||
continue;
|
||||
|
||||
foreach (var renderAnnotation in ea.RenderAnnotation(this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
|
||||
var orderGenerator = SpriteRenderable.None;
|
||||
if (World.OrderGenerator != null)
|
||||
orderGenerator = World.OrderGenerator.RenderAnnotations(this, World);
|
||||
|
||||
return actors
|
||||
.Concat(selected)
|
||||
.Concat(effects)
|
||||
.Concat(orderGenerator)
|
||||
.Select(r => r.PrepareRender(this));
|
||||
foreach (var renderAnnotation in World.OrderGenerator.RenderAnnotations(this, World))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
|
||||
public void PrepareRenderables()
|
||||
@@ -190,9 +241,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
// PERF: Reuse collection to avoid allocations.
|
||||
onScreenActors.UnionWith(World.ScreenMap.RenderableActorsInBox(Viewport.TopLeft, Viewport.BottomRight));
|
||||
preparedRenderables.AddRange(GenerateRenderables());
|
||||
preparedOverlayRenderables.AddRange(GenerateOverlayRenderables());
|
||||
preparedAnnotationRenderables.AddRange(GenerateAnnotationRenderables());
|
||||
|
||||
GenerateRenderables();
|
||||
GenerateOverlayRenderables();
|
||||
GenerateAnnotationRenderables();
|
||||
|
||||
onScreenActors.Clear();
|
||||
}
|
||||
|
||||
@@ -201,11 +254,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);
|
||||
@@ -213,8 +262,7 @@ namespace OpenRA.Graphics
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.Context.EnableDepthBuffer();
|
||||
|
||||
if (terrainRenderer != null)
|
||||
terrainRenderer.RenderTerrain(this, Viewport);
|
||||
terrainRenderer?.RenderTerrain(this, Viewport);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
|
||||
@@ -277,11 +325,16 @@ namespace OpenRA.Graphics
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.MediumSpringGreen);
|
||||
}
|
||||
|
||||
foreach (var r in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||
foreach (var b in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||
{
|
||||
var tl = Viewport.WorldToViewPx(new float2(r.Left, r.Top));
|
||||
var br = Viewport.WorldToViewPx(new float2(r.Right, r.Bottom));
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.OrangeRed);
|
||||
var points = new float2[b.Vertices.Length];
|
||||
for (var index = 0; index < b.Vertices.Length; index++)
|
||||
{
|
||||
var vertex = b.Vertices[index];
|
||||
points[index] = Viewport.WorldToViewPx(vertex).ToFloat2();
|
||||
}
|
||||
|
||||
Game.Renderer.RgbaColorRenderer.DrawPolygon(points, 1, Color.OrangeRed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +359,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);
|
||||
}
|
||||
|
||||
@@ -325,7 +384,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,
|
||||
@@ -334,29 +393,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.
|
||||
@@ -375,7 +424,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-2021 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-2021 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
|
||||
@@ -50,8 +50,7 @@ namespace OpenRA
|
||||
return () => keys[name];
|
||||
|
||||
// Try and parse as a hardcoded definition
|
||||
Hotkey key;
|
||||
if (!Hotkey.TryParse(name, out key))
|
||||
if (!Hotkey.TryParse(name, out var key))
|
||||
key = Hotkey.Invalid;
|
||||
|
||||
return () => key;
|
||||
@@ -59,8 +58,7 @@ namespace OpenRA
|
||||
|
||||
public void Set(string name, Hotkey value)
|
||||
{
|
||||
HotkeyDefinition definition;
|
||||
if (!definitions.TryGetValue(name, out definition))
|
||||
if (!definitions.TryGetValue(name, out var definition))
|
||||
return;
|
||||
|
||||
keys[name] = value;
|
||||
@@ -98,14 +96,8 @@ namespace OpenRA
|
||||
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-2021 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 !MONO
|
||||
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-2021 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-2021 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()
|
||||
@@ -32,11 +32,9 @@ namespace OpenRA
|
||||
|
||||
var parts = s.Split(' ');
|
||||
|
||||
Keycode key;
|
||||
if (!Enum<Keycode>.TryParse(parts[0], true, out key))
|
||||
if (!Enum<Keycode>.TryParse(parts[0], true, out var key))
|
||||
{
|
||||
int c;
|
||||
if (!int.TryParse(parts[0], out c))
|
||||
if (!int.TryParse(parts[0], out var c))
|
||||
return false;
|
||||
key = (Keycode)c;
|
||||
}
|
||||
@@ -83,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-2021 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-2021 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-2021 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-2021 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
|
||||
@@ -498,8 +498,7 @@ namespace OpenRA
|
||||
|
||||
public static string DisplayString(Keycode k)
|
||||
{
|
||||
string ret;
|
||||
if (!KeyNames.TryGetValue(k, out ret))
|
||||
if (!KeyNames.TryGetValue(k, out var ret))
|
||||
return k.ToString();
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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,10 +14,8 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -35,9 +33,9 @@ namespace OpenRA
|
||||
mods = GetInstalledMods(searchPaths, explicitPaths);
|
||||
}
|
||||
|
||||
static IEnumerable<Pair<string, string>> GetCandidateMods(IEnumerable<string> searchPaths)
|
||||
static IEnumerable<(string Id, string Path)> GetCandidateMods(IEnumerable<string> searchPaths)
|
||||
{
|
||||
var mods = new List<Pair<string, string>>();
|
||||
var mods = new List<(string, string)>();
|
||||
foreach (var path in searchPaths)
|
||||
{
|
||||
try
|
||||
@@ -48,7 +46,7 @@ namespace OpenRA
|
||||
|
||||
var directory = new DirectoryInfo(resolved);
|
||||
foreach (var subdir in directory.EnumerateDirectories())
|
||||
mods.Add(Pair.New(subdir.Name, subdir.FullName));
|
||||
mods.Add((subdir.Name, subdir.FullName));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -76,11 +74,10 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Load mod '{0}': {1}".F(path, e));
|
||||
Log.Write("debug", $"Load mod '{path}': {e}");
|
||||
}
|
||||
|
||||
if (package != null)
|
||||
package.Dispose();
|
||||
package?.Dispose();
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -89,22 +86,22 @@ namespace OpenRA
|
||||
{
|
||||
var ret = new Dictionary<string, Manifest>();
|
||||
var candidates = GetCandidateMods(searchPaths)
|
||||
.Concat(explicitPaths.Select(p => Pair.New(Path.GetFileNameWithoutExtension(p), p)));
|
||||
.Concat(explicitPaths.Select(p => (Id: Path.GetFileNameWithoutExtension(p), Path: p)));
|
||||
|
||||
foreach (var pair in candidates)
|
||||
{
|
||||
var mod = LoadMod(pair.First, pair.Second);
|
||||
var mod = LoadMod(pair.Id, pair.Path);
|
||||
if (mod != null)
|
||||
ret[pair.First] = mod;
|
||||
ret[pair.Id] = mod;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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-2021 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,19 +76,16 @@ namespace OpenRA
|
||||
if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed)
|
||||
return;
|
||||
|
||||
Action<DownloadDataCompletedEventArgs> onQueryComplete = i =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
innerState = LinkState.Unlinked;
|
||||
var client = HttpClientFactory.Create();
|
||||
|
||||
if (i.Error != null)
|
||||
{
|
||||
innerState = LinkState.ConnectionFailed;
|
||||
return;
|
||||
}
|
||||
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
|
||||
var result = await httpResponseMessage.Content.ReadAsStringAsync();
|
||||
|
||||
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First();
|
||||
var yaml = MiniYaml.FromString(result).First();
|
||||
if (yaml.Key == "Player")
|
||||
{
|
||||
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
||||
@@ -100,6 +97,8 @@ namespace OpenRA
|
||||
else
|
||||
innerState = LinkState.Linked;
|
||||
}
|
||||
else
|
||||
innerState = LinkState.Unlinked;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -108,13 +107,11 @@ namespace OpenRA
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (onComplete != null)
|
||||
onComplete();
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
innerState = LinkState.CheckingLink;
|
||||
new Download(playerDatabase.Profile + Fingerprint, _ => { }, onQueryComplete);
|
||||
}
|
||||
|
||||
public void GenerateKeypair()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2021 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-2021 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-2021 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,11 +60,12 @@ namespace OpenRA
|
||||
public string Version;
|
||||
public string Website;
|
||||
public string WebIcon32;
|
||||
public string WindowTitle;
|
||||
public bool Hidden;
|
||||
}
|
||||
|
||||
/// <summary> Describes what is to be loaded in order to run a mod. </summary>
|
||||
public class Manifest
|
||||
public class Manifest : IDisposable
|
||||
{
|
||||
public readonly string Id;
|
||||
public readonly IReadOnlyPackage Package;
|
||||
@@ -70,13 +83,14 @@ namespace OpenRA
|
||||
public readonly string[] SoundFormats = { };
|
||||
public readonly string[] SpriteFormats = { };
|
||||
public readonly string[] PackageFormats = { };
|
||||
public readonly string[] VideoFormats = { };
|
||||
|
||||
readonly string[] reservedModuleNames =
|
||||
{
|
||||
"Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||
"Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
||||
"ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
|
||||
"ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
|
||||
"RequiresMods", "PackageFormats"
|
||||
};
|
||||
|
||||
@@ -89,16 +103,33 @@ namespace OpenRA
|
||||
{
|
||||
Id = modId;
|
||||
Package = package;
|
||||
yaml = new MiniYaml(null, MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml")).ToDictionary();
|
||||
|
||||
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml");
|
||||
for (var i = nodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (nodes[i].Key != "Include")
|
||||
continue;
|
||||
|
||||
// Replace `Includes: filename.yaml` with the contents of filename.yaml
|
||||
var filename = nodes[i].Value.Value;
|
||||
var contents = package.GetStream(filename);
|
||||
if (contents == null)
|
||||
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
|
||||
|
||||
nodes.RemoveAt(i);
|
||||
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
|
||||
}
|
||||
|
||||
// Merge inherited overrides
|
||||
yaml = new MiniYaml(null, MiniYaml.Merge(new[] { nodes })).ToDictionary();
|
||||
|
||||
Metadata = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);
|
||||
|
||||
// TODO: Use fieldloader
|
||||
MapFolders = YamlDictionary(yaml, "MapFolders");
|
||||
|
||||
MiniYaml packages;
|
||||
if (yaml.TryGetValue("Packages", out packages))
|
||||
Packages = packages.ToDictionary(x => x.Value).AsReadOnly();
|
||||
if (yaml.TryGetValue("Packages", out var packages))
|
||||
Packages = packages.ToDictionary(x => x.Value);
|
||||
|
||||
Rules = YamlList(yaml, "Rules");
|
||||
Sequences = YamlList(yaml, "Sequences");
|
||||
@@ -138,6 +169,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)
|
||||
@@ -149,7 +183,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) });
|
||||
@@ -182,10 +216,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
|
||||
@@ -217,9 +250,8 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public T Get<T>(ObjectCreator oc) where T : IGlobalModData
|
||||
{
|
||||
MiniYaml data;
|
||||
var t = typeof(T);
|
||||
if (!yaml.TryGetValue(t.Name, out data))
|
||||
if (!yaml.TryGetValue(t.Name, out var data))
|
||||
{
|
||||
// Lazily create the default values if not explicitly defined.
|
||||
return (T)oc.CreateBasic(t);
|
||||
@@ -241,5 +273,14 @@ namespace OpenRA
|
||||
|
||||
return (T)module;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var module in modules)
|
||||
{
|
||||
var disposableModule = module as IDisposable;
|
||||
disposableModule?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user