Compare commits
879 Commits
playtest-2
...
playtest-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dda81b2db | ||
|
|
4243d42a63 | ||
|
|
4dec9258be | ||
|
|
b2e3ce8edf | ||
|
|
dbd48831b7 | ||
|
|
d11875b091 | ||
|
|
838a3f83c6 | ||
|
|
8512cdb8bb | ||
|
|
0b78f93178 | ||
|
|
59fd3184ca | ||
|
|
ffa7db01a0 | ||
|
|
0eb1e2307d | ||
|
|
bbf60a3697 | ||
|
|
a8fa37d84b | ||
|
|
0176c1a217 | ||
|
|
43abbbecb1 | ||
|
|
d063b28728 | ||
|
|
50c4f19523 | ||
|
|
a93435ccc4 | ||
|
|
ffed6c0a4a | ||
|
|
52934818e9 | ||
|
|
8ddfcd705b | ||
|
|
578313c3a5 | ||
|
|
01fdd67ef0 | ||
|
|
ac9b641240 | ||
|
|
18604cf905 | ||
|
|
dc2fca9df8 | ||
|
|
e8d95a1d57 | ||
|
|
d6aab1bd7a | ||
|
|
a293e162b2 | ||
|
|
60752b115f | ||
|
|
b4f02440d9 | ||
|
|
bd951d8fb0 | ||
|
|
f520b375e4 | ||
|
|
cadf6956a9 | ||
|
|
db317eb226 | ||
|
|
b36ca98a83 | ||
|
|
1b5261e58a | ||
|
|
5654736086 | ||
|
|
3038f15f00 | ||
|
|
187dd5a071 | ||
|
|
740f04801b | ||
|
|
619d433e8b | ||
|
|
4ca87c1e05 | ||
|
|
0b97003d83 | ||
|
|
6ba02da2eb | ||
|
|
4496cffa4a | ||
|
|
c3f1f617e0 | ||
|
|
dbab122594 | ||
|
|
521236398d | ||
|
|
4b7f8c6c82 | ||
|
|
2e60e41764 | ||
|
|
e8f552bf68 | ||
|
|
8743f13caa | ||
|
|
963a8e6ea9 | ||
|
|
c8f1ce0d42 | ||
|
|
13e0e927fe | ||
|
|
d3c130e8ae | ||
|
|
8ff1762eac | ||
|
|
ef418403c5 | ||
|
|
74da4065fc | ||
|
|
b3e4fc807e | ||
|
|
893ee6eb2c | ||
|
|
a5df442499 | ||
|
|
a87df0f81e | ||
|
|
75130c47ff | ||
|
|
fd73556685 | ||
|
|
958cfcd378 | ||
|
|
08be5d3907 | ||
|
|
4adee762d9 | ||
|
|
bf0b139398 | ||
|
|
65fedd1eb1 | ||
|
|
117b229cb6 | ||
|
|
76f52abd3d | ||
|
|
a7212d52fb | ||
|
|
5e0a789dac | ||
|
|
17b4158a56 | ||
|
|
09d9cf7da6 | ||
|
|
63ee481f01 | ||
|
|
818fc9d173 | ||
|
|
fed3b5f565 | ||
|
|
1d2dc1c232 | ||
|
|
722ea75cab | ||
|
|
e81b9e9467 | ||
|
|
509dc09aa6 | ||
|
|
d71c377e93 | ||
|
|
c6baf16fa7 | ||
|
|
0529a16298 | ||
|
|
53adc2bec7 | ||
|
|
e97617d618 | ||
|
|
4d47dd6ad7 | ||
|
|
7ad95ed0b0 | ||
|
|
1d6732f24f | ||
|
|
85931d5f0b | ||
|
|
6f68b4fb3e | ||
|
|
9d84f4a845 | ||
|
|
5b24fddd5a | ||
|
|
0f54d6f709 | ||
|
|
d1ed0e09d0 | ||
|
|
d8bc4905cb | ||
|
|
4204ba1c1f | ||
|
|
17f469f1a5 | ||
|
|
0e64ac4709 | ||
|
|
afd8b9ab86 | ||
|
|
9097837e6d | ||
|
|
93fcfbb131 | ||
|
|
7a2d2cfd5f | ||
|
|
bfca4897ea | ||
|
|
488440197e | ||
|
|
4ffcd9b4a5 | ||
|
|
72cf5d29e7 | ||
|
|
f1b54ece8a | ||
|
|
ce671a2737 | ||
|
|
224ca78257 | ||
|
|
ace353cb84 | ||
|
|
7e8f920bc1 | ||
|
|
86ab6e7ed3 | ||
|
|
b859b9ee43 | ||
|
|
0b6b997e81 | ||
|
|
f921a45747 | ||
|
|
3bea08f020 | ||
|
|
9413d9595c | ||
|
|
85c948fd8d | ||
|
|
45606c9528 | ||
|
|
1b0ae1e512 | ||
|
|
70b43222fe | ||
|
|
bc54789641 | ||
|
|
65920a883f | ||
|
|
5cd8acada4 | ||
|
|
ce8f3ed168 | ||
|
|
a7334d5583 | ||
|
|
db16165261 | ||
|
|
34dc52451a | ||
|
|
346b6af30d | ||
|
|
13983765d6 | ||
|
|
cf5abc68ad | ||
|
|
efd8a9c1cb | ||
|
|
fa02c97111 | ||
|
|
4afe9dd248 | ||
|
|
abb8e27747 | ||
|
|
afa9b9760e | ||
|
|
a1cb0cf002 | ||
|
|
07edf2f7c6 | ||
|
|
6212b82f6d | ||
|
|
afa8d3522c | ||
|
|
92c9988eea | ||
|
|
d95d7e0b0f | ||
|
|
77ef3a0ed2 | ||
|
|
70c251d5d5 | ||
|
|
03c7c8f79c | ||
|
|
5bdb7bd689 | ||
|
|
fdb3866238 | ||
|
|
46dc827d46 | ||
|
|
801796b184 | ||
|
|
59dae0ebf6 | ||
|
|
1c2db8806b | ||
|
|
9a7778e55d | ||
|
|
6749060e57 | ||
|
|
4a39bc8286 | ||
|
|
e38db04ab7 | ||
|
|
1fa1677204 | ||
|
|
2685dbea3b | ||
|
|
2c522e0371 | ||
|
|
bc4a5193e9 | ||
|
|
739f357090 | ||
|
|
e69f129fed | ||
|
|
372d940936 | ||
|
|
b30cfe4ada | ||
|
|
daf8f8d956 | ||
|
|
6e145f902b | ||
|
|
cd9ef47f94 | ||
|
|
f341330d5e | ||
|
|
4f16b0d464 | ||
|
|
f7c57cfc6f | ||
|
|
ab1a798d04 | ||
|
|
96a1c8d54b | ||
|
|
b0906e1836 | ||
|
|
e88754cd4a | ||
|
|
3a6796ac9d | ||
|
|
c2e36b8eeb | ||
|
|
1a546d9baf | ||
|
|
aab9dfcaa0 | ||
|
|
8da112dc92 | ||
|
|
6c962b6675 | ||
|
|
6005b59064 | ||
|
|
686686417f | ||
|
|
8f4a92af99 | ||
|
|
3200f4fb0f | ||
|
|
9b137afa6d | ||
|
|
69aa4f5962 | ||
|
|
297f4ad9ed | ||
|
|
1f436ad0cb | ||
|
|
c3ccd1906d | ||
|
|
5cee040fc0 | ||
|
|
03cfd5b880 | ||
|
|
4b9c6c4746 | ||
|
|
3abd65052c | ||
|
|
b491ec871c | ||
|
|
9a25dd6a60 | ||
|
|
faf9f7cede | ||
|
|
a6b9bab033 | ||
|
|
6dca6b3740 | ||
|
|
2e4cd8d820 | ||
|
|
9e138178ad | ||
|
|
9c2e3aaa05 | ||
|
|
8a5a8a1646 | ||
|
|
ff268eee44 | ||
|
|
ec5c218ac4 | ||
|
|
fdafbd9f15 | ||
|
|
2a2bd676a3 | ||
|
|
b2c3a55c12 | ||
|
|
e10b64d62c | ||
|
|
f801fc502f | ||
|
|
3f8efb1163 | ||
|
|
f75115a645 | ||
|
|
04033e5c79 | ||
|
|
841798a0a8 | ||
|
|
057c201e85 | ||
|
|
83be37acaf | ||
|
|
57af145c12 | ||
|
|
27ce2fcda9 | ||
|
|
78bedb0513 | ||
|
|
8a508ecafa | ||
|
|
dbe16bfdb5 | ||
|
|
88ab77ce0f | ||
|
|
714b24329c | ||
|
|
3996aadcb6 | ||
|
|
2650973d51 | ||
|
|
d283231d94 | ||
|
|
f7aac24c43 | ||
|
|
3e7bf1880f | ||
|
|
266b5d194f | ||
|
|
115c72aa4b | ||
|
|
d81a07931f | ||
|
|
ba1279aa93 | ||
|
|
32e7ab7a30 | ||
|
|
4823d5454d | ||
|
|
e0abf8ed27 | ||
|
|
1d2361cdd3 | ||
|
|
ca475fe133 | ||
|
|
dc5818d035 | ||
|
|
a755070d76 | ||
|
|
f29d2028fe | ||
|
|
6e1b2333f3 | ||
|
|
1b3422db2d | ||
|
|
7772ebedf4 | ||
|
|
78bf530a4e | ||
|
|
6df7f18a3b | ||
|
|
0a1083e554 | ||
|
|
33e8bf9928 | ||
|
|
edffaa4987 | ||
|
|
6b3c04a584 | ||
|
|
7d3cf7894b | ||
|
|
522a29f1e4 | ||
|
|
c58db84d35 | ||
|
|
1760a12428 | ||
|
|
118039ded3 | ||
|
|
839945cf57 | ||
|
|
da5c70184e | ||
|
|
3c323a8672 | ||
|
|
53c7954f84 | ||
|
|
7237fd83c6 | ||
|
|
8e9f20cf4b | ||
|
|
779218f381 | ||
|
|
243ed3d76a | ||
|
|
100abe3246 | ||
|
|
a7cedc54ff | ||
|
|
300a7222f3 | ||
|
|
2e2e1cae7f | ||
|
|
57df34dcbf | ||
|
|
0a1f2b7275 | ||
|
|
0aa84aa5ed | ||
|
|
4040863707 | ||
|
|
f6e87f63e7 | ||
|
|
c4e8af08ec | ||
|
|
a3c9d72cfd | ||
|
|
07a2ee5eab | ||
|
|
568d7efdb1 | ||
|
|
4b4172f757 | ||
|
|
995a3da43a | ||
|
|
b0f1059df2 | ||
|
|
f0ffb4e8d9 | ||
|
|
3db15beeb9 | ||
|
|
728162e688 | ||
|
|
f98e7656da | ||
|
|
c20d02fd2e | ||
|
|
26e6d86821 | ||
|
|
7eab278711 | ||
|
|
da7433a95f | ||
|
|
9c9a23be86 | ||
|
|
2683b2507e | ||
|
|
6ca0208694 | ||
|
|
736e70df78 | ||
|
|
17089f2aee | ||
|
|
34810756c2 | ||
|
|
dc4c3fd546 | ||
|
|
4f42778d26 | ||
|
|
e1cd00c1dd | ||
|
|
086fc88e3e | ||
|
|
35e131bac7 | ||
|
|
b219214426 | ||
|
|
d1790229a9 | ||
|
|
600478603c | ||
|
|
4187a69904 | ||
|
|
90778bc8a8 | ||
|
|
242f33c2ba | ||
|
|
2923c9907f | ||
|
|
d52313ab18 | ||
|
|
fef388834e | ||
|
|
5fb468922e | ||
|
|
2e70b6931b | ||
|
|
8276b17570 | ||
|
|
2def72a078 | ||
|
|
2e801a55ce | ||
|
|
52b08a769d | ||
|
|
d7f9c2f852 | ||
|
|
b613b1b2e0 | ||
|
|
716343732f | ||
|
|
ab8bc53ed8 | ||
|
|
6ff31786cb | ||
|
|
c474b77d79 | ||
|
|
2704f3bcd5 | ||
|
|
16e260e69f | ||
|
|
7cadbcb8f6 | ||
|
|
07e6708b88 | ||
|
|
7ba276c0c1 | ||
|
|
3b626e5ff7 | ||
|
|
452aed0a97 | ||
|
|
5d40ed3e32 | ||
|
|
8494200321 | ||
|
|
83c4c842ee | ||
|
|
423ea555b3 | ||
|
|
4af31be6bd | ||
|
|
ff9bddbf21 | ||
|
|
52f1ab0969 | ||
|
|
f1eb96b273 | ||
|
|
854dd75282 | ||
|
|
8f2a933ba8 | ||
|
|
ce233a6cca | ||
|
|
94fba3521a | ||
|
|
8dfb7d8bbc | ||
|
|
e8d6d63707 | ||
|
|
533b2f9ad7 | ||
|
|
46d0157c2e | ||
|
|
3507ad2f87 | ||
|
|
e2647e8a60 | ||
|
|
6a212eea53 | ||
|
|
7acc6dacbc | ||
|
|
73577e3a7e | ||
|
|
2a0b3b39ea | ||
|
|
f7ddb969c6 | ||
|
|
40e8c5136d | ||
|
|
abdcf90c30 | ||
|
|
36d7b5131e | ||
|
|
f79a3a8d03 | ||
|
|
f7db7e3308 | ||
|
|
49f0e35bd7 | ||
|
|
82758a8bef | ||
|
|
7fd0a3aa58 | ||
|
|
7f81de2f8a | ||
|
|
43b55ae333 | ||
|
|
c7c6cf864c | ||
|
|
61afa096e3 | ||
|
|
aa9c00367b | ||
|
|
677904c682 | ||
|
|
fc0495ac27 | ||
|
|
f4988356a0 | ||
|
|
8bcc2b30d9 | ||
|
|
96ca3baff8 | ||
|
|
a869dacdc4 | ||
|
|
ada5b6d0e5 | ||
|
|
d080a47cbf | ||
|
|
afa5a7772d | ||
|
|
cadfbe8a29 | ||
|
|
5834d7615b | ||
|
|
bc5eb66088 | ||
|
|
8d69d26542 | ||
|
|
479795f494 | ||
|
|
45041bde14 | ||
|
|
9a9920e4af | ||
|
|
e1b7fc2617 | ||
|
|
45b13dabfb | ||
|
|
34844e87a3 | ||
|
|
69587a2128 | ||
|
|
e5222f95f6 | ||
|
|
ba7290cc2c | ||
|
|
28fdb71163 | ||
|
|
8321d1f3e3 | ||
|
|
df40d38b91 | ||
|
|
c06df1ed21 | ||
|
|
aecfff905d | ||
|
|
2a3139dc39 | ||
|
|
767fd5438e | ||
|
|
a43ec158fa | ||
|
|
7f3f36885a | ||
|
|
29d827079e | ||
|
|
b104f8baa2 | ||
|
|
e8b2a12714 | ||
|
|
0ddc4360fe | ||
|
|
c6816cd33f | ||
|
|
b1ac1d06e3 | ||
|
|
1f93029e51 | ||
|
|
2e0d7d0e79 | ||
|
|
2e7de2874d | ||
|
|
5dd6aa7a3a | ||
|
|
5e40a5b683 | ||
|
|
ddba9b2efd | ||
|
|
7ea40482e2 | ||
|
|
cc6191554a | ||
|
|
550256373a | ||
|
|
434ea9ca88 | ||
|
|
ac4fef6630 | ||
|
|
61659d0d32 | ||
|
|
b74141666b | ||
|
|
3258d89651 | ||
|
|
127ef8bb27 | ||
|
|
a26210f914 | ||
|
|
0e3cc1ad28 | ||
|
|
ffc3f6e0d0 | ||
|
|
b2e6a0484b | ||
|
|
d95bb4152a | ||
|
|
224bda6de5 | ||
|
|
d8edcb1bad | ||
|
|
d45870ca23 | ||
|
|
430429f337 | ||
|
|
b8c251cf41 | ||
|
|
d502b296bb | ||
|
|
aa8d9f5dda | ||
|
|
6f19379a2b | ||
|
|
3b798897be | ||
|
|
48abb7372b | ||
|
|
5b8c313d55 | ||
|
|
96d629cefd | ||
|
|
d04c6275da | ||
|
|
033268a7ba | ||
|
|
9f9d1f9e5f | ||
|
|
d92d9f86fc | ||
|
|
0d2d17d2fb | ||
|
|
db85fff7c0 | ||
|
|
91c65f1e9c | ||
|
|
ccda89f268 | ||
|
|
b11af3bcb5 | ||
|
|
97ec200dcf | ||
|
|
42232f4a55 | ||
|
|
cf65bb6e41 | ||
|
|
5e736527b7 | ||
|
|
2a0cfa5c43 | ||
|
|
f0c6e5e855 | ||
|
|
618ac5838a | ||
|
|
55de4a59d0 | ||
|
|
4a80c37507 | ||
|
|
e94ef1658f | ||
|
|
552cf83951 | ||
|
|
fc29438748 | ||
|
|
d2973801e4 | ||
|
|
cf09b99ed8 | ||
|
|
cdf2df58a0 | ||
|
|
5dcb840f9f | ||
|
|
a3f7641b68 | ||
|
|
0222ea675c | ||
|
|
9b4f602cca | ||
|
|
248c12827e | ||
|
|
f0d7a6caca | ||
|
|
74437ed56c | ||
|
|
1194f14cfe | ||
|
|
e9926fdc28 | ||
|
|
e6ec071ceb | ||
|
|
d787429a2e | ||
|
|
e01c79be06 | ||
|
|
431f06cd49 | ||
|
|
9a1ba31753 | ||
|
|
700b117122 | ||
|
|
5fd4b3a1bd | ||
|
|
b0706e7cd0 | ||
|
|
0dfdea1826 | ||
|
|
61b84e0c5a | ||
|
|
64a2b9de55 | ||
|
|
1c5427c86f | ||
|
|
11933c7e2c | ||
|
|
fb1d8d780f | ||
|
|
c867687d9a | ||
|
|
fff2f097d0 | ||
|
|
cd0898236d | ||
|
|
669cf01d80 | ||
|
|
9be5ef2bd5 | ||
|
|
53bf52149c | ||
|
|
22d8167d49 | ||
|
|
f5ddea305a | ||
|
|
81b79a1b29 | ||
|
|
4ec0089250 | ||
|
|
ea29cce3cf | ||
|
|
c4e79a11a8 | ||
|
|
1ff11d4115 | ||
|
|
c48cf51190 | ||
|
|
21a10f84a5 | ||
|
|
7a7b668f2b | ||
|
|
dcf31f356d | ||
|
|
dbe4b670d0 | ||
|
|
5a273700bf | ||
|
|
d68bc48ba5 | ||
|
|
437f9a7930 | ||
|
|
f9913db5e8 | ||
|
|
f9951f76ca | ||
|
|
166063337e | ||
|
|
6075569659 | ||
|
|
748afad45e | ||
|
|
18eddf4a70 | ||
|
|
770efd710b | ||
|
|
705d77501b | ||
|
|
696b48b7bf | ||
|
|
3111b2cf9b | ||
|
|
e0adbe652e | ||
|
|
1349dfb670 | ||
|
|
d5740efda8 | ||
|
|
382fefebfe | ||
|
|
1871715699 | ||
|
|
10734f2877 | ||
|
|
43ff56eea9 | ||
|
|
12f2fc3410 | ||
|
|
cb6e4b9926 | ||
|
|
69ca9ffaba | ||
|
|
4e6af29bb8 | ||
|
|
993f4ee6da | ||
|
|
53aaa28a71 | ||
|
|
0c6911bd2f | ||
|
|
398956b690 | ||
|
|
4917c6e453 | ||
|
|
9759fce9d7 | ||
|
|
2d2d869b5a | ||
|
|
965a73fc53 | ||
|
|
0a484b424e | ||
|
|
1fae993593 | ||
|
|
bc38cb3cc2 | ||
|
|
74bbde751f | ||
|
|
60bc114e39 | ||
|
|
c8af9168c9 | ||
|
|
2c1b8da7fd | ||
|
|
539ed67527 | ||
|
|
2ae40e7eb4 | ||
|
|
61a1cc18e1 | ||
|
|
6aaa7fa042 | ||
|
|
a01b3f88fc | ||
|
|
8dd50c0421 | ||
|
|
8abf5ce372 | ||
|
|
a9676235db | ||
|
|
7dddc7fc44 | ||
|
|
6045ad2ecf | ||
|
|
4bfd306cc2 | ||
|
|
d9800d4e2b | ||
|
|
99f3f37afe | ||
|
|
fe03929e6b | ||
|
|
e8f16b1b75 | ||
|
|
9ce110f6d9 | ||
|
|
2dc528899e | ||
|
|
f3f443d904 | ||
|
|
bd025873cf | ||
|
|
172161b9fe | ||
|
|
7a7197216c | ||
|
|
fcbdb147ed | ||
|
|
0ddb631117 | ||
|
|
64896eb73d | ||
|
|
a83c0f96dd | ||
|
|
f8af51643d | ||
|
|
a8bb03aa34 | ||
|
|
227b80d6c2 | ||
|
|
304e3ef9f9 | ||
|
|
62603b324d | ||
|
|
2b9b9bb984 | ||
|
|
227655f9aa | ||
|
|
ac53bc502e | ||
|
|
2ca6721027 | ||
|
|
6744cec239 | ||
|
|
1206205d17 | ||
|
|
ac1d45211d | ||
|
|
e29c234ce9 | ||
|
|
863d477928 | ||
|
|
9ca75d6c8d | ||
|
|
df07d89f9e | ||
|
|
b53ac5df36 | ||
|
|
d433473249 | ||
|
|
9f8e9426af | ||
|
|
2e751e937f | ||
|
|
8a04156280 | ||
|
|
63de0bb910 | ||
|
|
1e63013eb1 | ||
|
|
9b93771dbc | ||
|
|
b24e681531 | ||
|
|
62b0be9db8 | ||
|
|
07dc79ab19 | ||
|
|
3e0f055a3c | ||
|
|
b5523d6b3f | ||
|
|
4c990e48d7 | ||
|
|
8dbeada0f7 | ||
|
|
7f204b2dec | ||
|
|
9d248433c9 | ||
|
|
84db123b93 | ||
|
|
ff088323b5 | ||
|
|
ec42aed6bc | ||
|
|
688feea33b | ||
|
|
215aa6fa60 | ||
|
|
e984c98565 | ||
|
|
5d5fd7a0e8 | ||
|
|
d9eb224c77 | ||
|
|
ec704b7e34 | ||
|
|
43b9aa00d8 | ||
|
|
5e1cd5dff9 | ||
|
|
5789b73091 | ||
|
|
c23f248840 | ||
|
|
4a8571161a | ||
|
|
3fbdde5317 | ||
|
|
04f8a85cc6 | ||
|
|
c5547ec20b | ||
|
|
bb9790c754 | ||
|
|
b373714da7 | ||
|
|
60fccc1bc4 | ||
|
|
ebb6505190 | ||
|
|
57bdb1cff3 | ||
|
|
b97162d9f1 | ||
|
|
ea6e1fdd64 | ||
|
|
4767b91037 | ||
|
|
c94eb2582b | ||
|
|
843ac85c92 | ||
|
|
89c19e717f | ||
|
|
2053aec5f9 | ||
|
|
c34b947e43 | ||
|
|
f9974624c8 | ||
|
|
225d26bfdb | ||
|
|
afc1f22c63 | ||
|
|
9814083ac3 | ||
|
|
80ec530e4c | ||
|
|
361cbc34cc | ||
|
|
7416cd0ac1 | ||
|
|
23ba3cfcb6 | ||
|
|
8e6f6a3a52 | ||
|
|
e164a6119d | ||
|
|
0b0f8bc51e | ||
|
|
c6d8957307 | ||
|
|
199e9847d1 | ||
|
|
2548e62e31 | ||
|
|
b50f15c645 | ||
|
|
ff8f204cac | ||
|
|
6df453f806 | ||
|
|
3ec6754289 | ||
|
|
462ee48c60 | ||
|
|
37219ad67b | ||
|
|
4480f49e0f | ||
|
|
1860b448b7 | ||
|
|
774a717c19 | ||
|
|
9b22f0e11a | ||
|
|
1d3daa5e4d | ||
|
|
4c63c7bdd8 | ||
|
|
1722f42f83 | ||
|
|
ddc31825ec | ||
|
|
7168912273 | ||
|
|
04f5937476 | ||
|
|
8c1ed0d39f | ||
|
|
0635f3636d | ||
|
|
3cd7493664 | ||
|
|
63e4fbc5ad | ||
|
|
9637e1b2c7 | ||
|
|
ee91396984 | ||
|
|
cdf01cc25b | ||
|
|
87a44023b2 | ||
|
|
c170f658ff | ||
|
|
81fe19ca4a | ||
|
|
e043539805 | ||
|
|
b0caa104f6 | ||
|
|
e058eae706 | ||
|
|
8e5fbfe227 | ||
|
|
fb4cb04934 | ||
|
|
f33bf002ea | ||
|
|
882b5da20d | ||
|
|
39cdf65764 | ||
|
|
6b18eb1993 | ||
|
|
035c4adeb0 | ||
|
|
a11ff104a2 | ||
|
|
9216427c2b | ||
|
|
739653ce37 | ||
|
|
bbbf3b086b | ||
|
|
2997f31bc3 | ||
|
|
8e96d85d24 | ||
|
|
31a91a0269 | ||
|
|
044a5e18ee | ||
|
|
005b4166cc | ||
|
|
35249c1faf | ||
|
|
a853d431fc | ||
|
|
3808a1885e | ||
|
|
e37667efb6 | ||
|
|
60531c6eef | ||
|
|
b0187dd646 | ||
|
|
e73d3922dd | ||
|
|
fc725c6c0c | ||
|
|
e87a96f2b8 | ||
|
|
09c610e2cc | ||
|
|
1fa8286f1e | ||
|
|
563d9abce9 | ||
|
|
14ece703ef | ||
|
|
2ca3a1d120 | ||
|
|
4b652aa894 | ||
|
|
c69a85a328 | ||
|
|
b5068facdc | ||
|
|
5c0674d695 | ||
|
|
77af9ca01d | ||
|
|
d7c710e5fc | ||
|
|
2c678a240c | ||
|
|
6d84079a94 | ||
|
|
3f4f56d728 | ||
|
|
92bf1f43af | ||
|
|
59d6b14e06 | ||
|
|
a82e835f87 | ||
|
|
b9f0504380 | ||
|
|
043d7587a6 | ||
|
|
8f2f54f92f | ||
|
|
8f50052a1c | ||
|
|
009cbb74f5 | ||
|
|
d6cf3b35b6 | ||
|
|
e4d3712205 | ||
|
|
49c3dfe2fc | ||
|
|
737764a47a | ||
|
|
d6b9304fd8 | ||
|
|
6217cfbcd2 | ||
|
|
1c3276c935 | ||
|
|
96e2b4fe70 | ||
|
|
4d5f9e0de7 | ||
|
|
62a3099215 | ||
|
|
2c8b2c5c7d | ||
|
|
ffb639bd7c | ||
|
|
1afdcfb749 | ||
|
|
ee260af064 | ||
|
|
d095ccf668 | ||
|
|
af85ca8f18 | ||
|
|
6b4700b24f | ||
|
|
d33a0105a2 | ||
|
|
11f85a1a36 | ||
|
|
c3255e275c | ||
|
|
8b9c363222 | ||
|
|
832ae3149d | ||
|
|
b492e1e891 | ||
|
|
d9b3d0fd84 | ||
|
|
8ee67a4438 | ||
|
|
fd717041ae | ||
|
|
7ba8a19862 | ||
|
|
783eb1e9ea | ||
|
|
0fa6c7ed3c | ||
|
|
537184b65c | ||
|
|
4f07a00994 | ||
|
|
df85ea222a | ||
|
|
cf8db74d85 | ||
|
|
2172d91efb | ||
|
|
4fdda9efd2 | ||
|
|
ae7a7418eb | ||
|
|
edc1753e22 | ||
|
|
7fdbe10319 | ||
|
|
e1e47e7e0d | ||
|
|
5b617cf6e5 | ||
|
|
b59f1f39fe | ||
|
|
aa3f21632c | ||
|
|
4c32e23f89 | ||
|
|
bfc0f7f9e6 | ||
|
|
7f68c38e72 | ||
|
|
dac8a8e4f6 | ||
|
|
5ccfcb1cb9 | ||
|
|
d2a0535442 | ||
|
|
ae111248f3 | ||
|
|
4d335af9fc | ||
|
|
bfcb37e1b7 | ||
|
|
b6810f3dd3 | ||
|
|
cc67f9328c | ||
|
|
ff998fd552 | ||
|
|
d819f3206e | ||
|
|
a49227c494 | ||
|
|
b7ca740155 | ||
|
|
aa99c3cd40 | ||
|
|
294259c726 | ||
|
|
44254472d5 | ||
|
|
ee63835778 | ||
|
|
1291d1a53d | ||
|
|
49d26fd238 | ||
|
|
37101bf4f0 | ||
|
|
e1dda2bf54 | ||
|
|
b2b3390bc0 | ||
|
|
ceab7a222e | ||
|
|
7a3bae33ea | ||
|
|
00d854f31b | ||
|
|
bad2c1c2bf | ||
|
|
66d50ba0b5 | ||
|
|
245ca7d996 | ||
|
|
0e345b465c | ||
|
|
b295f36fa7 | ||
|
|
a36a77d637 | ||
|
|
7bb8cbf867 | ||
|
|
c19463f5b4 | ||
|
|
7491ede65a | ||
|
|
1abe93f25a | ||
|
|
785e20e0f3 | ||
|
|
6069a12973 | ||
|
|
902d2a0016 | ||
|
|
31d5d048fe | ||
|
|
28e204a8ae | ||
|
|
8fd9f98180 | ||
|
|
dd12d70ded | ||
|
|
06f6a8646f | ||
|
|
02b82ae344 | ||
|
|
d0e859cbeb | ||
|
|
620ae45cb3 | ||
|
|
880f90345e | ||
|
|
5e923ff091 | ||
|
|
d7684ee680 | ||
|
|
fbd62c242f | ||
|
|
b331065b87 | ||
|
|
80f4feeab3 | ||
|
|
a6078b6e46 | ||
|
|
fbd3a2efea | ||
|
|
f38c5f3380 | ||
|
|
abad50d2b9 | ||
|
|
cca7eda530 | ||
|
|
d3e4720507 | ||
|
|
aa8c434668 | ||
|
|
8e7de1a4fa | ||
|
|
e766c6f342 | ||
|
|
34a37421f6 | ||
|
|
eb77c57ac3 | ||
|
|
bbc08627ac | ||
|
|
67144fe66b | ||
|
|
395d31b249 | ||
|
|
e0985162a0 | ||
|
|
681dcc81a1 | ||
|
|
33bcdb10ba | ||
|
|
046427beea | ||
|
|
db4554acec | ||
|
|
3aeddbf9bd | ||
|
|
4c32803c16 | ||
|
|
4f0e317371 | ||
|
|
ab6b7c8551 | ||
|
|
d138a05baf | ||
|
|
dcf1cfe118 | ||
|
|
fbc850a73f | ||
|
|
08a71795d1 | ||
|
|
daf4eb812c | ||
|
|
63ee126898 | ||
|
|
0847cb9d31 | ||
|
|
daeba01bc1 | ||
|
|
c45634ed8d | ||
|
|
f3cfe24e11 | ||
|
|
a3030aa074 | ||
|
|
d86ff6696a | ||
|
|
da2330a116 | ||
|
|
4d2563cc11 | ||
|
|
a9cfbd9ad7 | ||
|
|
00b1bc7cd2 | ||
|
|
2f48e67e6d | ||
|
|
7588d6708b | ||
|
|
5e737980fe | ||
|
|
73698c2ecb | ||
|
|
f6b11bb662 | ||
|
|
e055db94ab | ||
|
|
82d5979eb1 | ||
|
|
3499325f23 | ||
|
|
439a4a70d4 | ||
|
|
28cd480225 | ||
|
|
f999001caf | ||
|
|
c87e1c789a | ||
|
|
386e5b3772 | ||
|
|
1d636082a3 | ||
|
|
a40639c559 | ||
|
|
062eef37f4 | ||
|
|
d7a49daae8 | ||
|
|
e57fd37697 | ||
|
|
407ecbc397 | ||
|
|
e25cb2914f | ||
|
|
120efcedaa | ||
|
|
efcaa86db6 | ||
|
|
a0598d2b9c | ||
|
|
19616c059c | ||
|
|
06c7dad50e | ||
|
|
d3491c2979 | ||
|
|
7ad63bd3c7 | ||
|
|
8b7797326a | ||
|
|
e318c028ba |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,6 +11,7 @@ obj
|
||||
*.CodeAnalysisLog.xml
|
||||
*.lastcodeanalysissucceeded
|
||||
_ReSharper.*/
|
||||
/.vs
|
||||
|
||||
# movies
|
||||
*.vqa
|
||||
@@ -61,6 +62,7 @@ OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.mode1v3
|
||||
|
||||
# auto-generated documentation
|
||||
DOCUMENTATION.md
|
||||
WEAPONS.md
|
||||
Lua-API.md
|
||||
*.html
|
||||
openra.6
|
||||
@@ -79,3 +81,5 @@ StyleCopViolations.xml
|
||||
|
||||
# Support directory
|
||||
/Support
|
||||
/da/Microsoft.Build.Utilities.v3.5.resources.dll
|
||||
/da
|
||||
|
||||
@@ -19,6 +19,11 @@ addons:
|
||||
- nsis-common
|
||||
- dpkg
|
||||
- markdown
|
||||
- zlib1g-dev
|
||||
- libbz2-dev
|
||||
- cmake
|
||||
- genisoimage
|
||||
- fakeroot
|
||||
|
||||
# Environment variables
|
||||
env:
|
||||
@@ -39,7 +44,7 @@ script:
|
||||
|
||||
# Automatically update the trait documentation and Lua API
|
||||
after_success:
|
||||
- test $TRAVIS_PULL_REQUEST == "false" && make docs && cd packaging && ./update-wiki.sh $TRAVIS_BRANCH; cd ..
|
||||
- test $TRAVIS_PULL_REQUEST == "false" && cd packaging && ./update-wiki.sh $TRAVIS_BRANCH; cd ..
|
||||
|
||||
# Only watch the development branch and tagged release.
|
||||
branches:
|
||||
@@ -72,7 +77,7 @@ deploy:
|
||||
secure: "g/LU11f+mjqv+lj0sR1UliHwogXL4ofJUwoG5Dbqlvdf5UTLWytw/OWSCv8RGyuh10miyWeaoqHh1cn2C1IFhUEqN1sSeKKKOWOTvJ2FR5mzi9uH3d/MOBzG5icQ7Qh0fZ1YPz5RaJJhYu6bmfvA/1gD49GoaX2kxQL4J5cEBgg="
|
||||
file:
|
||||
- build/OpenRA-${TRAVIS_TAG}.exe
|
||||
- build/OpenRA-${TRAVIS_TAG}.zip
|
||||
- build/OpenRA-${TRAVIS_TAG}.dmg
|
||||
- build/openra_${DOTVERSION}_all.deb
|
||||
skip_cleanup: true
|
||||
on:
|
||||
|
||||
5
AUTHORS
5
AUTHORS
@@ -5,7 +5,6 @@ The OpenRA developers are:
|
||||
* Chris Forbes (chrisf)
|
||||
* Igor Popov (ihptru)
|
||||
* Lukas Franke (abcdefg30)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Oliver Brakmann (obrakmann)
|
||||
* Paul Chote (pchote)
|
||||
* Reaperrr
|
||||
@@ -16,6 +15,7 @@ Previous developers included:
|
||||
* Caleb Anderson (RobotCaleb)
|
||||
* Curtis Shmyr (hamb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Megan Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Pavel Penev (penev92)
|
||||
@@ -89,6 +89,7 @@ Also thanks to:
|
||||
* Lesueur Benjamin (Valkirie)
|
||||
* Maarten Meuris (Nyerguds)
|
||||
* Mark Olson (markolson)
|
||||
* Markus Hartung (hartmark)
|
||||
* Matija Hustic (matija-hustic)
|
||||
* Matthew Gatland (mgatland)
|
||||
* Matthew Uzzell (MUzzell)
|
||||
@@ -170,6 +171,8 @@ Krueger and distributed under the GNU GPL terms.
|
||||
Using SmartIrc4Net developed by Mirco Bauer
|
||||
distributed under the LGPL version 2.1 or later.
|
||||
|
||||
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
||||
distributed under MIT License.
|
||||
|
||||
Finally, special thanks goes to the original teams
|
||||
at Westwood Studios and EA for creating the classic
|
||||
|
||||
@@ -29,7 +29,7 @@ To compile OpenRA, run `make all` from the command line.
|
||||
|
||||
Run with either `launch-game.sh` or `mono --debug OpenRA.Game.exe`.
|
||||
|
||||
Type `sudo make install-all` for system wide installation. Run `make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run from the `openra` shortcut.
|
||||
Type `sudo make install` for system wide installation. Run `make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run from the `openra` shortcut.
|
||||
|
||||
Debian/Ubuntu
|
||||
-------------
|
||||
|
||||
256
Makefile
256
Makefile
@@ -3,9 +3,6 @@
|
||||
# to compile, run:
|
||||
# make [DEBUG=false]
|
||||
#
|
||||
# to compile with development tools, run:
|
||||
# make all [DEBUG=false]
|
||||
#
|
||||
# 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
|
||||
@@ -17,18 +14,16 @@
|
||||
# to check the official mod dlls for StyleCop violations, run:
|
||||
# make check
|
||||
#
|
||||
# to generate documentation aimed at modders, run:
|
||||
# make docs
|
||||
#
|
||||
# to install, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install
|
||||
#
|
||||
# to install with development tools, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install-all
|
||||
#
|
||||
# to install Linux startup scripts, desktop files and icons:
|
||||
# make install-linux-shortcuts [DEBUG=false]
|
||||
#
|
||||
# to install the engine and common mod files (omitting the default mods):
|
||||
# make install-engine
|
||||
# make install-common-mod-files
|
||||
#
|
||||
# to uninstall, run:
|
||||
# make uninstall
|
||||
#
|
||||
@@ -46,7 +41,7 @@ SDK ?=
|
||||
CSC = mcs $(SDK)
|
||||
CSFLAGS = -nologo -warn:4 -codepage:utf8 -unsafe -warnaserror
|
||||
DEFINE = TRACE
|
||||
COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Xml.dll thirdparty/download/ICSharpCode.SharpZipLib.dll thirdparty/download/FuzzyLogicLibrary.dll thirdparty/download/MaxMind.Db.dll thirdparty/download/Eluant.dll thirdparty/download/SmarIrc4net.dll
|
||||
COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Xml.dll thirdparty/download/ICSharpCode.SharpZipLib.dll thirdparty/download/FuzzyLogicLibrary.dll thirdparty/download/MaxMind.Db.dll thirdparty/download/Eluant.dll thirdparty/download/SmarIrc4net.dll thirdparty/download/rix0rrr.BeaconLib.dll
|
||||
NUNIT_LIBS_PATH :=
|
||||
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
|
||||
|
||||
@@ -88,7 +83,6 @@ INSTALL_DATA = $(INSTALL) -m644
|
||||
|
||||
# program targets
|
||||
CORE = pdefault game utility server
|
||||
TOOLS = gamemonitor
|
||||
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
|
||||
|
||||
# dependencies
|
||||
@@ -109,7 +103,6 @@ game_SRCS := $(shell find OpenRA.Game/ -iname '*.cs')
|
||||
game_TARGET = OpenRA.Game.exe
|
||||
game_KIND = winexe
|
||||
game_LIBS = $(COMMON_LIBS) $(game_DEPS) thirdparty/download/SharpFont.dll thirdparty/download/Open.Nat.dll
|
||||
game_FLAGS = -win32icon:OpenRA.Game/OpenRA.ico
|
||||
PROGRAMS += game
|
||||
game: $(game_TARGET)
|
||||
|
||||
@@ -127,7 +120,7 @@ mod_common_SRCS := $(shell find OpenRA.Mods.Common/ -iname '*.cs')
|
||||
mod_common_TARGET = mods/common/OpenRA.Mods.Common.dll
|
||||
mod_common_KIND = library
|
||||
mod_common_DEPS = $(game_TARGET)
|
||||
mod_common_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) thirdparty/download/StyleCop.dll thirdparty/download/StyleCop.CSharp.dll thirdparty/download/StyleCop.CSharp.Rules.dll
|
||||
mod_common_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS)
|
||||
PROGRAMS += mod_common
|
||||
mod_common: $(mod_common_TARGET)
|
||||
|
||||
@@ -170,37 +163,34 @@ check-scripts:
|
||||
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
|
||||
@luac -p $(shell find lua/* -iname '*.lua')
|
||||
|
||||
check: utility mods
|
||||
check: utility stylecheck mods
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Game..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Game
|
||||
@mono --debug OpenRA.StyleCheck.exe OpenRA.Game
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Platforms.Default..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Platforms.Default
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.GameMonitor..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.GameMonitor
|
||||
@mono --debug OpenRA.StyleCheck.exe OpenRA.Platforms.Default
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.Common..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.Common
|
||||
@mono --debug OpenRA.StyleCheck.exe OpenRA.Mods.Common
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.Cnc..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.Cnc
|
||||
@mono --debug OpenRA.StyleCheck.exe OpenRA.Mods.Cnc
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.D2k..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.D2k
|
||||
@mono --debug OpenRA.StyleCheck.exe OpenRA.Mods.D2k
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Utility..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Utility
|
||||
@mono --debug OpenRA.StyleCheck.exe OpenRA.Utility
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Test..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Test
|
||||
@mono --debug OpenRA.StyleCheck.exe OpenRA.Test
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Server..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Server
|
||||
@mono --debug OpenRA.StyleCheck.exe OpenRA.Server
|
||||
|
||||
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)
|
||||
@@ -238,16 +228,6 @@ test: utility mods
|
||||
|
||||
##### Launchers / Utilities #####
|
||||
|
||||
gamemonitor_SRCS := $(shell find OpenRA.GameMonitor/ -iname '*.cs')
|
||||
gamemonitor_TARGET = OpenRA.exe
|
||||
gamemonitor_KIND = winexe
|
||||
gamemonitor_DEPS = $(game_TARGET)
|
||||
gamemonitor_LIBS = $(COMMON_LIBS) $(gamemonitor_DEPS) System.Windows.Forms.dll
|
||||
gamemonitor_FLAGS = -win32icon:OpenRA.Game/OpenRA.ico
|
||||
PROGRAMS += gamemonitor
|
||||
gamemonitor: $(gamemonitor_TARGET)
|
||||
|
||||
# Backend for the launcher apps - queries game/mod info and applies actions to an install
|
||||
utility_SRCS := $(shell find OpenRA.Utility/ -iname '*.cs')
|
||||
utility_TARGET = OpenRA.Utility.exe
|
||||
utility_KIND = exe
|
||||
@@ -256,6 +236,13 @@ utility_LIBS = $(COMMON_LIBS) $(utility_DEPS) thirdparty/download/ICSharpCode.Sh
|
||||
PROGRAMS += utility
|
||||
utility: $(utility_TARGET)
|
||||
|
||||
stylecheck_SRCS := $(shell find OpenRA.StyleCheck/ -iname '*.cs')
|
||||
stylecheck_TARGET = OpenRA.StyleCheck.exe
|
||||
stylecheck_KIND = exe
|
||||
stylecheck_LIBS = thirdparty/download/StyleCop.dll thirdparty/download/StyleCop.CSharp.dll thirdparty/download/StyleCop.CSharp.Rules.dll
|
||||
PROGRAMS += stylecheck
|
||||
stylecheck: $(stylecheck_TARGET)
|
||||
|
||||
# Dedicated server
|
||||
server_SRCS := $(shell find OpenRA.Server/ -iname '*.cs')
|
||||
server_TARGET = OpenRA.Server.exe
|
||||
@@ -297,13 +284,9 @@ default: core
|
||||
|
||||
core: dependencies game platforms mods utility server
|
||||
|
||||
tools: gamemonitor
|
||||
|
||||
package: all-dependencies core tools docs version
|
||||
|
||||
mods: mod_common mod_cnc mod_d2k
|
||||
|
||||
all: dependencies core tools
|
||||
all: dependencies core stylecheck
|
||||
|
||||
clean:
|
||||
@-$(RM_F) *.exe *.dll *.dylib *.dll.config ./OpenRA*/*.dll ./OpenRA*/*.mdb *.mdb mods/**/*.dll mods/**/*.mdb *.resources
|
||||
@@ -317,62 +300,49 @@ cli-dependencies:
|
||||
@ $(CP_R) thirdparty/download/*.dll .
|
||||
@ $(CP_R) thirdparty/download/*.dll.config .
|
||||
|
||||
linux-dependencies: cli-dependencies linux-native-dependencies
|
||||
linux-dependencies: cli-dependencies geoip-dependencies linux-native-dependencies
|
||||
|
||||
linux-native-dependencies:
|
||||
@./thirdparty/configure-native-deps.sh
|
||||
|
||||
windows-dependencies:
|
||||
windows-dependencies: cli-dependencies geoip-dependencies
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh
|
||||
|
||||
osx-dependencies: cli-dependencies
|
||||
osx-dependencies: cli-dependencies geoip-dependencies
|
||||
@./thirdparty/fetch-thirdparty-deps-osx.sh
|
||||
@ $(CP_R) thirdparty/download/osx/*.dylib .
|
||||
@ $(CP_R) thirdparty/download/osx/*.dll.config .
|
||||
|
||||
dependencies: $(os-dependencies)
|
||||
geoip-dependencies:
|
||||
@./thirdparty/fetch-geoip-db.sh
|
||||
@ $(CP) thirdparty/download/GeoLite2-Country.mmdb.gz .
|
||||
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
|
||||
dependencies: $(os-dependencies)
|
||||
|
||||
version: mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modchooser/mod.yaml mods/all/mod.yaml
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies geoip-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("\tmodchooser:.*$$","\tmodchooser: $(VERSION)"); print $0}' $${i}.tmp > $${i}.tmp2 && \
|
||||
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp2 > $${i} && \
|
||||
rm $${i}.tmp $${i}.tmp2; \
|
||||
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp > $${i} && \
|
||||
rm $${i}.tmp; \
|
||||
done
|
||||
|
||||
docs: utility mods version
|
||||
@mono --debug OpenRA.Utility.exe all --docs > DOCUMENTATION.md
|
||||
@mono --debug OpenRA.Utility.exe all --lua-docs > Lua-API.md
|
||||
|
||||
man-page: utility mods
|
||||
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
|
||||
|
||||
install: install-core
|
||||
|
||||
install-all: install-core install-tools
|
||||
install: default install-core
|
||||
|
||||
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
|
||||
|
||||
install-core: default
|
||||
@-echo "Installing OpenRA to $(DATA_INSTALL_DIR)"
|
||||
install-engine:
|
||||
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) $(foreach prog,$(CORE),$($(prog)_TARGET)) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_common_TARGET) "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(INSTALL_PROGRAM) $(mod_cnc_TARGET) "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(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) $(mod_d2k_TARGET) "$(DATA_INSTALL_DIR)/mods/d2k"
|
||||
@$(CP_R) mods/modchooser "$(DATA_INSTALL_DIR)/mods/"
|
||||
|
||||
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
|
||||
@$(INSTALL_DATA) "GeoLite2-Country.mmdb.gz" "$(DATA_INSTALL_DIR)/GeoLite2-Country.mmdb.gz"
|
||||
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
|
||||
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
|
||||
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
|
||||
|
||||
@@ -388,15 +358,27 @@ install-core: default
|
||||
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) SmarIrc4net.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
ifneq ($(UNAME_S),Darwin)
|
||||
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) $(mod_common_TARGET) "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(INSTALL_PROGRAM) $(mod_cnc_TARGET) "$(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) $(mod_d2k_TARGET) "$(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)"
|
||||
endif
|
||||
|
||||
install-tools: tools
|
||||
@-echo "Installing OpenRA tools to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) $(foreach prog,$(TOOLS),$($(prog)_TARGET)) "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-linux-icons:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/"
|
||||
@@ -404,70 +386,101 @@ install-linux-icons:
|
||||
|
||||
install-linux-desktop:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MOD}/ra/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-ra.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MOD}/cnc/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-cnc.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MOD}/d2k/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Dune 2000/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/{MOD}/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.xml"
|
||||
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-join-servers.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-replays.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-launch-mod.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MOD}/ra/g' packaging/linux/openra-join-servers.desktop.in | sed 's/{MODNAME}/Red Alert/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-ra-join-servers.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-ra-join-servers.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MOD}/cnc/g' packaging/linux/openra-join-servers.desktop.in | sed 's/{MODNAME}/Tiberian Dawn/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-cnc-join-servers.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-cnc-join-servers.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MOD}/d2k/g' packaging/linux/openra-join-servers.desktop.in | sed 's/{MODNAME}/Dune 2000/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-d2k-join-servers.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-d2k-join-servers.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@-$(RM) packaging/linux/openra-mimeinfo.xml packaging/linux/openra-ra-join-servers.desktop packaging/linux/openra-cnc-join-servers.desktop packaging/linux/openra-d2k-join-servers.desktop
|
||||
|
||||
install-linux-appdata:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
|
||||
@$(INSTALL_DATA) packaging/linux/openra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MOD}/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/{MOD}/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/{MOD}/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: man-page
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
|
||||
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
|
||||
|
||||
install-linux-scripts:
|
||||
@echo "#!/bin/sh" > openra
|
||||
@echo 'cd "$(gameinstalldir)"' >> openra
|
||||
# Note: this relies on the non-standard -f flag implemented by gnu readlink
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@echo 'mono OpenRA.Game.exe Engine.LaunchPath="$$(readlink -f $$0)" "$$@"' >> openra
|
||||
@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
|
||||
@echo 'mono --debug OpenRA.Game.exe Engine.LaunchPath="$$(readlink -f $$0)" "$$@"' >> openra
|
||||
endif
|
||||
@echo 'if [ $$? != 0 -a $$? != 1 ]' >> openra
|
||||
@echo 'then' >> openra
|
||||
@echo 'ZENITY=`which zenity` || echo "OpenRA needs zenity installed to display a graphical error dialog. See ~/.openra. for log files."' >> openra
|
||||
@echo '$$ZENITY --question --title "OpenRA" --text "OpenRA has encountered a fatal error.\nLog Files are available in ~/.openra." --ok-label "Quit" --cancel-label "View FAQ" || xdg-open https://github.com/OpenRA/OpenRA/wiki/FAQ' >> openra
|
||||
@echo 'exit 1' >> openra
|
||||
@echo 'fi' >> openra
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx openra "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) openra
|
||||
|
||||
@echo "#!/bin/sh" > openra-server
|
||||
@echo 'cd "$(gameinstalldir)"' >> openra-server
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@echo 'mono OpenRA.Server.exe "$$@"' >> openra-server
|
||||
else
|
||||
@echo 'mono --debug OpenRA.Server.exe "$$@"' >> openra-server
|
||||
@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/{MOD}/ra/g' packaging/linux/openra.debug.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra
|
||||
@sed 's/{MOD}/cnc/g' packaging/linux/openra.debug.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc
|
||||
@sed 's/{MOD}/d2k/g' packaging/linux/openra.debug.in | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx openra-server "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) openra-server
|
||||
@$(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/{MOD}/ra/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra-server
|
||||
@sed 's/{MOD}/cnc/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc-server
|
||||
@sed 's/{MOD}/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"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-server"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-join-servers.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-launch-mod.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-join-servers.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/16x16/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/32x32/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/48x48/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/64x64/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/128x128/apps/openra.png"
|
||||
@-$(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"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-ra-join-servers.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-cnc-join-servers.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-d2k-join-servers.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/16x16/apps/openra-ra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/32x32/apps/openra-ra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/48x48/apps/openra-ra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/64x64/apps/openra-ra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/128x128/apps/openra-ra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/16x16/apps/openra-cnc.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/32x32/apps/openra-cnc.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/48x48/apps/openra-cnc.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/64x64/apps/openra-cnc.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/128x128/apps/openra-cnc.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/16x16/apps/openra-d2k.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/32x32/apps/openra-d2k.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/48x48/apps/openra-d2k.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/64x64/apps/openra-d2k.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/128x128/apps/openra-d2k.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
|
||||
@@ -476,9 +489,6 @@ help:
|
||||
@echo 'to compile, run:'
|
||||
@echo ' make [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to compile with development tools, run:'
|
||||
@echo ' make all [DEBUG=false]'
|
||||
@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'
|
||||
@@ -488,15 +498,9 @@ help:
|
||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||
@echo ' make test'
|
||||
@echo
|
||||
@echo 'to generate documentation aimed at modders, run:'
|
||||
@echo ' make docs'
|
||||
@echo
|
||||
@echo 'to install, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
|
||||
@echo
|
||||
@echo 'to install with development tools, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install-all'
|
||||
@echo
|
||||
@echo 'to install Linux startup scripts, desktop files and icons'
|
||||
@echo ' make install-linux-shortcuts [DEBUG=false]'
|
||||
@echo
|
||||
@@ -515,4 +519,4 @@ help:
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: core tools package all mods clean distclean dependencies version $(PROGRAMS) nunit
|
||||
.PHONY: core package all mods clean distclean dependencies version $(PROGRAMS) nunit
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace OpenRA.Activities
|
||||
/// </summary>
|
||||
protected virtual void OnLastRun(Actor self) { }
|
||||
|
||||
public virtual bool Cancel(Actor self)
|
||||
public virtual bool Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
if (!IsInterruptible)
|
||||
return false;
|
||||
@@ -208,9 +208,11 @@ namespace OpenRA.Activities
|
||||
if (ChildActivity != null && !ChildActivity.Cancel(self))
|
||||
return false;
|
||||
|
||||
State = ActivityState.Canceled;
|
||||
NextActivity = null;
|
||||
if (!keepQueue)
|
||||
NextActivity = null;
|
||||
|
||||
ChildActivity = null;
|
||||
State = ActivityState.Canceled;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace OpenRA
|
||||
|
||||
public Activity CurrentActivity { get; private set; }
|
||||
|
||||
public Group Group;
|
||||
public int Generation;
|
||||
|
||||
public Rectangle Bounds { get; private set; }
|
||||
|
||||
@@ -16,10 +16,12 @@ using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
[Flags]
|
||||
enum ModRegistration { User = 1, System = 2 }
|
||||
|
||||
public class ExternalMod
|
||||
{
|
||||
public readonly string Id;
|
||||
@@ -38,38 +40,37 @@ namespace OpenRA
|
||||
{
|
||||
readonly Dictionary<string, ExternalMod> mods = new Dictionary<string, ExternalMod>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
readonly string launchPath;
|
||||
|
||||
public ExternalMods(string launchPath)
|
||||
public ExternalMods()
|
||||
{
|
||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||
if (launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath.Substring(1, launchPath.Length - 2);
|
||||
|
||||
this.launchPath = launchPath;
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, 256);
|
||||
|
||||
// Load registered mods
|
||||
var supportPath = Platform.ResolvePath(Path.Combine("^", "ModMetadata"));
|
||||
if (!Directory.Exists(supportPath))
|
||||
return;
|
||||
|
||||
foreach (var path in Directory.GetFiles(supportPath, "*.yaml"))
|
||||
// If the player has defined a local support directory (in the game directory)
|
||||
// then this will override both the regular and system support dirs
|
||||
var sources = new[] { Platform.SystemSupportDir, Platform.SupportDir };
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
try
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
continue;
|
||||
|
||||
foreach (var path in Directory.GetFiles(metadataPath, "*.yaml"))
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
LoadMod(yaml);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
LoadMod(yaml, path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadMod(MiniYaml yaml)
|
||||
void LoadMod(MiniYaml yaml, string path = null)
|
||||
{
|
||||
var mod = FieldLoader.Load<ExternalMod>(yaml);
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
@@ -80,10 +81,13 @@ namespace OpenRA
|
||||
mod.Icon = sheetBuilder.Add(bitmap);
|
||||
}
|
||||
|
||||
mods.Add(ExternalMod.MakeKey(mod), mod);
|
||||
// Avoid possibly overwriting a valid mod with an obviously bogus one
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
if (File.Exists(mod.LaunchPath) && (path == null || Path.GetFileNameWithoutExtension(path) == key))
|
||||
mods[key] = mod;
|
||||
}
|
||||
|
||||
internal void Register(Manifest mod)
|
||||
internal void Register(Manifest mod, string launchPath, ModRegistration registration)
|
||||
{
|
||||
if (mod.Metadata.Hidden)
|
||||
return;
|
||||
@@ -107,41 +111,87 @@ namespace OpenRA
|
||||
}))
|
||||
};
|
||||
|
||||
var supportPath = Platform.ResolvePath(Path.Combine("^", "ModMetadata"));
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.SystemSupportDir);
|
||||
|
||||
try
|
||||
{
|
||||
// Make sure the mod is available for this session, even if saving it fails
|
||||
LoadMod(yaml.First().Value);
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
sources.Add(Platform.SupportDir);
|
||||
|
||||
Directory.CreateDirectory(supportPath);
|
||||
File.WriteAllLines(Path.Combine(supportPath, key + ".yaml"), yaml.ToLines(false).ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to register currrent mod metadata");
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
// Make sure the mod is available for this session, even if saving it fails
|
||||
LoadMod(yaml.First().Value);
|
||||
|
||||
// Clean up stale mod registrations:
|
||||
// - LaunchPath no longer exists (uninstalled)
|
||||
// - LaunchPath and mod match the current mod, but version differs (newer version installed on top)
|
||||
List<string> toRemove = null;
|
||||
foreach (var kv in mods)
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var k = kv.Key;
|
||||
var m = kv.Value;
|
||||
if (!File.Exists(m.LaunchPath) || (m.LaunchPath == launchPath && m.Id == mod.Id && k != key))
|
||||
if (!Directory.Exists(source))
|
||||
continue;
|
||||
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
|
||||
try
|
||||
{
|
||||
Log.Write("debug", "Removing stale mod metadata entry '{0}'", k);
|
||||
if (toRemove == null)
|
||||
toRemove = new List<string>();
|
||||
Directory.CreateDirectory(metadataPath);
|
||||
File.WriteAllLines(Path.Combine(metadataPath, key + ".yaml"), yaml.ToLines(false).ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to register current mod metadata");
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toRemove.Add(k);
|
||||
var path = Path.Combine(supportPath, k + ".yaml");
|
||||
/// <summary>
|
||||
/// Removes invalid mod registrations:
|
||||
/// * LaunchPath no longer exists
|
||||
/// * LaunchPath and mod id matches the active mod, but the version is different
|
||||
/// * Filename doesn't match internal key
|
||||
/// * Fails to parse as a mod registration
|
||||
/// </summary>
|
||||
internal void ClearInvalidRegistrations(ExternalMod activeMod, ModRegistration registration)
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.SystemSupportDir);
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
sources.Add(Platform.SupportDir);
|
||||
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
continue;
|
||||
|
||||
foreach (var path in Directory.GetFiles(metadataPath, "*.yaml"))
|
||||
{
|
||||
string modKey = null;
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
var m = FieldLoader.Load<ExternalMod>(yaml);
|
||||
modKey = ExternalMod.MakeKey(m);
|
||||
|
||||
// Continue to the next entry if this one is valid
|
||||
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey &&
|
||||
!(activeMod != null && m.LaunchPath == activeMod.LaunchPath && m.Id == activeMod.Id && m.Version != activeMod.Version))
|
||||
continue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
|
||||
// Remove from the ingame mod switcher
|
||||
if (Path.GetFileNameWithoutExtension(path) == modKey)
|
||||
mods.Remove(modKey);
|
||||
|
||||
// Remove stale or corrupted metadata
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
Log.Write("debug", "Removed invalid mod metadata file '{0}'", path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -150,10 +200,34 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
foreach (var r in toRemove)
|
||||
mods.Remove(r);
|
||||
internal void Unregister(Manifest mod, ModRegistration registration)
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.SystemSupportDir);
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
sources.Add(Platform.SupportDir);
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
mods.Remove(key);
|
||||
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var path = Path.Combine(source, "ModMetadata", key + ".yaml");
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ExternalMod this[string key] { get { return mods[key]; } }
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace OpenRA
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
int rr, rp, ry;
|
||||
if (Exts.TryParseIntegerInvariant(value, out rr) && Exts.TryParseIntegerInvariant(value, out rp) && Exts.TryParseIntegerInvariant(value, out ry))
|
||||
if (Exts.TryParseIntegerInvariant(parts[0], out rr) && Exts.TryParseIntegerInvariant(parts[1], out rp) && Exts.TryParseIntegerInvariant(parts[2], out ry))
|
||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||
}
|
||||
}
|
||||
@@ -398,13 +398,29 @@ namespace OpenRA
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(ConditionExpression))
|
||||
else if (fieldType == typeof(BooleanExpression))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ConditionExpression(value);
|
||||
return new BooleanExpression(value);
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(IntegerExpression))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new IntegerExpression(value);
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace OpenRA.FileFormats
|
||||
case "PLTE":
|
||||
{
|
||||
palette = new Color[256];
|
||||
for (var i = 0; i < 256; i++)
|
||||
for (var i = 0; i < length / 3; i++)
|
||||
{
|
||||
var r = cr.ReadByte(); var g = cr.ReadByte(); var b = cr.ReadByte();
|
||||
palette[i] = Color.FromArgb(r, g, b);
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class BagFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Stream s;
|
||||
readonly Dictionary<string, IdxEntry> index;
|
||||
|
||||
public BagFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
// A bag file is always accompanied with an .idx counterpart
|
||||
// For example: audio.bag requires the audio.idx file
|
||||
var indexFilename = Path.ChangeExtension(filename, ".idx");
|
||||
|
||||
// Build the index and dispose the stream, it is no longer needed after this
|
||||
List<IdxEntry> entries;
|
||||
using (var indexStream = context.Open(indexFilename))
|
||||
entries = new IdxReader(indexStream).Entries;
|
||||
|
||||
index = entries.ToDictionaryWithConflictLog(x => x.Filename,
|
||||
"{0} (bag format)".F(filename),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
|
||||
|
||||
s = context.Open(filename);
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
IdxEntry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
s.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
|
||||
var waveHeaderMemoryStream = new MemoryStream();
|
||||
|
||||
var channels = (entry.Flags & 1) > 0 ? 2 : 1;
|
||||
|
||||
if ((entry.Flags & 2) > 0)
|
||||
{
|
||||
// PCM
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 36));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(16));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)1));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(2 * channels * entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)(2 * channels)));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)16));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
if ((entry.Flags & 8) > 0)
|
||||
{
|
||||
// IMA ADPCM
|
||||
var samplesPerChunk = (2 * (entry.ChunkSize - 4)) + 1;
|
||||
var bytesPerSec = (int)Math.Floor(((double)(2 * entry.ChunkSize) / samplesPerChunk) * ((double)entry.SampleRate / 2));
|
||||
var chunkSize = entry.ChunkSize > entry.Length ? entry.Length : entry.ChunkSize;
|
||||
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 52));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(20));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)17));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(bytesPerSec));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)chunkSize));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)2));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)samplesPerChunk));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fact"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4 * entry.Length));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
waveHeaderMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Construct a merged stream
|
||||
var mergedStream = new MergedStream(waveHeaderMemoryStream, s);
|
||||
mergedStream.SetLength(waveHeaderMemoryStream.Length + entry.Length);
|
||||
|
||||
return mergedStream;
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class BigFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
|
||||
public BigFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
if (s.ReadASCII(4) != "BIGF")
|
||||
throw new InvalidDataException("Header is not BIGF");
|
||||
|
||||
// Total archive size.
|
||||
s.ReadUInt32();
|
||||
|
||||
var entryCount = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
entryCount = int2.Swap(entryCount);
|
||||
|
||||
// First entry offset? This is apparently bogus for EA's .big files
|
||||
// and we don't have to try seeking there since the entries typically start next in EA's .big files.
|
||||
s.ReadUInt32();
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entry = new Entry(s);
|
||||
index.Add(entry.Path, entry);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
class Entry
|
||||
{
|
||||
readonly Stream s;
|
||||
readonly uint offset;
|
||||
readonly uint size;
|
||||
public readonly string Path;
|
||||
|
||||
public Entry(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
|
||||
offset = s.ReadUInt32();
|
||||
size = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
offset = int2.Swap(offset);
|
||||
size = int2.Swap(size);
|
||||
}
|
||||
|
||||
Path = s.ReadASCIIZ();
|
||||
}
|
||||
|
||||
public Stream GetData()
|
||||
{
|
||||
s.Position = offset;
|
||||
return new MemoryStream(s.ReadBytes((int)size));
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
return index[filename].GetData();
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class D2kSoundResources : IReadOnlyPackage
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
public Entry(uint offset, uint length)
|
||||
{
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Stream s;
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
|
||||
public D2kSoundResources(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
var headerLength = s.ReadUInt32();
|
||||
while (s.Position < headerLength + 4)
|
||||
{
|
||||
var name = s.ReadASCIIZ();
|
||||
var offset = s.ReadUInt32();
|
||||
var length = s.ReadUInt32();
|
||||
index.Add(name, new Entry(offset, length));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(e.Offset, SeekOrigin.Begin);
|
||||
return new MemoryStream(s.ReadBytes((int)e.Length));
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,82 +34,63 @@ namespace OpenRA.FileSystem
|
||||
// Mod packages that should not be disposed
|
||||
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
|
||||
readonly IReadOnlyDictionary<string, Manifest> installedMods;
|
||||
readonly IPackageLoader[] packageLoaders;
|
||||
|
||||
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||
|
||||
public FileSystem(IReadOnlyDictionary<string, Manifest> installedMods)
|
||||
public FileSystem(IReadOnlyDictionary<string, Manifest> installedMods, IPackageLoader[] packageLoaders)
|
||||
{
|
||||
this.installedMods = installedMods;
|
||||
this.packageLoaders = packageLoaders
|
||||
.Append(new ZipFileLoader())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public bool TryParsePackage(Stream stream, string filename, out IReadOnlyPackage package)
|
||||
{
|
||||
package = null;
|
||||
foreach (var packageLoader in packageLoaders)
|
||||
if (packageLoader.TryParsePackage(stream, filename, this, out package))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename)
|
||||
{
|
||||
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new MixFile(this, filename);
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".oramod", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new D2kSoundResources(this, filename);
|
||||
if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new InstallShieldPackage(this, filename);
|
||||
if (filename.EndsWith(".PAK", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new PakFile(this, filename);
|
||||
if (filename.EndsWith(".big", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new BigFile(this, filename);
|
||||
if (filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new BagFile(this, filename);
|
||||
// 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))
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Children of another package require special handling
|
||||
IReadOnlyPackage parent;
|
||||
string subPath = null;
|
||||
if (TryGetPackageContaining(filename, out parent, out subPath))
|
||||
return OpenPackage(subPath, parent);
|
||||
return parent.OpenPackage(subPath, this);
|
||||
|
||||
return new Folder(Platform.ResolvePath(filename));
|
||||
}
|
||||
// Try and open it normally
|
||||
IReadOnlyPackage package;
|
||||
var stream = Open(filename);
|
||||
if (TryParsePackage(stream, filename, out package))
|
||||
return package;
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, IReadOnlyPackage parent)
|
||||
{
|
||||
// HACK: limit support to zip and folder until we generalize the PackageLoader support
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
using (var s = parent.GetStream(filename))
|
||||
return new ZipFile(s, filename, parent);
|
||||
}
|
||||
|
||||
if (parent is ZipFile)
|
||||
return new ZipFolder(this, (ZipFile)parent, filename, filename);
|
||||
|
||||
if (parent is ZipFolder)
|
||||
{
|
||||
var folder = (ZipFolder)parent;
|
||||
return new ZipFolder(this, folder.Parent, folder.Name + "/" + filename, filename);
|
||||
}
|
||||
|
||||
if (parent is Folder)
|
||||
{
|
||||
var subFolder = Platform.ResolvePath(Path.Combine(parent.Name, filename));
|
||||
if (Directory.Exists(subFolder))
|
||||
return new Folder(subFolder);
|
||||
}
|
||||
// No package loaders took ownership of the stream, so clean it up
|
||||
stream.Dispose();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Mount(string name, string explicitName = null)
|
||||
{
|
||||
var optional = name.StartsWith("~");
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
if (optional)
|
||||
name = name.Substring(1);
|
||||
|
||||
try
|
||||
{
|
||||
IReadOnlyPackage package;
|
||||
if (name.StartsWith("$"))
|
||||
if (name.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
name = name.Substring(1);
|
||||
|
||||
@@ -299,5 +280,40 @@ namespace OpenRA.FileSystem
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a filesystem for an assembly, accounting for explicit and mod mounts.
|
||||
/// Assemblies must exist in the native OS file system (not inside an OpenRA-defined package).
|
||||
/// </summary>
|
||||
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
var parent = path.Substring(0, explicitSplit);
|
||||
var filename = path.Substring(explicitSplit + 1);
|
||||
|
||||
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
|
||||
if (parentPath == null)
|
||||
return null;
|
||||
|
||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out mod))
|
||||
return null;
|
||||
|
||||
if (!(mod.Package is Folder))
|
||||
return null;
|
||||
|
||||
path = Path.Combine(mod.Package.Name, filename);
|
||||
}
|
||||
else
|
||||
path = Path.Combine(parentPath, filename);
|
||||
}
|
||||
|
||||
var resolvedPath = Platform.ResolvePath(path);
|
||||
return File.Exists(resolvedPath) ? resolvedPath : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,30 @@ namespace OpenRA.FileSystem
|
||||
return combined.StartsWith(path, StringComparison.Ordinal) && File.Exists(combined);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
var resolvedPath = Platform.ResolvePath(Path.Combine(Name, filename));
|
||||
if (Directory.Exists(resolvedPath))
|
||||
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))
|
||||
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))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
{
|
||||
// HACK: ZipFiles can't be loaded as read-write from a stream, so we are
|
||||
|
||||
@@ -15,12 +15,23 @@ using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public interface IPackageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempt to parse a stream as this type of package.
|
||||
/// If successful, the loader is expected to take ownership of `s` and dispose it once done.
|
||||
/// If unsuccessful, the loader is expected to return the stream position to where it started.
|
||||
/// </summary>
|
||||
bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package);
|
||||
}
|
||||
|
||||
public interface IReadOnlyPackage : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
IEnumerable<string> Contents { get; }
|
||||
Stream GetStream(string filename);
|
||||
bool Contains(string filename);
|
||||
IReadOnlyPackage OpenPackage(string filename, FileSystem context);
|
||||
}
|
||||
|
||||
public interface IReadWritePackage : IReadOnlyPackage
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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 OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class InstallShieldPackage : IReadOnlyPackage
|
||||
{
|
||||
public struct Entry
|
||||
{
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
public Entry(uint offset, uint length)
|
||||
{
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
readonly long dataStart = 255;
|
||||
|
||||
public InstallShieldPackage(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
// Parse package header
|
||||
var signature = s.ReadUInt32();
|
||||
if (signature != 0x8C655D13)
|
||||
throw new InvalidDataException("Not an Installshield package");
|
||||
|
||||
s.Position += 8;
|
||||
/*var FileCount = */s.ReadUInt16();
|
||||
s.Position += 4;
|
||||
/*var ArchiveSize = */s.ReadUInt32();
|
||||
s.Position += 19;
|
||||
var tocAddress = s.ReadInt32();
|
||||
s.Position += 4;
|
||||
var dirCount = s.ReadUInt16();
|
||||
|
||||
// Parse the directory list
|
||||
s.Position = tocAddress;
|
||||
|
||||
// Parse directories
|
||||
var directories = new Dictionary<string, uint>();
|
||||
for (var i = 0; i < dirCount; i++)
|
||||
{
|
||||
// Parse directory header
|
||||
var fileCount = s.ReadUInt16();
|
||||
var chunkSize = s.ReadUInt16();
|
||||
var nameLength = s.ReadUInt16();
|
||||
var dirName = s.ReadASCII(nameLength);
|
||||
|
||||
// Skip to the end of the chunk
|
||||
s.ReadBytes(chunkSize - nameLength - 6);
|
||||
directories.Add(dirName, fileCount);
|
||||
}
|
||||
|
||||
// Parse files
|
||||
foreach (var dir in directories)
|
||||
for (var i = 0; i < dir.Value; i++)
|
||||
ParseFile(s, dir.Key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
uint accumulatedData = 0;
|
||||
void ParseFile(Stream s, string dirName)
|
||||
{
|
||||
s.Position += 7;
|
||||
var compressedSize = s.ReadUInt32();
|
||||
s.Position += 12;
|
||||
var chunkSize = s.ReadUInt16();
|
||||
s.Position += 4;
|
||||
var nameLength = s.ReadByte();
|
||||
var fileName = dirName + "\\" + s.ReadASCII(nameLength);
|
||||
|
||||
// Use index syntax to overwrite any duplicate entries with the last value
|
||||
index[fileName] = new Entry(accumulatedData, compressedSize);
|
||||
accumulatedData += compressedSize;
|
||||
|
||||
// Skip to the end of the chunk
|
||||
s.Position += chunkSize - nameLength - 30;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(dataStart + e.Offset, SeekOrigin.Begin);
|
||||
|
||||
var ret = new MemoryStream();
|
||||
Blast.Decompress(s, ret);
|
||||
ret.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, Entry> Index { get { return new ReadOnlyDictionary<string, Entry>(index); } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class MixFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, PackageEntry> index;
|
||||
readonly long dataStart;
|
||||
readonly Stream s;
|
||||
readonly FileSystem context;
|
||||
|
||||
public MixFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.context = context;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
// Detect format type
|
||||
var isCncMix = s.ReadUInt16() != 0;
|
||||
|
||||
// The C&C mix format doesn't contain any flags or encryption
|
||||
var isEncrypted = false;
|
||||
if (!isCncMix)
|
||||
isEncrypted = (s.ReadUInt16() & 0x2) != 0;
|
||||
|
||||
List<PackageEntry> entries;
|
||||
if (isEncrypted)
|
||||
{
|
||||
long unused;
|
||||
entries = ParseHeader(DecryptHeader(s, 4, out dataStart), 0, out unused);
|
||||
}
|
||||
else
|
||||
entries = ParseHeader(s, isCncMix ? 0 : 4, out dataStart);
|
||||
|
||||
index = ParseIndex(entries.ToDictionaryWithConflictLog(x => x.Hash,
|
||||
"{0} ({1} format, Encrypted: {2}, DataStart: {3})".F(filename, isCncMix ? "C&C" : "RA/TS/RA2", isEncrypted, dataStart),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, PackageEntry> ParseIndex(Dictionary<uint, PackageEntry> entries)
|
||||
{
|
||||
var classicIndex = new Dictionary<string, PackageEntry>();
|
||||
var crcIndex = new Dictionary<string, PackageEntry>();
|
||||
var allPossibleFilenames = new HashSet<string>();
|
||||
|
||||
// Try and find a local mix database
|
||||
var dbNameClassic = PackageEntry.HashFilename("local mix database.dat", PackageHashType.Classic);
|
||||
var dbNameCRC = PackageEntry.HashFilename("local mix database.dat", PackageHashType.CRC32);
|
||||
foreach (var kv in entries)
|
||||
{
|
||||
if (kv.Key == dbNameClassic || kv.Key == dbNameCRC)
|
||||
{
|
||||
using (var content = GetContent(kv.Value))
|
||||
{
|
||||
var db = new XccLocalDatabase(content);
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the global mix database
|
||||
// TODO: This should be passed to the mix file ctor
|
||||
if (context.Exists("global mix database.dat"))
|
||||
{
|
||||
using (var db = new XccGlobalDatabase(context.Open("global mix database.dat")))
|
||||
{
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var filename in allPossibleFilenames)
|
||||
{
|
||||
var classicHash = PackageEntry.HashFilename(filename, PackageHashType.Classic);
|
||||
var crcHash = PackageEntry.HashFilename(filename, PackageHashType.CRC32);
|
||||
PackageEntry e;
|
||||
|
||||
if (entries.TryGetValue(classicHash, out e))
|
||||
classicIndex.Add(filename, e);
|
||||
|
||||
if (entries.TryGetValue(crcHash, out e))
|
||||
crcIndex.Add(filename, e);
|
||||
}
|
||||
|
||||
var bestIndex = crcIndex.Count > classicIndex.Count ? crcIndex : classicIndex;
|
||||
|
||||
var unknown = entries.Count - bestIndex.Count;
|
||||
if (unknown > 0)
|
||||
Log.Write("debug", "{0}: failed to resolve filenames for {1} unknown hashes".F(Name, unknown));
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
static List<PackageEntry> ParseHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
var numFiles = s.ReadUInt16();
|
||||
/*uint dataSize = */s.ReadUInt32();
|
||||
|
||||
var items = new List<PackageEntry>();
|
||||
for (var i = 0; i < numFiles; i++)
|
||||
items.Add(new PackageEntry(s));
|
||||
|
||||
headerEnd = offset + 6 + numFiles * PackageEntry.Size;
|
||||
return items;
|
||||
}
|
||||
|
||||
static MemoryStream DecryptHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Decrypt blowfish key
|
||||
var keyblock = s.ReadBytes(80);
|
||||
var blowfishKey = new BlowfishKeyProvider().DecryptKey(keyblock);
|
||||
var fish = new Blowfish(blowfishKey);
|
||||
|
||||
// Decrypt first block to work out the header length
|
||||
var ms = Decrypt(ReadBlocks(s, offset + 80, 1), fish);
|
||||
var numFiles = ms.ReadUInt16();
|
||||
|
||||
// Decrypt the full header - round bytes up to a full block
|
||||
var blockCount = (13 + numFiles * PackageEntry.Size) / 8;
|
||||
headerEnd = offset + 80 + blockCount * 8;
|
||||
|
||||
return Decrypt(ReadBlocks(s, offset + 80, blockCount), fish);
|
||||
}
|
||||
|
||||
static MemoryStream Decrypt(uint[] h, Blowfish fish)
|
||||
{
|
||||
var decrypted = fish.Decrypt(h);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
var writer = new BinaryWriter(ms);
|
||||
foreach (var t in decrypted)
|
||||
writer.Write(t);
|
||||
writer.Flush();
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
|
||||
static uint[] ReadBlocks(Stream s, long offset, int count)
|
||||
{
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException("offset", "Non-negative number required.");
|
||||
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count", "Non-negative number required.");
|
||||
|
||||
if (offset + (count * 2) > s.Length)
|
||||
throw new ArgumentException("Bytes to read {0} and offset {1} greater than stream length {2}.".F(count * 2, offset, s.Length));
|
||||
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// A block is a single encryption unit (represented as two 32-bit integers)
|
||||
var ret = new uint[2 * count];
|
||||
for (var i = 0; i < ret.Length; i++)
|
||||
ret[i] = s.ReadUInt32();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Stream GetContent(PackageEntry entry)
|
||||
{
|
||||
Stream parentStream;
|
||||
var baseOffset = dataStart + entry.Offset;
|
||||
var nestedOffset = baseOffset + SegmentStream.GetOverallNestedOffset(s, out parentStream);
|
||||
|
||||
// Special case FileStream - instead of creating an in-memory copy,
|
||||
// just reference the portion of the on-disk file that we need to save memory.
|
||||
// We use GetType instead of 'is' here since we can't handle any derived classes of FileStream.
|
||||
if (parentStream.GetType() == typeof(FileStream))
|
||||
{
|
||||
var path = ((FileStream)parentStream).Name;
|
||||
return new SegmentStream(File.OpenRead(path), nestedOffset, entry.Length);
|
||||
}
|
||||
|
||||
// For all other streams, create a copy in memory.
|
||||
// This uses more memory but is the only way in general to ensure the returned streams won't clash.
|
||||
s.Seek(baseOffset, SeekOrigin.Begin);
|
||||
return new MemoryStream(s.ReadBytes((int)entry.Length));
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
return GetContent(e);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, PackageEntry> Index
|
||||
{
|
||||
get
|
||||
{
|
||||
var absoluteIndex = index.ToDictionary(e => e.Key, e => new PackageEntry(e.Value.Hash, (uint)(e.Value.Offset + dataStart), e.Value.Length));
|
||||
return new ReadOnlyDictionary<string, PackageEntry>(absoluteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public uint Offset;
|
||||
public uint Length;
|
||||
public string Filename;
|
||||
}
|
||||
|
||||
public sealed class PakFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index;
|
||||
readonly Stream stream;
|
||||
|
||||
public PakFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
index = new Dictionary<string, Entry>();
|
||||
|
||||
stream = context.Open(filename);
|
||||
try
|
||||
{
|
||||
index = new Dictionary<string, Entry>();
|
||||
var offset = stream.ReadUInt32();
|
||||
while (offset != 0)
|
||||
{
|
||||
var file = stream.ReadASCIIZ();
|
||||
var next = stream.ReadUInt32();
|
||||
var length = (next == 0 ? (uint)stream.Length : next) - offset;
|
||||
|
||||
// Ignore duplicate files
|
||||
if (index.ContainsKey(file))
|
||||
continue;
|
||||
|
||||
index.Add(file, new Entry { Offset = offset, Length = length, Filename = file });
|
||||
offset = next;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
stream.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
var data = stream.ReadBytes((int)entry.Length);
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,152 +9,225 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class ZipFile : IReadWritePackage
|
||||
public class ZipFileLoader : IPackageLoader
|
||||
{
|
||||
public IReadWritePackage Parent { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
readonly Stream pkgStream;
|
||||
readonly SZipFile pkg;
|
||||
static readonly string[] Extensions = { ".zip", ".oramap" };
|
||||
|
||||
static ZipFile()
|
||||
class ReadOnlyZipFile : IReadOnlyPackage
|
||||
{
|
||||
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
|
||||
}
|
||||
public string Name { get; protected set; }
|
||||
protected ZipFile pkg;
|
||||
|
||||
public ZipFile(Stream stream, string name, IReadOnlyPackage parent = null)
|
||||
{
|
||||
// SharpZipLib breaks when asked to update archives loaded from outside streams or files
|
||||
// We can work around this by creating a clean in-memory-only file, cutting all outside references
|
||||
pkgStream = new MemoryStream();
|
||||
stream.CopyTo(pkgStream);
|
||||
pkgStream.Position = 0;
|
||||
// Dummy constructor for use with ReadWriteZipFile
|
||||
protected ReadOnlyZipFile() { }
|
||||
|
||||
Name = name;
|
||||
Parent = parent as IReadWritePackage;
|
||||
pkg = new SZipFile(pkgStream);
|
||||
}
|
||||
public ReadOnlyZipFile(Stream s, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
pkg = ZipFileHelper.Create(s);
|
||||
}
|
||||
|
||||
public ZipFile(IReadOnlyFileSystem context, string filename)
|
||||
{
|
||||
string name;
|
||||
IReadOnlyPackage p;
|
||||
if (!context.TryGetPackageContaining(filename, out p, out name))
|
||||
throw new FileNotFoundException("Unable to find parent package for " + filename);
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
var entry = pkg.GetEntry(filename);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
Name = name;
|
||||
Parent = p as IReadWritePackage;
|
||||
using (var z = pkg.GetInputStream(entry))
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
z.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
|
||||
// SharpZipLib breaks when asked to update archives loaded from outside streams or files
|
||||
// We can work around this by creating a clean in-memory-only file, cutting all outside references
|
||||
pkgStream = new MemoryStream();
|
||||
using (var sourceStream = p.GetStream(name))
|
||||
sourceStream.CopyTo(pkgStream);
|
||||
pkgStream.Position = 0;
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
}
|
||||
}
|
||||
|
||||
pkg = new SZipFile(pkgStream);
|
||||
}
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return pkg.GetEntry(filename) != null;
|
||||
}
|
||||
|
||||
ZipFile(string filename, IReadWritePackage parent)
|
||||
{
|
||||
pkgStream = new MemoryStream();
|
||||
public void Dispose()
|
||||
{
|
||||
if (pkg != null)
|
||||
pkg.Close();
|
||||
}
|
||||
|
||||
Name = filename;
|
||||
Parent = parent;
|
||||
pkg = SZipFile.Create(pkgStream);
|
||||
}
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
// Directories are stored with a trailing "/" in the index
|
||||
var entry = pkg.GetEntry(filename) ?? pkg.GetEntry(filename + "/");
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
public static ZipFile Create(string filename, IReadWritePackage parent)
|
||||
{
|
||||
return new ZipFile(filename, parent);
|
||||
}
|
||||
if (entry.IsDirectory)
|
||||
return new ZipFolder(this, filename);
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
var entry = pkg.GetEntry(filename);
|
||||
if (entry == null)
|
||||
// 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))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
return null;
|
||||
|
||||
using (var z = pkg.GetInputStream(entry))
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
z.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
||||
{
|
||||
get
|
||||
readonly MemoryStream pkgStream = new MemoryStream();
|
||||
|
||||
public ReadWriteZipFile(string filename, bool create = false)
|
||||
{
|
||||
foreach (ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
// SharpZipLib breaks when asked to update archives loaded from outside streams or files
|
||||
// We can work around this by creating a clean in-memory-only file, cutting all outside references
|
||||
if (!create)
|
||||
new MemoryStream(File.ReadAllBytes(filename)).CopyTo(pkgStream);
|
||||
|
||||
pkgStream.Position = 0;
|
||||
pkg = ZipFileHelper.Create(pkgStream);
|
||||
Name = filename;
|
||||
}
|
||||
|
||||
void Commit()
|
||||
{
|
||||
var pos = pkgStream.Position;
|
||||
pkgStream.Position = 0;
|
||||
File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length));
|
||||
pkgStream.Position = pos;
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
{
|
||||
pkg.BeginUpdate();
|
||||
pkg.Add(new StaticStreamDataSource(new MemoryStream(contents)), filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
}
|
||||
|
||||
public void Delete(string filename)
|
||||
{
|
||||
pkg.BeginUpdate();
|
||||
pkg.Delete(filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
sealed class ZipFolder : IReadOnlyPackage
|
||||
{
|
||||
return pkg.GetEntry(filename) != null;
|
||||
public string Name { get { return path; } }
|
||||
public ReadOnlyZipFile Parent { get; private set; }
|
||||
readonly string path;
|
||||
|
||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||
{
|
||||
if (path.EndsWith("/", StringComparison.Ordinal))
|
||||
path = path.Substring(0, path.Length - 1);
|
||||
|
||||
Parent = parent;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
// Zip files use '/' as a path separator
|
||||
return Parent.GetStream(path + '/' + filename);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var entry in Parent.Contents)
|
||||
{
|
||||
if (entry.StartsWith(path, StringComparison.Ordinal) && entry != path)
|
||||
{
|
||||
var filename = entry.Substring(path.Length + 1);
|
||||
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
|
||||
if (dirLevels == 1)
|
||||
yield return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return Parent.Contains(path + '/' + filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
return Parent.OpenPackage(path + '/' + filename, context);
|
||||
}
|
||||
|
||||
public void Dispose() { /* nothing to do */ }
|
||||
}
|
||||
|
||||
void Commit()
|
||||
class StaticStreamDataSource : IStaticDataSource
|
||||
{
|
||||
if (Parent == null)
|
||||
throw new InvalidDataException("Cannot update ZipFile without writable parent");
|
||||
readonly Stream s;
|
||||
public StaticStreamDataSource(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
var pos = pkgStream.Position;
|
||||
pkgStream.Position = 0;
|
||||
Parent.Update(Name, pkgStream.ReadBytes((int)pkgStream.Length));
|
||||
pkgStream.Position = pos;
|
||||
public Stream GetSource()
|
||||
{
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
public bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
{
|
||||
pkg.BeginUpdate();
|
||||
pkg.Add(new StaticStreamDataSource(new MemoryStream(contents)), filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new ReadOnlyZipFile(s, filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Delete(string filename)
|
||||
public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package)
|
||||
{
|
||||
pkg.BeginUpdate();
|
||||
pkg.Delete(filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new ReadWriteZipFile(filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public static IReadWritePackage Create(string filename)
|
||||
{
|
||||
if (pkg != null)
|
||||
pkg.Close();
|
||||
|
||||
if (pkgStream != null)
|
||||
pkgStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class StaticStreamDataSource : IStaticDataSource
|
||||
{
|
||||
readonly Stream s;
|
||||
public StaticStreamDataSource(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
public Stream GetSource()
|
||||
{
|
||||
return s;
|
||||
return new ReadWriteZipFile(filename, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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 System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class ZipFolder : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public ZipFile Parent { get; private set; }
|
||||
readonly string path;
|
||||
|
||||
static ZipFolder()
|
||||
{
|
||||
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
|
||||
}
|
||||
|
||||
public ZipFolder(FileSystem context, ZipFile parent, string path, string filename)
|
||||
{
|
||||
if (filename.EndsWith("/"))
|
||||
filename = filename.Substring(0, filename.Length - 1);
|
||||
|
||||
Name = filename;
|
||||
Parent = parent;
|
||||
if (path.EndsWith("/"))
|
||||
path = path.Substring(0, path.Length - 1);
|
||||
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
// Zip files use '/' as a path separator
|
||||
return Parent.GetStream(path + '/' + filename);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var entry in Parent.Contents)
|
||||
{
|
||||
if (entry.StartsWith(path) && entry != path)
|
||||
{
|
||||
var filename = entry.Substring(path.Length + 1);
|
||||
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
|
||||
if (dirLevels == 1)
|
||||
yield return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return Parent.Contains(path + '/' + filename);
|
||||
}
|
||||
|
||||
public void Dispose() { /* nothing to do */ }
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,8 @@ namespace OpenRA
|
||||
|
||||
public static GlobalChat GlobalChat;
|
||||
|
||||
public static string EngineVersion { get; private set; }
|
||||
|
||||
static Task discoverNat;
|
||||
|
||||
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
|
||||
@@ -163,7 +165,7 @@ namespace OpenRA
|
||||
using (new PerfTimer("PrepareMap"))
|
||||
map = ModData.PrepareMap(mapUID);
|
||||
using (new PerfTimer("NewWorld"))
|
||||
OrderManager.World = new World(map, OrderManager, type);
|
||||
OrderManager.World = new World(ModData, map, OrderManager, type);
|
||||
|
||||
worldRenderer = new WorldRenderer(ModData, OrderManager.World);
|
||||
|
||||
@@ -246,14 +248,26 @@ namespace OpenRA
|
||||
{
|
||||
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
|
||||
|
||||
// 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();
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (string.IsNullOrEmpty(EngineVersion))
|
||||
EngineVersion = "Unknown";
|
||||
|
||||
Console.WriteLine("Engine version is {0}", EngineVersion);
|
||||
|
||||
// Special case handling of Game.Mod argument: if it matches a real filesystem path
|
||||
// then we use this to override the mod search path, and replace it with the mod id
|
||||
var modArgument = args.GetValue("Game.Mod", null);
|
||||
var modID = args.GetValue("Game.Mod", null);
|
||||
var explicitModPaths = new string[0];
|
||||
if (modArgument != null && (File.Exists(modArgument) || Directory.Exists(modArgument)))
|
||||
if (modID != null && (File.Exists(modID) || Directory.Exists(modID)))
|
||||
{
|
||||
explicitModPaths = new[] { modArgument };
|
||||
args.ReplaceValue("Game.Mod", Path.GetFileNameWithoutExtension(modArgument));
|
||||
explicitModPaths = new[] { modID };
|
||||
modID = Path.GetFileNameWithoutExtension(modID);
|
||||
}
|
||||
|
||||
InitializeSettings(args);
|
||||
@@ -317,32 +331,37 @@ namespace OpenRA
|
||||
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
|
||||
var modSearchPaths = modSearchArg != null ?
|
||||
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
|
||||
new[] { Path.Combine(".", "mods"), Path.Combine("^", "mods") };
|
||||
new[] { Path.Combine(".", "mods") };
|
||||
|
||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||
Console.WriteLine("Internal mods:");
|
||||
foreach (var mod in Mods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
|
||||
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
|
||||
ExternalMods = new ExternalMods(launchPath);
|
||||
ExternalMods = new ExternalMods();
|
||||
|
||||
Manifest currentMod;
|
||||
if (modID != null && Mods.TryGetValue(modID, out currentMod))
|
||||
{
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
|
||||
|
||||
// Sanitize input from platform-specific launchers
|
||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||
if (launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath.Substring(1, launchPath.Length - 2);
|
||||
|
||||
ExternalMods.Register(Mods[modID], launchPath, ModRegistration.User);
|
||||
|
||||
ExternalMod activeMod;
|
||||
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out activeMod))
|
||||
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
|
||||
}
|
||||
|
||||
Console.WriteLine("External mods:");
|
||||
foreach (var mod in ExternalMods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
||||
|
||||
InitializeMod(Settings.Game.Mod, args);
|
||||
}
|
||||
|
||||
public static bool IsModInstalled(string modId)
|
||||
{
|
||||
return Mods.ContainsKey(modId) && Mods[modId].RequiresMods.All(IsModInstalled);
|
||||
}
|
||||
|
||||
public static bool IsModInstalled(KeyValuePair<string, string> mod)
|
||||
{
|
||||
return Mods.ContainsKey(mod.Key)
|
||||
&& Mods[mod.Key].Metadata.Version == mod.Value
|
||||
&& IsModInstalled(mod.Key);
|
||||
InitializeMod(modID, args);
|
||||
}
|
||||
|
||||
public static void InitializeMod(string mod, Arguments args)
|
||||
@@ -372,17 +391,17 @@ namespace OpenRA
|
||||
|
||||
ModData = null;
|
||||
|
||||
// Fall back to default if the mod doesn't exist or has missing prerequisites.
|
||||
if (mod == null || !IsModInstalled(mod))
|
||||
mod = args.GetValue("Engine.DefaultMod", "modchooser");
|
||||
if (mod == null)
|
||||
throw new InvalidOperationException("Game.Mod argument missing.");
|
||||
|
||||
if (!Mods.ContainsKey(mod))
|
||||
throw new InvalidOperationException("Unknown or invalid mod '{0}'.".F(mod));
|
||||
|
||||
Console.WriteLine("Loading mod: {0}", mod);
|
||||
Settings.Game.Mod = mod;
|
||||
|
||||
Sound.StopVideo();
|
||||
|
||||
ModData = new ModData(Mods[mod], Mods, true);
|
||||
ExternalMods.Register(ModData.Manifest);
|
||||
|
||||
if (!ModData.LoadScreen.BeforeLoad())
|
||||
return;
|
||||
@@ -539,8 +558,6 @@ namespace OpenRA
|
||||
var integralTickTimestep = (uiTickDelta / Timestep) * Timestep;
|
||||
Ui.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : Timestep;
|
||||
|
||||
Viewport.TicksSinceLastMove += uiTickDelta / Timestep;
|
||||
|
||||
Sync.CheckSyncUnchanged(world, Ui.Tick);
|
||||
Cursor.Tick();
|
||||
}
|
||||
@@ -633,9 +650,9 @@ namespace OpenRA
|
||||
|
||||
using (new PerfSample("render_widgets"))
|
||||
{
|
||||
Renderer.WorldVoxelRenderer.BeginFrame();
|
||||
Renderer.WorldModelRenderer.BeginFrame();
|
||||
Ui.PrepareRenderables();
|
||||
Renderer.WorldVoxelRenderer.EndFrame();
|
||||
Renderer.WorldModelRenderer.EndFrame();
|
||||
|
||||
Ui.Draw();
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace OpenRA
|
||||
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
|
||||
public readonly TileSet TileSet;
|
||||
public readonly SequenceProvider Sequences;
|
||||
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
|
||||
|
||||
public Ruleset(
|
||||
IReadOnlyDictionary<string, ActorInfo> actors,
|
||||
@@ -37,7 +38,8 @@ namespace OpenRA
|
||||
IReadOnlyDictionary<string, SoundInfo> notifications,
|
||||
IReadOnlyDictionary<string, MusicInfo> music,
|
||||
TileSet tileSet,
|
||||
SequenceProvider sequences)
|
||||
SequenceProvider sequences,
|
||||
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
{
|
||||
Actors = actors;
|
||||
Weapons = weapons;
|
||||
@@ -46,6 +48,7 @@ namespace OpenRA
|
||||
Music = music;
|
||||
TileSet = tileSet;
|
||||
Sequences = sequences;
|
||||
ModelSequences = modelSequences;
|
||||
|
||||
foreach (var a in Actors.Values)
|
||||
{
|
||||
@@ -119,8 +122,11 @@ namespace OpenRA
|
||||
var music = MergeOrDefault("Manifest,Music", fs, m.Music, null, null,
|
||||
k => new MusicInfo(k.Key, k.Value));
|
||||
|
||||
var modelSequences = MergeOrDefault("Manifest,ModelSequences", fs, m.ModelSequences, null, null,
|
||||
k => k);
|
||||
|
||||
// The default ruleset does not include a preferred tileset or sequence set
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null);
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null, modelSequences);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
@@ -145,12 +151,13 @@ namespace OpenRA
|
||||
var dr = modData.DefaultRules;
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
var sequences = modData.DefaultSequences[tileSet];
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences);
|
||||
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences, dr.ModelSequences);
|
||||
}
|
||||
|
||||
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
|
||||
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications,
|
||||
MiniYaml mapMusic, MiniYaml mapSequences)
|
||||
MiniYaml mapMusic, MiniYaml mapSequences, MiniYaml mapModelSequences)
|
||||
{
|
||||
var m = modData.Manifest;
|
||||
var dr = modData.DefaultRules;
|
||||
@@ -180,8 +187,12 @@ namespace OpenRA
|
||||
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
|
||||
new SequenceProvider(fileSystem, modData, ts, mapSequences);
|
||||
|
||||
// TODO: Add support for custom voxel sequences
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences);
|
||||
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);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
|
||||
@@ -39,9 +39,24 @@ namespace OpenRA.GameRules
|
||||
[Desc("The maximum range the weapon can fire.")]
|
||||
public readonly WDist Range = WDist.Zero;
|
||||
|
||||
[Desc("The sound played when the weapon is fired.")]
|
||||
[Desc("First burst is aimed at this offset relative to target position.")]
|
||||
public readonly WVec FirstBurstTargetOffset = WVec.Zero;
|
||||
|
||||
[Desc("Each burst after the first lands by this offset away from the previous burst.")]
|
||||
public readonly WVec FollowingBurstTargetOffset = WVec.Zero;
|
||||
|
||||
[Desc("The sound played each time the weapon is fired.")]
|
||||
public readonly string[] Report = null;
|
||||
|
||||
[Desc("Sound played only on first burst in a salvo.")]
|
||||
public readonly string[] StartBurstReport = null;
|
||||
|
||||
[Desc("The sound played when the weapon is reloaded.")]
|
||||
public readonly string[] AfterFireSound = null;
|
||||
|
||||
[Desc("Delay in ticks to play reloading sound.")]
|
||||
public readonly int AfterFireSoundDelay = 0;
|
||||
|
||||
[Desc("Delay in ticks between reloading ammo magazines.")]
|
||||
public readonly int ReloadDelay = 1;
|
||||
|
||||
@@ -60,6 +75,9 @@ namespace OpenRA.GameRules
|
||||
[Desc("The minimum range the weapon can fire.")]
|
||||
public readonly WDist MinRange = WDist.Zero;
|
||||
|
||||
[Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")]
|
||||
public readonly bool TargetActorCenter = false;
|
||||
|
||||
[FieldLoader.LoadUsing("LoadProjectile")]
|
||||
public readonly IProjectileInfo Projectile;
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ namespace OpenRA.Chat
|
||||
volatile ChatConnectionStatus connectionStatus = ChatConnectionStatus.Disconnected;
|
||||
public ChatConnectionStatus ConnectionStatus { get { return connectionStatus; } }
|
||||
|
||||
string nickname;
|
||||
|
||||
public GlobalChat()
|
||||
{
|
||||
client.Encoding = System.Text.Encoding.UTF8;
|
||||
@@ -106,8 +108,6 @@ namespace OpenRA.Chat
|
||||
client.OnDevoice += (_, e) => SetUserVoiced(e.Whom, false);
|
||||
client.OnPart += OnPart;
|
||||
client.OnQuit += OnQuit;
|
||||
|
||||
TrySetNickname(Game.Settings.Player.Name);
|
||||
}
|
||||
|
||||
void SetUserOp(string whom, bool isOp)
|
||||
@@ -130,11 +130,13 @@ namespace OpenRA.Chat
|
||||
});
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
public void Connect(string nickname)
|
||||
{
|
||||
if (client.IsConnected)
|
||||
if (client.IsConnected || !IsValidNickname(nickname))
|
||||
return;
|
||||
|
||||
this.nickname = nickname;
|
||||
|
||||
new Thread(() =>
|
||||
{
|
||||
try
|
||||
@@ -185,12 +187,7 @@ namespace OpenRA.Chat
|
||||
AddNotification("Connected.");
|
||||
connectionStatus = ChatConnectionStatus.Connected;
|
||||
|
||||
// Guard against settings.yaml modification
|
||||
var nick = SanitizedName(Game.Settings.Chat.Nickname);
|
||||
if (nick != Game.Settings.Chat.Nickname)
|
||||
Game.RunAfterTick(() => Game.Settings.Chat.Nickname = nick);
|
||||
|
||||
client.Login(nick, "in-game IRC client", 0, "OpenRA");
|
||||
client.Login(nickname, "in-game IRC client", 0, "OpenRA");
|
||||
client.RfcJoin("#" + Game.Settings.Chat.Channel);
|
||||
}
|
||||
|
||||
@@ -341,7 +338,6 @@ namespace OpenRA.Chat
|
||||
if (Rfc2812.IsValidNickname(nick))
|
||||
{
|
||||
client.RfcNick(nick);
|
||||
Game.Settings.Chat.Nickname = nick;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -368,7 +364,7 @@ namespace OpenRA.Chat
|
||||
|
||||
AddNotification("Disconnecting from {0}...".F(client.Address));
|
||||
|
||||
Game.RunAfterTick(() => Game.Settings.Chat.ConnectAutomatically = false);
|
||||
Game.RunAfterTick(() => { Game.Settings.Chat.ConnectAutomatically = false; Game.Settings.Save(); });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -95,7 +95,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public static bool operator ==(HSLColor me, HSLColor other)
|
||||
{
|
||||
return me.H == other.H && me.S == other.S && me.L == other.L;
|
||||
// Binary floating point numbers (float, double) calculations can yield the same RGB color created by different functions with little different HSL representation
|
||||
return (me.H == other.H && me.S == other.S && me.L == other.L) || me.RGB == other.RGB;
|
||||
}
|
||||
|
||||
public static bool operator !=(HSLColor me, HSLColor other) { return !(me == other); }
|
||||
|
||||
83
OpenRA.Game/Graphics/Model.cs
Normal file
83
OpenRA.Game/Graphics/Model.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface IModel
|
||||
{
|
||||
uint Frames { get; }
|
||||
uint Sections { get; }
|
||||
|
||||
float[] TransformationMatrix(uint section, uint frame);
|
||||
float[] Size { get; }
|
||||
float[] Bounds(uint frame);
|
||||
ModelRenderData RenderData(uint section);
|
||||
}
|
||||
|
||||
public struct ModelRenderData
|
||||
{
|
||||
public readonly int Start;
|
||||
public readonly int Count;
|
||||
public readonly Sheet Sheet;
|
||||
|
||||
public ModelRenderData(int start, int count, Sheet sheet)
|
||||
{
|
||||
Start = start;
|
||||
Count = count;
|
||||
Sheet = sheet;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IModelCache : IDisposable
|
||||
{
|
||||
IModel GetModelSequence(string model, string sequence);
|
||||
bool HasModelSequence(string model, string sequence);
|
||||
IVertexBuffer<Vertex> VertexBuffer { get; }
|
||||
}
|
||||
|
||||
public interface IModelSequenceLoader
|
||||
{
|
||||
Action<string> OnMissingModelError { get; set; }
|
||||
IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions);
|
||||
}
|
||||
|
||||
public class PlaceholderModelSequenceLoader : IModelSequenceLoader
|
||||
{
|
||||
public Action<string> OnMissingModelError { get; set; }
|
||||
|
||||
class PlaceholderModelCache : IModelCache
|
||||
{
|
||||
public IVertexBuffer<Vertex> VertexBuffer { get { throw new NotImplementedException(); } }
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public IModel GetModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool HasModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public PlaceholderModelSequenceLoader(ModData modData) { }
|
||||
|
||||
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)
|
||||
{
|
||||
return new PlaceholderModelCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,18 @@ using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct VoxelAnimation
|
||||
public struct ModelAnimation
|
||||
{
|
||||
public readonly Voxel Voxel;
|
||||
public readonly IModel Model;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<IEnumerable<WRot>> RotationFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<uint> FrameFunc;
|
||||
public readonly bool ShowShadow;
|
||||
|
||||
public VoxelAnimation(Voxel voxel, Func<WVec> offset, Func<IEnumerable<WRot>> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<IEnumerable<WRot>> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
{
|
||||
Voxel = voxel;
|
||||
Model = model;
|
||||
OffsetFunc = offset;
|
||||
RotationFunc = rotation;
|
||||
DisableFunc = disable;
|
||||
@@ -17,14 +17,14 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class VoxelRenderProxy
|
||||
public class ModelRenderProxy
|
||||
{
|
||||
public readonly Sprite Sprite;
|
||||
public readonly Sprite ShadowSprite;
|
||||
public readonly float ShadowDirection;
|
||||
public readonly float3[] ProjectedShadowBounds;
|
||||
|
||||
public VoxelRenderProxy(Sprite sprite, Sprite shadowSprite, float3[] projectedShadowBounds, float shadowDirection)
|
||||
public ModelRenderProxy(Sprite sprite, Sprite shadowSprite, float3[] projectedShadowBounds, float shadowDirection)
|
||||
{
|
||||
Sprite = sprite;
|
||||
ShadowSprite = shadowSprite;
|
||||
@@ -33,7 +33,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class VoxelRenderer : IDisposable
|
||||
public sealed class ModelRenderer : IDisposable
|
||||
{
|
||||
// Static constants
|
||||
static readonly float[] ShadowDiffuse = new float[] { 0, 0, 0 };
|
||||
@@ -53,7 +53,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
SheetBuilder sheetBuilder;
|
||||
|
||||
public VoxelRenderer(Renderer renderer, IShader shader)
|
||||
public ModelRenderer(Renderer renderer, IShader shader)
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
@@ -78,8 +78,8 @@ namespace OpenRA.Graphics
|
||||
shader.SetMatrix("View", view);
|
||||
}
|
||||
|
||||
public VoxelRenderProxy RenderAsync(
|
||||
WorldRenderer wr, IEnumerable<VoxelAnimation> voxels, WRot camera, float scale,
|
||||
public ModelRenderProxy RenderAsync(
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, WRot camera, float scale,
|
||||
float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
|
||||
{
|
||||
@@ -105,18 +105,18 @@ namespace OpenRA.Graphics
|
||||
var stl = new float2(float.MaxValue, float.MaxValue);
|
||||
var sbr = new float2(float.MinValue, float.MinValue);
|
||||
|
||||
foreach (var v in voxels)
|
||||
foreach (var m in models)
|
||||
{
|
||||
// Convert screen offset back to world coords
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(v.OffsetFunc()));
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var worldTransform = v.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
var worldTransform = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
worldTransform = Util.MatrixMultiply(scaleTransform, worldTransform);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
var bounds = v.Voxel.Bounds(v.FrameFunc());
|
||||
var bounds = m.Model.Bounds(m.FrameFunc());
|
||||
var worldBounds = Util.MatrixAABBMultiply(worldTransform, bounds);
|
||||
var screenBounds = Util.MatrixAABBMultiply(cameraTransform, worldBounds);
|
||||
var shadowBounds = Util.MatrixAABBMultiply(shadowTransform, worldBounds);
|
||||
@@ -177,13 +177,13 @@ namespace OpenRA.Graphics
|
||||
|
||||
doRender.Add(Pair.New<Sheet, Action>(sprite.Sheet, () =>
|
||||
{
|
||||
foreach (var v in voxels)
|
||||
foreach (var m in models)
|
||||
{
|
||||
// Convert screen offset to world offset
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(v.OffsetFunc()));
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var rotations = v.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
var rotations = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
var worldTransform = Util.MatrixMultiply(scaleTransform, rotations);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
@@ -196,11 +196,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
var lightTransform = Util.MatrixMultiply(Util.MatrixInverse(rotations), invShadowTransform);
|
||||
|
||||
var frame = v.FrameFunc();
|
||||
for (uint i = 0; i < v.Voxel.Limbs; i++)
|
||||
var frame = m.FrameFunc();
|
||||
for (uint i = 0; i < m.Model.Sections; i++)
|
||||
{
|
||||
var rd = v.Voxel.RenderData(i);
|
||||
var t = v.Voxel.TransformationMatrix(i, frame);
|
||||
var rd = m.Model.RenderData(i);
|
||||
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));
|
||||
@@ -208,12 +208,12 @@ namespace OpenRA.Graphics
|
||||
// Transform light vector from shadow -> world -> limb coords
|
||||
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
|
||||
|
||||
Render(rd, Util.MatrixMultiply(transform, t), lightDirection,
|
||||
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
|
||||
lightAmbientColor, lightDiffuseColor, color.TextureMidIndex, normals.TextureMidIndex);
|
||||
|
||||
// Disable shadow normals by forcing zero diffuse and identity ambient light
|
||||
if (v.ShowShadow)
|
||||
Render(rd, Util.MatrixMultiply(shadow, t), lightDirection,
|
||||
if (m.ShowShadow)
|
||||
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
|
||||
ShadowAmbient, ShadowDiffuse, shadowPalette.TextureMidIndex, normals.TextureMidIndex);
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, ZVector);
|
||||
screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
|
||||
return new VoxelRenderProxy(sprite, shadowSprite, screenCorners, -screenLightVector[2] / screenLightVector[1]);
|
||||
return new ModelRenderProxy(sprite, shadowSprite, screenCorners, -screenLightVector[2] / screenLightVector[1]);
|
||||
}
|
||||
|
||||
static void CalculateSpriteGeometry(float2 tl, float2 br, float scale, out Size size, out int2 offset)
|
||||
@@ -258,7 +258,8 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
void Render(
|
||||
VoxelRenderData renderData,
|
||||
ModelRenderData renderData,
|
||||
IModelCache cache,
|
||||
float[] t, float[] lightDirection,
|
||||
float[] ambientLight, float[] diffuseLight,
|
||||
float colorPaletteTextureMidIndex, float normalsPaletteTextureMidIndex)
|
||||
@@ -270,7 +271,7 @@ namespace OpenRA.Graphics
|
||||
shader.SetVec("AmbientLight", ambientLight, 3);
|
||||
shader.SetVec("DiffuseLight", diffuseLight, 3);
|
||||
|
||||
shader.Render(() => renderer.DrawBatch(Game.ModData.VoxelLoader.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.TriangleList));
|
||||
shader.Render(() => renderer.DrawBatch(cache.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.TriangleList));
|
||||
}
|
||||
|
||||
public void BeginFrame()
|
||||
@@ -80,7 +80,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public static long TicksSinceLastMove = 0;
|
||||
public static long LastMoveRunTime = 0;
|
||||
public static int2 LastMousePos;
|
||||
|
||||
float ClosestTo(float[] collection, float target)
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.FileSystem;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public static class VoxelProvider
|
||||
{
|
||||
static Dictionary<string, Dictionary<string, Voxel>> units;
|
||||
|
||||
public static void Initialize(VoxelLoader loader, IReadOnlyFileSystem fileSystem, List<MiniYamlNode> sequences)
|
||||
{
|
||||
units = new Dictionary<string, Dictionary<string, Voxel>>();
|
||||
foreach (var s in sequences)
|
||||
LoadVoxelsForUnit(loader, s.Key, s.Value);
|
||||
|
||||
loader.RefreshBuffer();
|
||||
}
|
||||
|
||||
static Voxel LoadVoxel(VoxelLoader voxelLoader, string unit, MiniYaml info)
|
||||
{
|
||||
var vxl = unit;
|
||||
var hva = unit;
|
||||
if (info.Value != null)
|
||||
{
|
||||
var fields = info.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (fields.Length >= 1)
|
||||
vxl = hva = fields[0].Trim();
|
||||
|
||||
if (fields.Length >= 2)
|
||||
hva = fields[1].Trim();
|
||||
}
|
||||
|
||||
return voxelLoader.Load(vxl, hva);
|
||||
}
|
||||
|
||||
static void LoadVoxelsForUnit(VoxelLoader loader, string unit, MiniYaml sequences)
|
||||
{
|
||||
Game.ModData.LoadScreen.Display();
|
||||
try
|
||||
{
|
||||
var seq = sequences.ToDictionary(my => LoadVoxel(loader, unit, my));
|
||||
units.Add(unit, seq);
|
||||
}
|
||||
catch (FileNotFoundException) { } // Do nothing; we can crash later if we actually wanted art
|
||||
}
|
||||
|
||||
public static Voxel GetVoxel(string unitName, string voxelName)
|
||||
{
|
||||
try { return units[unitName][voxelName]; }
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
if (units.ContainsKey(unitName))
|
||||
throw new InvalidOperationException(
|
||||
"Unit `{0}` does not have a voxel `{1}`".F(unitName, voxelName));
|
||||
else
|
||||
throw new InvalidOperationException(
|
||||
"Unit `{0}` does not have any voxels defined.".F(unitName));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasVoxel(string unit, string seq)
|
||||
{
|
||||
if (!units.ContainsKey(unit))
|
||||
throw new InvalidOperationException(
|
||||
"Unit `{0}` does not have any voxels defined.".F(unit));
|
||||
|
||||
return units[unit].ContainsKey(seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ namespace OpenRA.Graphics
|
||||
r => ZPosition(r.Pos, r.ZOffset);
|
||||
|
||||
public readonly Size TileSize;
|
||||
public readonly int TileScale;
|
||||
public readonly World World;
|
||||
public readonly Theater Theater;
|
||||
public Viewport Viewport { get; private set; }
|
||||
@@ -41,6 +42,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
World = world;
|
||||
TileSize = World.Map.Grid.TileSize;
|
||||
TileScale = World.Map.Grid.Type == MapGridType.RectangularIsometric ? 1448 : 1024;
|
||||
Viewport = new Viewport(this, world.Map);
|
||||
|
||||
createPaletteReference = CreatePaletteReference;
|
||||
@@ -111,9 +113,9 @@ namespace OpenRA.Graphics
|
||||
worldRenderables = worldRenderables.Concat(World.Effects.SelectMany(e => e.Render(this)));
|
||||
worldRenderables = worldRenderables.OrderBy(RenderableScreenZPositionComparisonKey);
|
||||
|
||||
Game.Renderer.WorldVoxelRenderer.BeginFrame();
|
||||
Game.Renderer.WorldModelRenderer.BeginFrame();
|
||||
var renderables = worldRenderables.Select(r => r.PrepareRender(this)).ToList();
|
||||
Game.Renderer.WorldVoxelRenderer.EndFrame();
|
||||
Game.Renderer.WorldModelRenderer.EndFrame();
|
||||
|
||||
return renderables;
|
||||
}
|
||||
@@ -180,14 +182,14 @@ namespace OpenRA.Graphics
|
||||
if (World.OrderGenerator != null)
|
||||
aboveShroudOrderGenerator = World.OrderGenerator.RenderAboveShroud(this, World);
|
||||
|
||||
Game.Renderer.WorldVoxelRenderer.BeginFrame();
|
||||
Game.Renderer.WorldModelRenderer.BeginFrame();
|
||||
var finalOverlayRenderables = aboveShroud
|
||||
.Concat(aboveShroudSelected)
|
||||
.Concat(aboveShroudEffects)
|
||||
.Concat(aboveShroudOrderGenerator)
|
||||
.Select(r => r.PrepareRender(this))
|
||||
.ToList();
|
||||
Game.Renderer.WorldVoxelRenderer.EndFrame();
|
||||
Game.Renderer.WorldModelRenderer.EndFrame();
|
||||
|
||||
// HACK: Keep old grouping behaviour
|
||||
foreach (var g in finalOverlayRenderables.GroupBy(prs => prs.GetType()))
|
||||
@@ -216,13 +218,13 @@ namespace OpenRA.Graphics
|
||||
// Conversion between world and screen coordinates
|
||||
public float2 ScreenPosition(WPos pos)
|
||||
{
|
||||
return new float2(TileSize.Width * pos.X / 1024f, TileSize.Height * (pos.Y - pos.Z) / 1024f);
|
||||
return new float2((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale);
|
||||
}
|
||||
|
||||
public float3 Screen3DPosition(WPos pos)
|
||||
{
|
||||
var z = ZPosition(pos, 0) * TileSize.Height / 1024f;
|
||||
return new float3(TileSize.Width * pos.X / 1024f, TileSize.Height * (pos.Y - pos.Z) / 1024f, z);
|
||||
var z = ZPosition(pos, 0) * (float)TileSize.Height / TileScale;
|
||||
return new float3((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale, z);
|
||||
}
|
||||
|
||||
public int2 ScreenPxPosition(WPos pos)
|
||||
@@ -239,16 +241,16 @@ namespace OpenRA.Graphics
|
||||
return new float3((float)Math.Round(px.X), (float)Math.Round(px.Y), px.Z);
|
||||
}
|
||||
|
||||
// For scaling vectors to pixel sizes in the voxel renderer
|
||||
// For scaling vectors to pixel sizes in the model renderer
|
||||
public float3 ScreenVectorComponents(WVec vec)
|
||||
{
|
||||
return new float3(
|
||||
TileSize.Width * vec.X / 1024f,
|
||||
TileSize.Height * (vec.Y - vec.Z) / 1024f,
|
||||
TileSize.Height * vec.Z / 1024f);
|
||||
(float)TileSize.Width * vec.X / TileScale,
|
||||
(float)TileSize.Height * (vec.Y - vec.Z) / TileScale,
|
||||
(float)TileSize.Height * vec.Z / TileScale);
|
||||
}
|
||||
|
||||
// For scaling vectors to pixel sizes in the voxel renderer
|
||||
// For scaling vectors to pixel sizes in the model renderer
|
||||
public float[] ScreenVector(WVec vec)
|
||||
{
|
||||
var xyz = ScreenVectorComponents(vec);
|
||||
@@ -264,7 +266,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public float ScreenZPosition(WPos pos, int offset)
|
||||
{
|
||||
return ZPosition(pos, offset) * TileSize.Height / 1024f;
|
||||
return ZPosition(pos, offset) * (float)TileSize.Height / TileScale;
|
||||
}
|
||||
|
||||
static int ZPosition(WPos pos, int offset)
|
||||
@@ -278,7 +280,7 @@ namespace OpenRA.Graphics
|
||||
/// </summary>
|
||||
public WPos ProjectedPosition(int2 screenPx)
|
||||
{
|
||||
return new WPos(1024 * screenPx.X / TileSize.Width, 1024 * screenPx.Y / TileSize.Height, 0);
|
||||
return new WPos(TileScale * screenPx.X / TileSize.Width, TileScale * screenPx.Y / TileSize.Height, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class Group
|
||||
{
|
||||
readonly Actor[] actors;
|
||||
readonly int id;
|
||||
|
||||
static int nextGroup;
|
||||
|
||||
public IEnumerable<Actor> Actors { get { return actors; } }
|
||||
|
||||
public Group(IEnumerable<Actor> actors)
|
||||
{
|
||||
this.actors = actors.ToArray();
|
||||
|
||||
foreach (var a in actors)
|
||||
a.Group = this;
|
||||
|
||||
id = nextGroup++;
|
||||
}
|
||||
|
||||
public void Dump()
|
||||
{
|
||||
/* debug crap */
|
||||
Game.Debug("Group #{0}: {1}".F(
|
||||
id, actors.Select(a => "#{0} {1}".F(a.ActorID, a.Info.Name)).JoinWith(",")));
|
||||
}
|
||||
|
||||
/* TODO: add lazy group path crap, groupleader, pruning, etc */
|
||||
}
|
||||
}
|
||||
@@ -69,5 +69,6 @@ namespace OpenRA
|
||||
public Modifiers Modifiers;
|
||||
public int MultiTapCount;
|
||||
public char UnicodeChar;
|
||||
public bool IsRepeat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,13 @@ namespace OpenRA
|
||||
{
|
||||
try
|
||||
{
|
||||
var directory = new DirectoryInfo(Platform.ResolvePath(path));
|
||||
var resolved = Platform.ResolvePath(path);
|
||||
if (!Directory.Exists(resolved))
|
||||
continue;
|
||||
|
||||
var directory = new DirectoryInfo(resolved);
|
||||
foreach (var subdir in directory.EnumerateDirectories())
|
||||
mods.Add(Pair.New(subdir.Name, subdir.FullName));
|
||||
|
||||
foreach (var file in directory.EnumerateFiles("*.oramod"))
|
||||
mods.Add(Pair.New(Path.GetFileNameWithoutExtension(file.Name), file.FullName));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -67,40 +68,39 @@ namespace OpenRA
|
||||
IReadOnlyPackage package = null;
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
package = new Folder(path);
|
||||
else
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var fileStream = File.OpenRead(path))
|
||||
package = new ZipFile(fileStream, path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new InvalidDataException(path + " is not a valid mod package");
|
||||
}
|
||||
Log.Write("debug", path + " is not a valid mod package");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!package.Contains("mod.yaml"))
|
||||
throw new InvalidDataException(path + " is not a valid mod package");
|
||||
package = new Folder(path);
|
||||
if (package.Contains("mod.yaml"))
|
||||
{
|
||||
var manifest = new Manifest(id, package);
|
||||
|
||||
using (var stream = package.GetStream("icon.png"))
|
||||
if (stream != null)
|
||||
using (var bitmap = new Bitmap(stream))
|
||||
icons[id] = sheetBuilder.Add(bitmap);
|
||||
if (package.Contains("icon.png"))
|
||||
{
|
||||
using (var stream = package.GetStream("icon.png"))
|
||||
if (stream != null)
|
||||
using (var bitmap = new Bitmap(stream))
|
||||
icons[id] = sheetBuilder.Add(bitmap);
|
||||
}
|
||||
else if (!manifest.Metadata.Hidden)
|
||||
Log.Write("debug", "Mod '{0}' is missing 'icon.png'.".F(path));
|
||||
|
||||
// Mods in the support directory and oramod packages (which are listed later
|
||||
// in the CandidateMods list) override mods in the main install.
|
||||
return new Manifest(id, package);
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception e)
|
||||
{
|
||||
if (package != null)
|
||||
package.Dispose();
|
||||
|
||||
return null;
|
||||
Log.Write("debug", "Load mod '{0}': {1}".F(path, e));
|
||||
}
|
||||
|
||||
if (package != null)
|
||||
package.Dispose();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
|
||||
@@ -112,9 +112,6 @@ namespace OpenRA
|
||||
foreach (var pair in candidates)
|
||||
{
|
||||
var mod = LoadMod(pair.First, pair.Second);
|
||||
|
||||
// Mods in the support directory and oramod packages (which are listed later
|
||||
// in the CandidateMods list) override mods in the main install.
|
||||
if (mod != null)
|
||||
ret[pair.First] = mod;
|
||||
}
|
||||
|
||||
@@ -31,12 +31,21 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ModelSequenceFormat : IGlobalModData
|
||||
{
|
||||
public readonly string Type;
|
||||
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
|
||||
public ModelSequenceFormat(MiniYaml yaml)
|
||||
{
|
||||
Type = yaml.Value;
|
||||
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
|
||||
}
|
||||
}
|
||||
|
||||
public class ModMetadata
|
||||
{
|
||||
public string Title;
|
||||
public string Description;
|
||||
public string Version;
|
||||
public string Author;
|
||||
public bool Hidden;
|
||||
}
|
||||
|
||||
@@ -48,25 +57,24 @@ namespace OpenRA
|
||||
public readonly ModMetadata Metadata;
|
||||
public readonly string[]
|
||||
Rules, ServerTraits,
|
||||
Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||
Sequences, ModelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||
Weapons, Voices, Notifications, Music, Translations, TileSets,
|
||||
ChromeMetrics, MapCompatibility, Missions;
|
||||
|
||||
public readonly IReadOnlyDictionary<string, string> Packages;
|
||||
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
||||
public readonly MiniYaml LoadScreen;
|
||||
|
||||
public readonly Dictionary<string, string> RequiresMods;
|
||||
public readonly Dictionary<string, Pair<string, int>> Fonts;
|
||||
|
||||
public readonly string[] SoundFormats = { };
|
||||
public readonly string[] SpriteFormats = { };
|
||||
public readonly string[] PackageFormats = { };
|
||||
|
||||
readonly string[] reservedModuleNames = { "Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||
"Sequences", "VoxelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions",
|
||||
"ServerTraits", "LoadScreen", "Fonts", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
|
||||
"RequiresMods" };
|
||||
"RequiresMods", "PackageFormats" };
|
||||
|
||||
readonly TypeDictionary modules = new TypeDictionary();
|
||||
readonly Dictionary<string, MiniYaml> yaml;
|
||||
@@ -90,7 +98,7 @@ namespace OpenRA
|
||||
|
||||
Rules = YamlList(yaml, "Rules");
|
||||
Sequences = YamlList(yaml, "Sequences");
|
||||
VoxelSequences = YamlList(yaml, "VoxelSequences");
|
||||
ModelSequences = YamlList(yaml, "ModelSequences");
|
||||
Cursors = YamlList(yaml, "Cursors");
|
||||
Chrome = YamlList(yaml, "Chrome");
|
||||
Assemblies = YamlList(yaml, "Assemblies");
|
||||
@@ -115,8 +123,6 @@ namespace OpenRA
|
||||
return Pair.New(nd["Font"].Value, Exts.ParseIntegerInvariant(nd["Size"].Value));
|
||||
});
|
||||
|
||||
RequiresMods = yaml["RequiresMods"].ToDictionary(my => my.Value);
|
||||
|
||||
// Allow inherited mods to import parent maps.
|
||||
var compat = new List<string> { Id };
|
||||
|
||||
@@ -125,6 +131,9 @@ namespace OpenRA
|
||||
|
||||
MapCompatibility = compat.ToArray();
|
||||
|
||||
if (yaml.ContainsKey("PackageFormats"))
|
||||
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", yaml["PackageFormats"].Value);
|
||||
|
||||
if (yaml.ContainsKey("SoundFormats"))
|
||||
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", yaml["SoundFormats"].Value);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -168,7 +169,7 @@ namespace OpenRA
|
||||
new MapField("Actors", "ActorDefinitions"),
|
||||
new MapField("Rules", "RuleDefinitions", required: false),
|
||||
new MapField("Sequences", "SequenceDefinitions", required: false),
|
||||
new MapField("VoxelSequences", "VoxelSequenceDefinitions", required: false),
|
||||
new MapField("ModelSequences", "ModelSequenceDefinitions", required: false),
|
||||
new MapField("Weapons", "WeaponDefinitions", required: false),
|
||||
new MapField("Voices", "VoiceDefinitions", required: false),
|
||||
new MapField("Music", "MusicDefinitions", required: false),
|
||||
@@ -199,7 +200,7 @@ namespace OpenRA
|
||||
// Custom map yaml. Public for access by the map importers and lint checks
|
||||
public readonly MiniYaml RuleDefinitions;
|
||||
public readonly MiniYaml SequenceDefinitions;
|
||||
public readonly MiniYaml VoxelSequenceDefinitions;
|
||||
public readonly MiniYaml ModelSequenceDefinitions;
|
||||
public readonly MiniYaml WeaponDefinitions;
|
||||
public readonly MiniYaml VoiceDefinitions;
|
||||
public readonly MiniYaml MusicDefinitions;
|
||||
@@ -386,13 +387,13 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
Rules = Ruleset.Load(modData, this, Tileset, RuleDefinitions, WeaponDefinitions,
|
||||
VoiceDefinitions, NotificationDefinitions, MusicDefinitions, SequenceDefinitions);
|
||||
VoiceDefinitions, NotificationDefinitions, MusicDefinitions, SequenceDefinitions, ModelSequenceDefinitions);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e);
|
||||
InvalidCustomRules = true;
|
||||
Rules = Ruleset.LoadDefaultsForTileSet(modData, Tileset);
|
||||
Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e.Message);
|
||||
}
|
||||
|
||||
Rules.Sequences.Preload();
|
||||
@@ -761,8 +762,11 @@ namespace OpenRA
|
||||
// (b) Therefore:
|
||||
// - ax + by adds (a - b) * 512 + 512 to u
|
||||
// - ax + by adds (a + b) * 512 + 512 to v
|
||||
var z = Height.Contains(cell) ? 512 * Height[cell] : 0;
|
||||
return new WPos(512 * (cell.X - cell.Y + 1), 512 * (cell.X + cell.Y + 1), z);
|
||||
// (c) u, v coordinates run diagonally to the cell axes, and we define
|
||||
// 1024 as the length projected onto the primary cell axis
|
||||
// - 512 * sqrt(2) = 724
|
||||
var z = Height.Contains(cell) ? 724 * Height[cell] : 0;
|
||||
return new WPos(724 * (cell.X - cell.Y + 1), 724 * (cell.X + cell.Y + 1), z);
|
||||
}
|
||||
|
||||
public WPos CenterOfSubCell(CPos cell, SubCell subCell)
|
||||
@@ -786,15 +790,15 @@ namespace OpenRA
|
||||
return new CPos(pos.X / 1024, pos.Y / 1024);
|
||||
|
||||
// Convert from world position to isometric cell position:
|
||||
// (a) Subtract (512, 512) to move the rotation center to the middle of the corner cell
|
||||
// (b) Rotate axes by -pi/4
|
||||
// (c) Divide through by sqrt(2) to bring us to an equivalent world pos aligned with u,v axes
|
||||
// (d) Apply an offset so that the integer division by 1024 rounds in the right direction:
|
||||
// (i) u is always positive, so add 512 (which then partially cancels the -1024 term from the rotation)
|
||||
// (ii) v can be negative, so we need to be careful about rounding directions. We add 512 *away from 0* (negative if y > x).
|
||||
// (e) Divide by 1024 to bring into cell coords.
|
||||
var u = (pos.Y + pos.X - 512) / 1024;
|
||||
var v = (pos.Y - pos.X + (pos.Y > pos.X ? 512 : -512)) / 1024;
|
||||
// (a) Subtract ([1/2 cell], [1/2 cell]) to move the rotation center to the middle of the corner cell
|
||||
// (b) Rotate axes by -pi/4 to align the world axes with the cell axes
|
||||
// (c) Apply an offset so that the integer division by [1 cell] rounds in the right direction:
|
||||
// (i) u is always positive, so add [1/2 cell] (which then partially cancels the -[1 cell] term from the rotation)
|
||||
// (ii) v can be negative, so we need to be careful about rounding directions. We add [1/2 cell] *away from 0* (negative if y > x).
|
||||
// (e) Divide by [1 cell] to bring into cell coords.
|
||||
// The world axes are rotated relative to the cell axes, so the standard cell size (1024) is increased by a factor of sqrt(2)
|
||||
var u = (pos.Y + pos.X - 724) / 1448;
|
||||
var v = (pos.Y - pos.X + (pos.Y > pos.X ? 724 : -724)) / 1448;
|
||||
return new CPos(u, v);
|
||||
}
|
||||
|
||||
@@ -868,19 +872,19 @@ namespace OpenRA
|
||||
Bounds = Rectangle.FromLTRB(tl.U, tl.V, br.U + 1, br.V + 1);
|
||||
|
||||
// Directly calculate the projected map corners in world units avoiding unnecessary
|
||||
// conversions. This abuses the definition that the width of the cell is always
|
||||
// 1024 units, and that the height of two rows is 2048 for classic cells and 1024
|
||||
// conversions. This abuses the definition that the width of the cell along the x world axis
|
||||
// is always 1024 or 1448 units, and that the height of two rows is 2048 for classic cells and 724
|
||||
// for isometric cells.
|
||||
var wtop = tl.V * 1024;
|
||||
var wbottom = (br.V + 1) * 1024;
|
||||
if (Grid.Type == MapGridType.RectangularIsometric)
|
||||
{
|
||||
wtop /= 2;
|
||||
wbottom /= 2;
|
||||
ProjectedTopLeft = new WPos(tl.U * 1448, tl.V * 724, 0);
|
||||
ProjectedBottomRight = new WPos(br.U * 1448 - 1, (br.V + 1) * 724 - 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProjectedTopLeft = new WPos(tl.U * 1024, tl.V * 1024, 0);
|
||||
ProjectedBottomRight = new WPos(br.U * 1024 - 1, (br.V + 1) * 1024 - 1, 0);
|
||||
}
|
||||
|
||||
ProjectedTopLeft = new WPos(tl.U * 1024, wtop, 0);
|
||||
ProjectedBottomRight = new WPos(br.U * 1024 - 1, wbottom - 1, 0);
|
||||
|
||||
ProjectedCellBounds = new ProjectedCellRegion(this, tl, br);
|
||||
}
|
||||
|
||||
@@ -54,12 +54,21 @@ namespace OpenRA
|
||||
? MapClassification.Unknown : Enum<MapClassification>.Parse(kv.Value);
|
||||
|
||||
IReadOnlyPackage package;
|
||||
var optional = name.StartsWith("~");
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
if (optional)
|
||||
name = name.Substring(1);
|
||||
|
||||
try
|
||||
{
|
||||
// HACK: If the path is inside the the support directory then we may need to create it
|
||||
if (name.StartsWith("^", StringComparison.Ordinal))
|
||||
{
|
||||
// Assume that the path is a directory if there is not an existing file with the same name
|
||||
var resolved = Platform.ResolvePath(name);
|
||||
if (!File.Exists(resolved))
|
||||
Directory.CreateDirectory(resolved);
|
||||
}
|
||||
|
||||
package = modData.ModFiles.OpenPackage(name);
|
||||
}
|
||||
catch
|
||||
@@ -92,7 +101,7 @@ namespace OpenRA
|
||||
{
|
||||
using (new Support.PerfTimer(map))
|
||||
{
|
||||
mapPackage = modData.ModFiles.OpenPackage(map, kv.Key);
|
||||
mapPackage = kv.Key.OpenPackage(map, modData.ModFiles);
|
||||
if (mapPackage == null)
|
||||
continue;
|
||||
|
||||
@@ -113,7 +122,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public void QueryRemoteMapDetails(IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action queryFailed = null)
|
||||
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action queryFailed = null)
|
||||
{
|
||||
var maps = uids.Distinct()
|
||||
.Select(uid => previews[uid])
|
||||
@@ -126,7 +135,7 @@ namespace OpenRA
|
||||
foreach (var p in maps.Values)
|
||||
p.UpdateRemoteSearch(MapStatus.Searching, null);
|
||||
|
||||
var url = Game.Settings.Game.MapRepository + "hash/" + string.Join(",", maps.Keys) + "/yaml";
|
||||
var url = repositoryUrl + "hash/" + string.Join(",", maps.Keys) + "/yaml";
|
||||
|
||||
Action<DownloadDataCompletedEventArgs> onInfoComplete = i =>
|
||||
{
|
||||
@@ -201,7 +210,19 @@ namespace OpenRA
|
||||
foreach (var p in todo)
|
||||
{
|
||||
if (p.Preview != null)
|
||||
Game.RunAfterTick(() => p.SetMinimap(sheetBuilder.Add(p.Preview)));
|
||||
{
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
p.SetMinimap(sheetBuilder.Add(p.Preview));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to load minimap with exception: {0}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Yuck... But this helps the UI Jank when opening the map selector significantly.
|
||||
Thread.Sleep(Environment.ProcessorCount == 1 ? 25 : 5);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
@@ -91,21 +92,34 @@ namespace OpenRA
|
||||
else if (defaultSubCellIndex < (SubCellOffsets.Length > 1 ? 1 : 0) || defaultSubCellIndex >= SubCellOffsets.Length)
|
||||
throw new InvalidDataException("Subcell default index must be a valid index into the offset triples and must be greater than 0 for mods with subcells");
|
||||
|
||||
var leftDelta = Type == MapGridType.RectangularIsometric ? new WVec(-512, 0, 0) : new WVec(-512, -512, 0);
|
||||
var topDelta = Type == MapGridType.RectangularIsometric ? new WVec(0, -512, 0) : new WVec(512, -512, 0);
|
||||
var rightDelta = Type == MapGridType.RectangularIsometric ? new WVec(512, 0, 0) : new WVec(512, 512, 0);
|
||||
var bottomDelta = Type == MapGridType.RectangularIsometric ? new WVec(0, 512, 0) : new WVec(-512, 512, 0);
|
||||
CellCorners = cellCornerHalfHeights.Select(ramp => new WVec[]
|
||||
{
|
||||
leftDelta + new WVec(0, 0, 512 * ramp[0]),
|
||||
topDelta + new WVec(0, 0, 512 * ramp[1]),
|
||||
rightDelta + new WVec(0, 0, 512 * ramp[2]),
|
||||
bottomDelta + new WVec(0, 0, 512 * ramp[3])
|
||||
}).ToArray();
|
||||
|
||||
var makeCorners = Type == MapGridType.RectangularIsometric ?
|
||||
(Func<int[], WVec[]>)IsometricCellCorners : RectangularCellCorners;
|
||||
CellCorners = cellCornerHalfHeights.Select(makeCorners).ToArray();
|
||||
TilesByDistance = CreateTilesByDistance();
|
||||
}
|
||||
|
||||
static WVec[] IsometricCellCorners(int[] cornerHeight)
|
||||
{
|
||||
return new WVec[]
|
||||
{
|
||||
new WVec(-724, 0, 724 * cornerHeight[0]),
|
||||
new WVec(0, -724, 724 * cornerHeight[1]),
|
||||
new WVec(724, 0, 724 * cornerHeight[2]),
|
||||
new WVec(0, 724, 724 * cornerHeight[3])
|
||||
};
|
||||
}
|
||||
|
||||
static WVec[] RectangularCellCorners(int[] cornerHeight)
|
||||
{
|
||||
return new WVec[]
|
||||
{
|
||||
new WVec(-512, -512, 512 * cornerHeight[0]),
|
||||
new WVec(512, -512, 512 * cornerHeight[1]),
|
||||
new WVec(512, 512, 512 * cornerHeight[2]),
|
||||
new WVec(-512, 512, 512 * cornerHeight[3])
|
||||
};
|
||||
}
|
||||
|
||||
CVec[][] CreateTilesByDistance()
|
||||
{
|
||||
var ts = new List<CVec>[MaximumTileSearchRange + 1];
|
||||
|
||||
@@ -123,8 +123,8 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
static readonly CPos[] NoSpawns = new CPos[] { };
|
||||
MapCache cache;
|
||||
ModData modData;
|
||||
readonly MapCache cache;
|
||||
readonly ModData modData;
|
||||
|
||||
public readonly string Uid;
|
||||
public IReadOnlyPackage Package { get; private set; }
|
||||
@@ -307,8 +307,9 @@ namespace OpenRA
|
||||
var musicDefinitions = LoadRuleSection(yaml, "Music");
|
||||
var notificationDefinitions = LoadRuleSection(yaml, "Notifications");
|
||||
var sequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
||||
var modelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences");
|
||||
var rules = Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions,
|
||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions);
|
||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions);
|
||||
var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions,
|
||||
weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions);
|
||||
return Pair.New(rules, flagged);
|
||||
@@ -390,8 +391,9 @@ namespace OpenRA
|
||||
var musicDefinitions = LoadRuleSection(rulesYaml, "Music");
|
||||
var notificationDefinitions = LoadRuleSection(rulesYaml, "Notifications");
|
||||
var sequenceDefinitions = LoadRuleSection(rulesYaml, "Sequences");
|
||||
var modelSequenceDefinitions = LoadRuleSection(rulesYaml, "ModelSequences");
|
||||
var rules = Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions,
|
||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions);
|
||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions);
|
||||
var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions,
|
||||
weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions);
|
||||
return Pair.New(rules, flagged);
|
||||
@@ -416,7 +418,7 @@ namespace OpenRA
|
||||
innerData = newData;
|
||||
}
|
||||
|
||||
public void Install(Action onSuccess)
|
||||
public void Install(string mapRepositoryUrl, Action onSuccess)
|
||||
{
|
||||
if (Status != MapStatus.DownloadAvailable || !Game.Settings.Game.AllowDownloading)
|
||||
return;
|
||||
@@ -431,12 +433,11 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
var mapInstallPackage = installLocation.Key as IReadWritePackage;
|
||||
var modData = Game.ModData;
|
||||
new Thread(() =>
|
||||
{
|
||||
// Request the filename from the server
|
||||
// Run in a worker thread to avoid network delays
|
||||
var mapUrl = Game.Settings.Game.MapRepository + Uid;
|
||||
var mapUrl = mapRepositoryUrl + Uid;
|
||||
var mapFilename = string.Empty;
|
||||
try
|
||||
{
|
||||
@@ -472,7 +473,7 @@ namespace OpenRA
|
||||
Log.Write("debug", "Downloaded map to '{0}'", mapFilename);
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
var package = modData.ModFiles.OpenPackage(mapFilename, mapInstallPackage);
|
||||
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
|
||||
if (package == null)
|
||||
innerData.Status = MapStatus.DownloadError;
|
||||
else
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace OpenRA
|
||||
public readonly int[] Frames;
|
||||
public readonly int2 Size;
|
||||
public readonly bool PickAny;
|
||||
public readonly string Category;
|
||||
public readonly string[] Categories;
|
||||
public readonly string Palette;
|
||||
|
||||
readonly TerrainTileInfo[] tileInfo;
|
||||
|
||||
@@ -231,11 +231,6 @@ namespace OpenRA
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Dictionary<string, MiniYaml> DictFromFile(string path)
|
||||
{
|
||||
return FromFile(path).ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
|
||||
public static Dictionary<string, MiniYaml> DictFromStream(Stream stream, string fileName = "<no filename available>")
|
||||
{
|
||||
return FromStream(stream, fileName).ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
@@ -13,7 +13,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Widgets;
|
||||
@@ -27,11 +26,12 @@ namespace OpenRA
|
||||
public readonly ObjectCreator ObjectCreator;
|
||||
public readonly WidgetLoader WidgetLoader;
|
||||
public readonly MapCache MapCache;
|
||||
public readonly IPackageLoader[] PackageLoaders;
|
||||
public readonly ISoundLoader[] SoundLoaders;
|
||||
public readonly ISpriteLoader[] SpriteLoaders;
|
||||
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
||||
public readonly IModelSequenceLoader ModelSequenceLoader;
|
||||
public ILoadScreen LoadScreen { get; private set; }
|
||||
public VoxelLoader VoxelLoader { get; private set; }
|
||||
public CursorProvider CursorProvider { get; private set; }
|
||||
public FS ModFiles;
|
||||
public IReadOnlyFileSystem DefaultFileSystem { get { return ModFiles; } }
|
||||
@@ -49,13 +49,13 @@ namespace OpenRA
|
||||
{
|
||||
Languages = new string[0];
|
||||
|
||||
ModFiles = new FS(mods);
|
||||
|
||||
// Take a local copy of the manifest
|
||||
Manifest = new Manifest(mod.Id, mod.Package);
|
||||
ModFiles.LoadFromManifest(Manifest);
|
||||
ObjectCreator = new ObjectCreator(Manifest, mods);
|
||||
PackageLoaders = ObjectCreator.GetLoaders<IPackageLoader>(Manifest.PackageFormats, "package");
|
||||
|
||||
ObjectCreator = new ObjectCreator(Manifest, ModFiles);
|
||||
ModFiles = new FS(mods, PackageLoaders);
|
||||
ModFiles.LoadFromManifest(Manifest);
|
||||
Manifest.LoadCustomData(ObjectCreator);
|
||||
|
||||
if (useLoadScreen)
|
||||
@@ -68,18 +68,27 @@ namespace OpenRA
|
||||
WidgetLoader = new WidgetLoader(this);
|
||||
MapCache = new MapCache(this);
|
||||
|
||||
SoundLoaders = GetLoaders<ISoundLoader>(Manifest.SoundFormats, "sound");
|
||||
SpriteLoaders = GetLoaders<ISpriteLoader>(Manifest.SpriteFormats, "sprite");
|
||||
SoundLoaders = ObjectCreator.GetLoaders<ISoundLoader>(Manifest.SoundFormats, "sound");
|
||||
SpriteLoaders = ObjectCreator.GetLoaders<ISpriteLoader>(Manifest.SpriteFormats, "sprite");
|
||||
|
||||
var sequenceFormat = Manifest.Get<SpriteSequenceFormat>();
|
||||
var sequenceLoader = ObjectCreator.FindType(sequenceFormat.Type + "Loader");
|
||||
var ctor = sequenceLoader != null ? sequenceLoader.GetConstructor(new[] { typeof(ModData) }) : null;
|
||||
if (sequenceLoader == null || !sequenceLoader.GetInterfaces().Contains(typeof(ISpriteSequenceLoader)) || ctor == null)
|
||||
var sequenceCtor = sequenceLoader != null ? sequenceLoader.GetConstructor(new[] { typeof(ModData) }) : null;
|
||||
if (sequenceLoader == null || !sequenceLoader.GetInterfaces().Contains(typeof(ISpriteSequenceLoader)) || sequenceCtor == null)
|
||||
throw new InvalidOperationException("Unable to find a sequence loader for type '{0}'.".F(sequenceFormat.Type));
|
||||
|
||||
SpriteSequenceLoader = (ISpriteSequenceLoader)ctor.Invoke(new[] { this });
|
||||
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
|
||||
SpriteSequenceLoader.OnMissingSpriteError = s => Log.Write("debug", s);
|
||||
|
||||
var modelFormat = Manifest.Get<ModelSequenceFormat>();
|
||||
var modelLoader = ObjectCreator.FindType(modelFormat.Type + "Loader");
|
||||
var modelCtor = modelLoader != null ? modelLoader.GetConstructor(new[] { typeof(ModData) }) : null;
|
||||
if (modelLoader == null || !modelLoader.GetInterfaces().Contains(typeof(IModelSequenceLoader)) || modelCtor == null)
|
||||
throw new InvalidOperationException("Unable to find a model loader for type '{0}'.".F(modelFormat.Type));
|
||||
|
||||
ModelSequenceLoader = (IModelSequenceLoader)modelCtor.Invoke(new[] { this });
|
||||
ModelSequenceLoader.OnMissingModelError = s => Log.Write("debug", s);
|
||||
|
||||
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
|
||||
defaultTileSets = Exts.Lazy(() =>
|
||||
{
|
||||
@@ -122,28 +131,9 @@ namespace OpenRA
|
||||
|
||||
Game.Sound.Initialize(SoundLoaders, fileSystem);
|
||||
|
||||
if (VoxelLoader != null)
|
||||
VoxelLoader.Dispose();
|
||||
VoxelLoader = new VoxelLoader(fileSystem);
|
||||
|
||||
CursorProvider = new CursorProvider(this);
|
||||
}
|
||||
|
||||
TLoader[] GetLoaders<TLoader>(IEnumerable<string> formats, string name)
|
||||
{
|
||||
var loaders = new List<TLoader>();
|
||||
foreach (var format in formats)
|
||||
{
|
||||
var loader = ObjectCreator.FindType(format + "Loader");
|
||||
if (loader == null || !loader.GetInterfaces().Contains(typeof(TLoader)))
|
||||
throw new InvalidOperationException("Unable to find a {0} loader for type '{1}'.".F(name, format));
|
||||
|
||||
loaders.Add((TLoader)ObjectCreator.CreateBasic(loader));
|
||||
}
|
||||
|
||||
return loaders.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<string> Languages { get; private set; }
|
||||
|
||||
void LoadTranslations(Map map)
|
||||
@@ -204,9 +194,6 @@ namespace OpenRA
|
||||
foreach (var entry in map.Rules.Music)
|
||||
entry.Value.Load(map);
|
||||
|
||||
VoxelProvider.Initialize(VoxelLoader, map, MiniYaml.Load(map, Manifest.VoxelSequences, map.VoxelSequenceDefinitions));
|
||||
VoxelLoader.Finish();
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -215,8 +202,9 @@ namespace OpenRA
|
||||
if (LoadScreen != null)
|
||||
LoadScreen.Dispose();
|
||||
MapCache.Dispose();
|
||||
if (VoxelLoader != null)
|
||||
VoxelLoader.Dispose();
|
||||
|
||||
if (ObjectCreator != null)
|
||||
ObjectCreator.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace OpenRA
|
||||
public uint ExtraData;
|
||||
public bool IsImmediate;
|
||||
public bool SuppressVisualFeedback;
|
||||
public Actor VisualFeedbackTarget;
|
||||
|
||||
public Player Player { get { return Subject != null ? Subject.Owner : null; } }
|
||||
|
||||
|
||||
@@ -90,9 +90,14 @@ namespace OpenRA.Network
|
||||
return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null && s.Value.AllowBots).Key;
|
||||
}
|
||||
|
||||
public bool IsSinglePlayer
|
||||
public IEnumerable<Client> NonBotClients
|
||||
{
|
||||
get { return Clients.Count(c => c.Bot == null) == 1; }
|
||||
get { return Clients.Where(c => c.Bot == null); }
|
||||
}
|
||||
|
||||
public IEnumerable<Client> NonBotPlayers
|
||||
{
|
||||
get { return Clients.Where(c => c.Bot == null && c.Slot != null); }
|
||||
}
|
||||
|
||||
public enum ClientState { NotReady, Invalid, Ready, Disconnected = 1000 }
|
||||
|
||||
@@ -65,6 +65,8 @@ namespace OpenRA.Network
|
||||
report.SyncedRandom = orderManager.World.SharedRandom.Last;
|
||||
report.TotalCount = orderManager.World.SharedRandom.TotalCount;
|
||||
report.Traits.Clear();
|
||||
report.Effects.Clear();
|
||||
|
||||
foreach (var actor in orderManager.World.ActorsHavingTrait<ISync>())
|
||||
foreach (var syncHash in actor.SyncHashes)
|
||||
if (syncHash.Hash != 0)
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace OpenRA.Network
|
||||
{
|
||||
static class UnitOrders
|
||||
{
|
||||
const string ServerChatName = "Battlefield Control";
|
||||
|
||||
static Player FindPlayerByClient(this World world, Session.Client c)
|
||||
{
|
||||
/* TODO: this is still a hack.
|
||||
@@ -57,7 +59,7 @@ namespace OpenRA.Network
|
||||
}
|
||||
|
||||
case "Message": // Server message
|
||||
Game.AddChatLine(Color.White, "Server", order.TargetString);
|
||||
Game.AddChatLine(Color.White, ServerChatName, order.TargetString);
|
||||
break;
|
||||
|
||||
case "Disconnected": /* reports that the target player disconnected */
|
||||
@@ -105,7 +107,7 @@ namespace OpenRA.Network
|
||||
break;
|
||||
}
|
||||
|
||||
Game.AddChatLine(Color.White, "Server", "The game has started.");
|
||||
Game.AddChatLine(Color.White, ServerChatName, "The game has started.");
|
||||
Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map, WorldType.Regular);
|
||||
break;
|
||||
}
|
||||
@@ -116,10 +118,10 @@ namespace OpenRA.Network
|
||||
if (client != null)
|
||||
{
|
||||
var pause = order.TargetString == "Pause";
|
||||
if (orderManager.World.Paused != pause && world != null && !world.LobbyInfo.IsSinglePlayer)
|
||||
if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1)
|
||||
{
|
||||
var pausetext = "The game is {0} by {1}".F(pause ? "paused" : "un-paused", client.Name);
|
||||
Game.AddChatLine(Color.White, "", pausetext);
|
||||
Game.AddChatLine(Color.White, ServerChatName, pausetext);
|
||||
}
|
||||
|
||||
orderManager.World.Paused = pause;
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class ObjectCreator
|
||||
public sealed class ObjectCreator : IDisposable
|
||||
{
|
||||
// .NET does not support unloading assemblies, so mod libraries will leak across mod changes.
|
||||
// This tracks the assemblies that have been loaded since game start so that we don't load multiple copies
|
||||
@@ -27,34 +28,30 @@ namespace OpenRA
|
||||
readonly Cache<Type, ConstructorInfo> ctorCache;
|
||||
readonly Pair<Assembly, string>[] assemblies;
|
||||
|
||||
public ObjectCreator(Assembly a)
|
||||
{
|
||||
typeCache = new Cache<string, Type>(FindType);
|
||||
ctorCache = new Cache<Type, ConstructorInfo>(GetCtor);
|
||||
assemblies = a.GetNamespaces().Select(ns => Pair.New(a, ns)).ToArray();
|
||||
}
|
||||
|
||||
public ObjectCreator(Manifest manifest, FileSystem.FileSystem modFiles)
|
||||
public ObjectCreator(Manifest manifest, InstalledMods mods)
|
||||
{
|
||||
typeCache = new Cache<string, Type>(FindType);
|
||||
ctorCache = new Cache<Type, ConstructorInfo>(GetCtor);
|
||||
|
||||
// Allow mods to load types from the core Game assembly, and any additional assemblies they specify.
|
||||
// Assemblies can only be loaded from directories to avoid circular dependencies on package loaders.
|
||||
var assemblyList = new List<Assembly>() { typeof(Game).Assembly };
|
||||
foreach (var path in manifest.Assemblies)
|
||||
{
|
||||
var data = modFiles.Open(path).ReadAllBytes();
|
||||
var resolvedPath = FileSystem.FileSystem.ResolveAssemblyPath(path, manifest, mods);
|
||||
if (resolvedPath == null)
|
||||
throw new FileNotFoundException("Assembly `{0}` not found.".F(path));
|
||||
|
||||
// .NET doesn't provide any way of querying the metadata of an assembly without either:
|
||||
// (a) loading duplicate data into the application domain, breaking the world.
|
||||
// (b) crashing if the assembly has already been loaded.
|
||||
// We can't check the internal name of the assembly, so we'll work off the data instead
|
||||
var hash = CryptoUtil.SHA1Hash(data);
|
||||
var hash = CryptoUtil.SHA1Hash(File.ReadAllBytes(resolvedPath));
|
||||
|
||||
Assembly assembly;
|
||||
if (!ResolvedAssemblies.TryGetValue(hash, out assembly))
|
||||
{
|
||||
assembly = Assembly.Load(data);
|
||||
assembly = Assembly.LoadFile(resolvedPath);
|
||||
ResolvedAssemblies.Add(hash, assembly);
|
||||
}
|
||||
|
||||
@@ -63,7 +60,6 @@ namespace OpenRA
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
|
||||
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => Pair.New(asm, ns))).ToArray();
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly;
|
||||
}
|
||||
|
||||
Assembly ResolveAssembly(object sender, ResolveEventArgs e)
|
||||
@@ -72,6 +68,9 @@ namespace OpenRA
|
||||
if (a.FullName == e.Name)
|
||||
return a;
|
||||
|
||||
if (assemblies == null)
|
||||
return null;
|
||||
|
||||
return assemblies.Select(a => a.First).FirstOrDefault(a => a.FullName == e.Name);
|
||||
}
|
||||
|
||||
@@ -146,6 +145,38 @@ namespace OpenRA
|
||||
.SelectMany(ma => ma.GetTypes());
|
||||
}
|
||||
|
||||
public TLoader[] GetLoaders<TLoader>(IEnumerable<string> formats, string name)
|
||||
{
|
||||
var loaders = new List<TLoader>();
|
||||
foreach (var format in formats)
|
||||
{
|
||||
var loader = FindType(format + "Loader");
|
||||
if (loader == null || !loader.GetInterfaces().Contains(typeof(TLoader)))
|
||||
throw new InvalidOperationException("Unable to find a {0} loader for type '{1}'.".F(name, format));
|
||||
|
||||
loaders.Add((TLoader)CreateBasic(loader));
|
||||
}
|
||||
|
||||
return loaders.ToArray();
|
||||
}
|
||||
|
||||
~ObjectCreator()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Constructor)]
|
||||
public sealed class UseCtorAttribute : Attribute { }
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<ApplicationIcon>OpenRA.ico</ApplicationIcon>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
@@ -42,6 +41,8 @@
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<StartArguments>Game.Mod=ra</StartArguments>
|
||||
<Commandlineparameters>Game.Mod=ra</Commandlineparameters>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -53,6 +54,24 @@
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Optimize>true</Optimize>
|
||||
<StartArguments>Game.Mod=ra</StartArguments>
|
||||
<Commandlineparameters>Game.Mod=ra</Commandlineparameters>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Default' ">
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>Game.Mod=ra</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Dune 2000' ">
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>Game.Mod=d2k</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Tiberian Dawn' ">
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>Game.Mod=cnc</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Tiberian Sun' ">
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>Game.Mod=ts</StartArguments>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
@@ -88,11 +107,8 @@
|
||||
<Compile Include="Activities\CallFunc.cs" />
|
||||
<Compile Include="Actor.cs" />
|
||||
<Compile Include="CacheStorage.cs" />
|
||||
<Compile Include="FileSystem\IdxEntry.cs" />
|
||||
<Compile Include="FileSystem\IPackage.cs" />
|
||||
<Compile Include="LogProxy.cs" />
|
||||
<Compile Include="FileFormats\IdxReader.cs" />
|
||||
<Compile Include="FileSystem\BagFile.cs" />
|
||||
<Compile Include="Map\MapGrid.cs" />
|
||||
<Compile Include="Map\MapPlayers.cs" />
|
||||
<Compile Include="MPos.cs" />
|
||||
@@ -124,7 +140,6 @@
|
||||
<Compile Include="Graphics\Util.cs" />
|
||||
<Compile Include="Graphics\Viewport.cs" />
|
||||
<Compile Include="Graphics\WorldRenderer.cs" />
|
||||
<Compile Include="Group.cs" />
|
||||
<Compile Include="IUtilityCommand.cs" />
|
||||
<Compile Include="ModData.cs" />
|
||||
<Compile Include="Network\Connection.cs" />
|
||||
@@ -146,9 +161,11 @@
|
||||
<Compile Include="Player.cs" />
|
||||
<Compile Include="Primitives\MergedStream.cs" />
|
||||
<Compile Include="Primitives\PlayerDictionary.cs" />
|
||||
<Compile Include="Primitives\ReadOnlyAdapterStream.cs" />
|
||||
<Compile Include="Primitives\SegmentStream.cs" />
|
||||
<Compile Include="Primitives\SpatiallyPartitioned.cs" />
|
||||
<Compile Include="Primitives\ConcurrentCache.cs" />
|
||||
<Compile Include="Primitives\ZipFileHelper.cs" />
|
||||
<Compile Include="SelectableExts.cs" />
|
||||
<Compile Include="Selection.cs" />
|
||||
<Compile Include="Server\Connection.cs" />
|
||||
@@ -180,11 +197,8 @@
|
||||
<Compile Include="Traits\DebugPauseState.cs" />
|
||||
<Compile Include="Network\UPnP.cs" />
|
||||
<Compile Include="Graphics\Renderable.cs" />
|
||||
<Compile Include="Graphics\Voxel.cs" />
|
||||
<Compile Include="Graphics\VoxelRenderer.cs" />
|
||||
<Compile Include="Graphics\VoxelLoader.cs" />
|
||||
<Compile Include="Graphics\VoxelProvider.cs" />
|
||||
<Compile Include="Graphics\VoxelAnimation.cs" />
|
||||
<Compile Include="Graphics\ModelRenderer.cs" />
|
||||
<Compile Include="Graphics\ModelAnimation.cs" />
|
||||
<Compile Include="Traits\Player\FrozenActorLayer.cs" />
|
||||
<Compile Include="Graphics\Theater.cs" />
|
||||
<Compile Include="Traits\Player\PlayerColorPalette.cs" />
|
||||
@@ -221,7 +235,6 @@
|
||||
<Compile Include="Support\PerfSample.cs" />
|
||||
<Compile Include="Graphics\SpriteRenderable.cs" />
|
||||
<Compile Include="Widgets\Widget.cs" />
|
||||
<Compile Include="Widgets\RootWidget.cs" />
|
||||
<Compile Include="Widgets\WidgetLoader.cs" />
|
||||
<Compile Include="Widgets\ChromeMetrics.cs" />
|
||||
<Compile Include="Widgets\WidgetUtils.cs" />
|
||||
@@ -237,21 +250,19 @@
|
||||
<Compile Include="Graphics\RgbaColorRenderer.cs" />
|
||||
<Compile Include="Traits\Player\IndexedPlayerPalette.cs" />
|
||||
<Compile Include="Traits\ActivityUtils.cs" />
|
||||
<Compile Include="FileSystem\ZipFolder.cs" />
|
||||
<Compile Include="Primitives\float3.cs" />
|
||||
<Compile Include="InstalledMods.cs" />
|
||||
<Compile Include="CryptoUtil.cs" />
|
||||
<Compile Include="Support\ConditionExpression.cs" />
|
||||
<Compile Include="Support\VariableExpression.cs" />
|
||||
<Compile Include="ExternalMods.cs" />
|
||||
<Compile Include="Graphics\Model.cs" />
|
||||
<Compile Include="UtilityCommands\RegisterModCommand.cs" />
|
||||
<Compile Include="UtilityCommands\UnregisterModCommand.cs" />
|
||||
<Compile Include="UtilityCommands\ClearInvalidModRegistrationsCommand.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="FileSystem\D2kSoundResources.cs" />
|
||||
<Compile Include="FileSystem\Folder.cs" />
|
||||
<Compile Include="FileSystem\InstallShieldPackage.cs" />
|
||||
<Compile Include="FileSystem\MixFile.cs" />
|
||||
<Compile Include="FileSystem\Pak.cs" />
|
||||
<Compile Include="FileSystem\ZipFile.cs" />
|
||||
<Compile Include="FileSystem\BigFile.cs" />
|
||||
<Compile Include="Map\PlayerReference.cs" />
|
||||
<Compile Include="Map\TileReference.cs" />
|
||||
<Compile Include="Map\TileSet.cs" />
|
||||
@@ -259,15 +270,7 @@
|
||||
<Compile Include="FieldSaver.cs" />
|
||||
<Compile Include="Manifest.cs" />
|
||||
<Compile Include="Graphics\Vertex.cs" />
|
||||
<Compile Include="FileFormats\Blast.cs" />
|
||||
<Compile Include="FileFormats\Blowfish.cs" />
|
||||
<Compile Include="FileFormats\BlowfishKeyProvider.cs" />
|
||||
<Compile Include="FileFormats\CRC32.cs" />
|
||||
<Compile Include="FileFormats\XccGlobalDatabase.cs" />
|
||||
<Compile Include="FileFormats\XccLocalDatabase.cs" />
|
||||
<Compile Include="FileFormats\HvaReader.cs" />
|
||||
<Compile Include="FileFormats\PngLoader.cs" />
|
||||
<Compile Include="FileFormats\VxlReader.cs" />
|
||||
<Compile Include="Primitives\ActionQueue.cs" />
|
||||
<Compile Include="Primitives\Bits.cs" />
|
||||
<Compile Include="Primitives\Cache.cs" />
|
||||
@@ -288,7 +291,6 @@
|
||||
<Compile Include="Map\MapCache.cs" />
|
||||
<Compile Include="Map\MapPreview.cs" />
|
||||
<Compile Include="Graphics\HSLColor.cs" />
|
||||
<Compile Include="FileSystem\PackageEntry.cs" />
|
||||
<Compile Include="CPos.cs" />
|
||||
<Compile Include="CVec.cs" />
|
||||
<Compile Include="WAngle.cs" />
|
||||
@@ -333,8 +335,5 @@
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="OpenRA.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 264 KiB |
@@ -15,8 +15,8 @@ namespace OpenRA.Orders
|
||||
{
|
||||
public class GenericSelectTarget : UnitOrderGenerator
|
||||
{
|
||||
public readonly string OrderName;
|
||||
protected readonly IEnumerable<Actor> Subjects;
|
||||
protected readonly string OrderName;
|
||||
protected readonly string Cursor;
|
||||
protected readonly MouseButton ExpectedButton;
|
||||
|
||||
|
||||
@@ -139,8 +139,15 @@ namespace OpenRA.Orders
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Alt))
|
||||
modifiers |= TargetModifiers.ForceMove;
|
||||
|
||||
// The Select(x => x) is required to work around an issue on mono 5.0
|
||||
// where calling OrderBy* on SelectManySingleSelectorIterator can in some
|
||||
// circumstances (which we were unable to identify) replace entries in the
|
||||
// enumeration with duplicates of other entries.
|
||||
// Other action that replace the SelectManySingleSelectorIterator with a
|
||||
// different enumerator type (e.g. .Where(true) or .ToList()) also work.
|
||||
var orders = self.TraitsImplementing<IIssueOrder>()
|
||||
.SelectMany(trait => trait.Orders.Select(x => new { Trait = trait, Order = x }))
|
||||
.Select(x => x)
|
||||
.OrderByDescending(x => x.Order.OrderPriority);
|
||||
|
||||
for (var i = 0; i < 2; i++)
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace OpenRA
|
||||
public static class Platform
|
||||
{
|
||||
public static PlatformType CurrentPlatform { get { return currentPlatform.Value; } }
|
||||
public static readonly Guid SessionGUID = Guid.NewGuid();
|
||||
|
||||
static Lazy<PlatformType> currentPlatform = Exts.Lazy(GetCurrentPlatform);
|
||||
|
||||
@@ -63,14 +64,19 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directory containing user-specific support files (settings, maps, replays, game data, etc).
|
||||
/// The directory will automatically be created if it does not exist when this is queried.
|
||||
/// </summary>
|
||||
public static string SupportDir { get { return supportDir.Value; } }
|
||||
static Lazy<string> supportDir = Exts.Lazy(GetSupportDir);
|
||||
|
||||
static string GetSupportDir()
|
||||
{
|
||||
// Use a local directory in the game root if it exists
|
||||
if (Directory.Exists("Support"))
|
||||
return "Support" + Path.DirectorySeparatorChar;
|
||||
// Use a local directory in the game root if it exists (shared with the system support dir)
|
||||
var localSupportDir = Path.Combine(GameDir, "Support");
|
||||
if (Directory.Exists(localSupportDir))
|
||||
return localSupportDir + Path.DirectorySeparatorChar;
|
||||
|
||||
var dir = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
|
||||
|
||||
@@ -93,12 +99,51 @@ namespace OpenRA
|
||||
return dir + Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
public static string GameDir { get { return AppDomain.CurrentDomain.BaseDirectory; } }
|
||||
/// <summary>
|
||||
/// Directory containing system-wide support files (mod metadata).
|
||||
/// This directory is not guaranteed to exist or be writable.
|
||||
/// Consumers are expected to check the validity of the returned value, and
|
||||
/// fall back to the user support directory if necessary.
|
||||
/// </summary>
|
||||
public static string SystemSupportDir { get { return systemSupportDir.Value; } }
|
||||
static Lazy<string> systemSupportDir = Exts.Lazy(GetSystemSupportDir);
|
||||
|
||||
static string GetSystemSupportDir()
|
||||
{
|
||||
// Use a local directory in the game root if it exists (shared with the system support dir)
|
||||
var localSupportDir = Path.Combine(GameDir, "Support");
|
||||
if (Directory.Exists(localSupportDir))
|
||||
return localSupportDir + Path.DirectorySeparatorChar;
|
||||
|
||||
switch (CurrentPlatform)
|
||||
{
|
||||
case PlatformType.Windows:
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenRA") + Path.DirectorySeparatorChar;
|
||||
case PlatformType.OSX:
|
||||
return "/Library/Application Support/OpenRA/";
|
||||
default:
|
||||
return "/var/games/openra/";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GameDir
|
||||
{
|
||||
get
|
||||
{
|
||||
var dir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
|
||||
// Add trailing DirectorySeparator for some buggy AppPool hosts
|
||||
if (!dir.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||
dir += Path.DirectorySeparatorChar;
|
||||
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Replaces special character prefixes with full paths.</summary>
|
||||
public static string ResolvePath(string path)
|
||||
{
|
||||
path = path.TrimEnd(new char[] { ' ', '\t' });
|
||||
path = path.TrimEnd(' ', '\t');
|
||||
|
||||
// Paths starting with ^ are relative to the support dir
|
||||
if (path.StartsWith("^", StringComparison.Ordinal))
|
||||
|
||||
@@ -47,12 +47,13 @@ namespace OpenRA
|
||||
public readonly bool Playable = true;
|
||||
public readonly int ClientIndex;
|
||||
public readonly PlayerReference PlayerReference;
|
||||
public readonly bool IsBot;
|
||||
public readonly string BotType;
|
||||
|
||||
/// <summary>The faction (including Random, etc) that was selected in the lobby.</summary>
|
||||
public readonly FactionInfo DisplayFaction;
|
||||
|
||||
public WinState WinState = WinState.Undefined;
|
||||
public bool IsBot;
|
||||
public int SpawnPoint;
|
||||
public bool HasObjectives = false;
|
||||
public bool Spectating;
|
||||
@@ -94,8 +95,6 @@ namespace OpenRA
|
||||
|
||||
public Player(World world, Session.Client client, PlayerReference pr)
|
||||
{
|
||||
string botType;
|
||||
|
||||
World = world;
|
||||
InternalName = pr.Name;
|
||||
PlayerReference = pr;
|
||||
@@ -105,8 +104,16 @@ namespace OpenRA
|
||||
{
|
||||
ClientIndex = client.Index;
|
||||
Color = client.Color;
|
||||
PlayerName = client.Name;
|
||||
botType = client.Bot;
|
||||
if (client.Bot != null)
|
||||
{
|
||||
var botInfo = world.Map.Rules.Actors["player"].TraitInfos<IBotInfo>().First(b => b.Type == client.Bot);
|
||||
var botsOfSameType = world.LobbyInfo.Clients.Where(c => c.Bot == client.Bot).ToArray();
|
||||
PlayerName = botsOfSameType.Length == 1 ? botInfo.Name : "{0} {1}".F(botInfo.Name, botsOfSameType.IndexOf(client) + 1);
|
||||
}
|
||||
else
|
||||
PlayerName = client.Name;
|
||||
|
||||
BotType = client.Bot;
|
||||
Faction = ChooseFaction(world, client.Faction, !pr.LockFaction);
|
||||
DisplayFaction = ChooseDisplayFaction(world, client.Faction);
|
||||
}
|
||||
@@ -119,7 +126,7 @@ namespace OpenRA
|
||||
NonCombatant = pr.NonCombatant;
|
||||
Playable = pr.Playable;
|
||||
Spectating = pr.Spectating;
|
||||
botType = pr.Bot;
|
||||
BotType = pr.Bot;
|
||||
Faction = ChooseFaction(world, pr.Faction, false);
|
||||
DisplayFaction = ChooseDisplayFaction(world, pr.Faction);
|
||||
}
|
||||
@@ -130,12 +137,12 @@ namespace OpenRA
|
||||
fogVisibilities = PlayerActor.TraitsImplementing<IFogVisibilityModifier>().ToArray();
|
||||
|
||||
// Enable the bot logic on the host
|
||||
IsBot = botType != null;
|
||||
IsBot = BotType != null;
|
||||
if (IsBot && Game.IsHost)
|
||||
{
|
||||
var logic = PlayerActor.TraitsImplementing<IBot>().FirstOrDefault(b => b.Info.Name == botType);
|
||||
var logic = PlayerActor.TraitsImplementing<IBot>().FirstOrDefault(b => b.Info.Type == BotType);
|
||||
if (logic == null)
|
||||
Log.Write("debug", "Invalid bot type: {0}", botType);
|
||||
Log.Write("debug", "Invalid bot type: {0}", BotType);
|
||||
else
|
||||
logic.Activate(this);
|
||||
}
|
||||
|
||||
89
OpenRA.Game/Primitives/ReadOnlyAdapterStream.cs
Normal file
89
OpenRA.Game/Primitives/ReadOnlyAdapterStream.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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;
|
||||
|
||||
namespace OpenRA.Primitives
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a read-only buffering layer so data can be streamed from sources where reading arbitrary amounts of
|
||||
/// data is difficult.
|
||||
/// </summary>
|
||||
public abstract class ReadOnlyAdapterStream : Stream
|
||||
{
|
||||
readonly Queue<byte> data = new Queue<byte>(1024);
|
||||
readonly Stream baseStream;
|
||||
|
||||
protected ReadOnlyAdapterStream(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException("stream");
|
||||
if (!stream.CanRead)
|
||||
throw new ArgumentException("stream must be readable.", "stream");
|
||||
|
||||
baseStream = stream;
|
||||
}
|
||||
|
||||
public sealed override bool CanSeek { get { return false; } }
|
||||
public sealed override bool CanRead { get { return true; } }
|
||||
public sealed override bool CanWrite { get { return false; } }
|
||||
|
||||
public sealed override long Length { get { throw new NotSupportedException(); } }
|
||||
public sealed override long Position
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public sealed override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
|
||||
public sealed override void SetLength(long value) { throw new NotSupportedException(); }
|
||||
public sealed override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
|
||||
public sealed override void Flush() { throw new NotSupportedException(); }
|
||||
|
||||
public sealed override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var copied = 0;
|
||||
ConsumeData(buffer, offset, count, ref copied);
|
||||
|
||||
var finished = false;
|
||||
while (copied < count && !finished)
|
||||
{
|
||||
finished = BufferData(baseStream, data);
|
||||
ConsumeData(buffer, offset, count, ref copied);
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data into a buffer, which will be used to satisfy <see cref="Read(byte[], int, int)"/> calls.
|
||||
/// </summary>
|
||||
/// <param name="baseStream">The source stream from which bytes should be read.</param>
|
||||
/// <param name="data">The queue where bytes should be enqueued. Do not dequeue from this buffer.</param>
|
||||
/// <returns>Return true if all data has been read; otherwise, false.</returns>
|
||||
protected abstract bool BufferData(Stream baseStream, Queue<byte> data);
|
||||
|
||||
void ConsumeData(byte[] buffer, int offset, int count, ref int copied)
|
||||
{
|
||||
while (copied < count && data.Count > 0)
|
||||
buffer[offset + copied++] = data.Dequeue();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
baseStream.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,15 @@ namespace OpenRA.Primitives
|
||||
public readonly long BaseOffset;
|
||||
public readonly long BaseCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SegmentStream"/> that wraps a subset of the source stream. This takes ownership of
|
||||
/// the source stream. The <see cref="SegmentStream"/> is dependent on the source and changes its underlying
|
||||
/// position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The source stream, of which only a segment should be exposed. Ownership is transferred
|
||||
/// to the <see cref="SegmentStream"/>.</param>
|
||||
/// <param name="offset">The offset at which the segment starts.</param>
|
||||
/// <param name="count">The length of the segment.</param>
|
||||
public SegmentStream(Stream stream, long offset, long count)
|
||||
{
|
||||
if (stream == null)
|
||||
@@ -90,7 +99,7 @@ namespace OpenRA.Primitives
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public static long GetOverallNestedOffset(Stream stream, out Stream overallBaseStream)
|
||||
static long GetOverallNestedOffset(Stream stream, out Stream overallBaseStream)
|
||||
{
|
||||
var offset = 0L;
|
||||
overallBaseStream = stream;
|
||||
@@ -99,5 +108,34 @@ namespace OpenRA.Primitives
|
||||
offset += segmentStream.BaseOffset + GetOverallNestedOffset(segmentStream.BaseStream, out overallBaseStream);
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Stream"/> that wraps a subset of the source stream without taking ownership of it,
|
||||
/// allowing it to be reused by the caller. The <see cref="Stream"/> is independent of the source stream and
|
||||
/// won't affect its position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The source stream, of which only a segment should be exposed. Ownership is retained by
|
||||
/// the caller.</param>
|
||||
/// <param name="offset">The offset at which the segment starts.</param>
|
||||
/// <param name="count">The length of the segment.</param>
|
||||
public static Stream CreateWithoutOwningStream(Stream stream, long offset, int count)
|
||||
{
|
||||
Stream parentStream;
|
||||
var nestedOffset = offset + GetOverallNestedOffset(stream, out parentStream);
|
||||
|
||||
// Special case FileStream - instead of creating an in-memory copy,
|
||||
// just reference the portion of the on-disk file that we need to save memory.
|
||||
// We use GetType instead of 'is' here since we can't handle any derived classes of FileStream.
|
||||
if (parentStream.GetType() == typeof(FileStream))
|
||||
{
|
||||
var path = ((FileStream)parentStream).Name;
|
||||
return new SegmentStream(File.OpenRead(path), nestedOffset, count);
|
||||
}
|
||||
|
||||
// For all other streams, create a copy in memory.
|
||||
// This uses more memory but is the only way in general to ensure the returned streams won't clash.
|
||||
stream.Seek(offset, SeekOrigin.Begin);
|
||||
return new MemoryStream(stream.ReadBytes(count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
OpenRA.Game/Primitives/ZipFileHelper.cs
Normal file
46
OpenRA.Game/Primitives/ZipFileHelper.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
|
||||
namespace OpenRA.Primitives
|
||||
{
|
||||
public static class ZipFileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ZipFile"/> with UTF8 encoding. Avoid using <see cref="ZipFile(Stream)"/> as you
|
||||
/// cannot be sure of the encoding that will be used.
|
||||
/// </summary>
|
||||
public static ZipFile Create(Stream stream)
|
||||
{
|
||||
// SharpZipLib uses this global as the encoding to use for all ZipFiles.
|
||||
// The initial value is the system code page, which causes several problems.
|
||||
// 1) On some systems, the code page for a certain encoding might not even be installed.
|
||||
// 2) The code page is different on every system, resulting in unpredictability.
|
||||
// 3) The code page might not work for decoding some archives.
|
||||
// We set the default to UTF8 instead which fixes all these problems.
|
||||
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
|
||||
return new ZipFile(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ZipFile"/> with UTF8 encoding. Avoid using <see cref="ZipFile(FileStream)"/> as you
|
||||
/// cannot be sure of the encoding that will be used.
|
||||
/// </summary>
|
||||
public static ZipFile Create(FileStream stream)
|
||||
{
|
||||
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
|
||||
return new ZipFile(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA
|
||||
public SpriteRenderer WorldSpriteRenderer { get; private set; }
|
||||
public SpriteRenderer WorldRgbaSpriteRenderer { get; private set; }
|
||||
public RgbaColorRenderer WorldRgbaColorRenderer { get; private set; }
|
||||
public VoxelRenderer WorldVoxelRenderer { get; private set; }
|
||||
public ModelRenderer WorldModelRenderer { get; private set; }
|
||||
public RgbaColorRenderer RgbaColorRenderer { get; private set; }
|
||||
public SpriteRenderer RgbaSpriteRenderer { get; private set; }
|
||||
public SpriteRenderer SpriteRenderer { get; private set; }
|
||||
@@ -59,7 +59,7 @@ namespace OpenRA
|
||||
WorldSpriteRenderer = new SpriteRenderer(this, Device.CreateShader("shp"));
|
||||
WorldRgbaSpriteRenderer = new SpriteRenderer(this, Device.CreateShader("rgba"));
|
||||
WorldRgbaColorRenderer = new RgbaColorRenderer(this, Device.CreateShader("color"));
|
||||
WorldVoxelRenderer = new VoxelRenderer(this, Device.CreateShader("vxl"));
|
||||
WorldModelRenderer = new ModelRenderer(this, Device.CreateShader("model"));
|
||||
RgbaColorRenderer = new RgbaColorRenderer(this, Device.CreateShader("color"));
|
||||
RgbaSpriteRenderer = new SpriteRenderer(this, Device.CreateShader("rgba"));
|
||||
SpriteRenderer = new SpriteRenderer(this, Device.CreateShader("shp"));
|
||||
@@ -136,7 +136,7 @@ namespace OpenRA
|
||||
lastZoom = zoom;
|
||||
WorldRgbaSpriteRenderer.SetViewportParams(Resolution, depthScale, depthOffset, zoom, scroll);
|
||||
WorldSpriteRenderer.SetViewportParams(Resolution, depthScale, depthOffset, zoom, scroll);
|
||||
WorldVoxelRenderer.SetViewportParams(Resolution, zoom, scroll);
|
||||
WorldModelRenderer.SetViewportParams(Resolution, zoom, scroll);
|
||||
WorldRgbaColorRenderer.SetViewportParams(Resolution, depthScale, depthOffset, zoom, scroll);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ namespace OpenRA
|
||||
SpriteRenderer.SetPalette(currentPaletteTexture);
|
||||
WorldSpriteRenderer.SetPalette(currentPaletteTexture);
|
||||
WorldRgbaSpriteRenderer.SetPalette(currentPaletteTexture);
|
||||
WorldVoxelRenderer.SetPalette(currentPaletteTexture);
|
||||
WorldModelRenderer.SetPalette(currentPaletteTexture);
|
||||
}
|
||||
|
||||
public void EndFrame(IInputHandler inputHandler)
|
||||
@@ -267,7 +267,7 @@ namespace OpenRA
|
||||
public void Dispose()
|
||||
{
|
||||
Device.Dispose();
|
||||
WorldVoxelRenderer.Dispose();
|
||||
WorldModelRenderer.Dispose();
|
||||
tempBuffer.Dispose();
|
||||
if (fontSheetBuilder != null)
|
||||
fontSheetBuilder.Dispose();
|
||||
|
||||
@@ -19,10 +19,23 @@ namespace OpenRA
|
||||
{
|
||||
public class Selection
|
||||
{
|
||||
public int Hash { get; private set; }
|
||||
public IEnumerable<Actor> Actors { get { return actors; } }
|
||||
readonly HashSet<Actor> actors = new HashSet<Actor>();
|
||||
|
||||
void UpdateHash()
|
||||
{
|
||||
// Not a real hash, but things checking this only care about checking when the selection has changed
|
||||
// For this purpose, having a false positive (forcing a refresh when nothing changed) is much better
|
||||
// than a false negative (selection state mismatch)
|
||||
Hash += 1;
|
||||
}
|
||||
|
||||
public void Add(World w, Actor a)
|
||||
{
|
||||
actors.Add(a);
|
||||
UpdateHash();
|
||||
|
||||
foreach (var sel in a.TraitsImplementing<INotifySelected>())
|
||||
sel.Selected(a);
|
||||
foreach (var ns in w.WorldActor.TraitsImplementing<INotifySelection>())
|
||||
@@ -58,6 +71,8 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
UpdateHash();
|
||||
|
||||
foreach (var a in newSelection)
|
||||
foreach (var sel in a.TraitsImplementing<INotifySelected>())
|
||||
sel.Selected(a);
|
||||
@@ -85,12 +100,17 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Actor> Actors { get { return actors; } }
|
||||
public void Clear() { actors.Clear(); }
|
||||
public void Clear()
|
||||
{
|
||||
actors.Clear();
|
||||
UpdateHash();
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
actors.RemoveWhere(a => !a.IsInWorld || (!a.Owner.IsAlliedWith(world.RenderPlayer) && world.FogObscures(a)));
|
||||
var removed = actors.RemoveWhere(a => !a.IsInWorld || (!a.Owner.IsAlliedWith(world.RenderPlayer) && world.FogObscures(a)));
|
||||
if (removed > 0)
|
||||
UpdateHash();
|
||||
|
||||
foreach (var cg in controlGroups.Values)
|
||||
{
|
||||
|
||||
@@ -306,7 +306,7 @@ namespace OpenRA.Server
|
||||
IpAddress = ((IPEndPoint)newConn.Socket.RemoteEndPoint).Address.ToString(),
|
||||
Index = newConn.PlayerIndex,
|
||||
Slot = LobbyInfo.FirstEmptySlot(),
|
||||
PreferredColor = handshake.Client.Color,
|
||||
PreferredColor = handshake.Client.PreferredColor,
|
||||
Color = handshake.Client.Color,
|
||||
Faction = "Random",
|
||||
SpawnPoint = 0,
|
||||
@@ -375,7 +375,7 @@ namespace OpenRA.Server
|
||||
Log.Write("server", "{0} ({1}) has joined the game.",
|
||||
client.Name, newConn.Socket.RemoteEndPoint);
|
||||
|
||||
if (Dedicated || !LobbyInfo.IsSinglePlayer)
|
||||
if (LobbyInfo.NonBotClients.Count() > 1)
|
||||
SendMessage("{0} has joined the game.".F(client.Name));
|
||||
|
||||
// Send initial ping
|
||||
@@ -392,7 +392,7 @@ namespace OpenRA.Server
|
||||
SendOrderTo(newConn, "Message", motd);
|
||||
}
|
||||
|
||||
if (!LobbyInfo.IsSinglePlayer && Map.DefinesUnsafeCustomRules)
|
||||
if (Map.DefinesUnsafeCustomRules)
|
||||
SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change.");
|
||||
|
||||
if (!LobbyInfo.GlobalSettings.EnableSingleplayer)
|
||||
@@ -679,7 +679,7 @@ namespace OpenRA.Server
|
||||
}
|
||||
|
||||
// HACK: Turn down the latency if there is only one real player
|
||||
if (LobbyInfo.IsSinglePlayer)
|
||||
if (LobbyInfo.NonBotClients.Count() == 1)
|
||||
LobbyInfo.GlobalSettings.OrderLatency = 1;
|
||||
|
||||
SyncLobbyInfo();
|
||||
|
||||
@@ -51,8 +51,6 @@ namespace OpenRA
|
||||
[Desc("Locks the game with a password.")]
|
||||
public string Password = "";
|
||||
|
||||
public string MasterServer = "http://master.openra.net/";
|
||||
|
||||
[Desc("Allow users to enable NAT discovery for external IP detection and automatic port forwarding.")]
|
||||
public bool DiscoverNatDevices = false;
|
||||
|
||||
@@ -120,6 +118,9 @@ namespace OpenRA
|
||||
[Desc("At which frames per second to cap the framerate.")]
|
||||
public int MaxFramerate = 60;
|
||||
|
||||
[Desc("Disable high resolution DPI scaling on Windows operating systems.")]
|
||||
public bool DisableWindowsDPIScaling = true;
|
||||
|
||||
public int BatchSize = 8192;
|
||||
public int SheetSize = 2048;
|
||||
|
||||
@@ -153,13 +154,11 @@ namespace OpenRA
|
||||
|
||||
public class GameSettings
|
||||
{
|
||||
[Desc("Load a specific mod on startup.")]
|
||||
public string Mod = null;
|
||||
public string PreviousMod = "ra";
|
||||
|
||||
public string Platform = "Default";
|
||||
|
||||
public bool ViewportEdgeScroll = true;
|
||||
public int ViewportEdgeScrollMargin = 5;
|
||||
|
||||
public bool LockMouseWindow = false;
|
||||
public MouseScrollType MiddleMouseScroll = MouseScrollType.Standard;
|
||||
public MouseScrollType RightMouseScroll = MouseScrollType.Disabled;
|
||||
@@ -175,13 +174,11 @@ namespace OpenRA
|
||||
public bool DrawTargetLine = true;
|
||||
|
||||
public bool AllowDownloading = true;
|
||||
public string MapRepository = "http://resource.openra.net/map/";
|
||||
|
||||
public bool AllowZoom = true;
|
||||
public Modifiers ZoomModifier = Modifiers.Ctrl;
|
||||
|
||||
public bool FetchNews = true;
|
||||
public string NewsUrl = "http://master.openra.net/gamenews";
|
||||
|
||||
public MPGameFilters MPGameFilters = MPGameFilters.Waiting | MPGameFilters.Empty | MPGameFilters.Protected | MPGameFilters.Started;
|
||||
}
|
||||
@@ -228,7 +225,10 @@ namespace OpenRA
|
||||
public Hotkey StopKey = new Hotkey(Keycode.S, Modifiers.None);
|
||||
public Hotkey ScatterKey = new Hotkey(Keycode.X, Modifiers.Ctrl);
|
||||
public Hotkey DeployKey = new Hotkey(Keycode.F, Modifiers.None);
|
||||
public Hotkey StanceCycleKey = new Hotkey(Keycode.Z, Modifiers.Ctrl);
|
||||
public Hotkey StanceHoldFireKey = new Hotkey(Keycode.F, Modifiers.Alt);
|
||||
public Hotkey StanceReturnFireKey = new Hotkey(Keycode.D, Modifiers.Alt);
|
||||
public Hotkey StanceDefendKey = new Hotkey(Keycode.S, Modifiers.Alt);
|
||||
public Hotkey StanceAttackAnythingKey = new Hotkey(Keycode.A, Modifiers.Alt);
|
||||
public Hotkey GuardKey = new Hotkey(Keycode.D, Modifiers.None);
|
||||
|
||||
public Hotkey ObserverCombinedView = new Hotkey(Keycode.MINUS, Modifiers.None);
|
||||
@@ -330,7 +330,6 @@ namespace OpenRA
|
||||
public string Hostname = "irc.openra.net";
|
||||
public int Port = 6667;
|
||||
public string Channel = "lobby";
|
||||
public string Nickname = "Newbie";
|
||||
public string QuitMessage = "Battle control terminated!";
|
||||
public string TimestampFormat = "HH:mm";
|
||||
public bool ConnectAutomatically = false;
|
||||
@@ -338,18 +337,23 @@ namespace OpenRA
|
||||
|
||||
public class Settings
|
||||
{
|
||||
string settingsFile;
|
||||
readonly string settingsFile;
|
||||
|
||||
public PlayerSettings Player = new PlayerSettings();
|
||||
public GameSettings Game = new GameSettings();
|
||||
public SoundSettings Sound = new SoundSettings();
|
||||
public GraphicSettings Graphics = new GraphicSettings();
|
||||
public ServerSettings Server = new ServerSettings();
|
||||
public DebugSettings Debug = new DebugSettings();
|
||||
public KeySettings Keys = new KeySettings();
|
||||
public ChatSettings Chat = new ChatSettings();
|
||||
public readonly PlayerSettings Player = new PlayerSettings();
|
||||
public readonly GameSettings Game = new GameSettings();
|
||||
public readonly SoundSettings Sound = new SoundSettings();
|
||||
public readonly GraphicSettings Graphics = new GraphicSettings();
|
||||
public readonly ServerSettings Server = new ServerSettings();
|
||||
public readonly DebugSettings Debug = new DebugSettings();
|
||||
public readonly KeySettings Keys = new KeySettings();
|
||||
public readonly ChatSettings Chat = new ChatSettings();
|
||||
|
||||
public Dictionary<string, object> Sections;
|
||||
public readonly Dictionary<string, object> Sections;
|
||||
|
||||
// A direct clone of the file loaded from disk.
|
||||
// Any changed settings will be merged over this on save,
|
||||
// allowing us to persist any unknown configuration keys
|
||||
readonly List<MiniYamlNode> yamlCache = new List<MiniYamlNode>();
|
||||
|
||||
public Settings(string file, Arguments args)
|
||||
{
|
||||
@@ -375,11 +379,13 @@ namespace OpenRA
|
||||
|
||||
if (File.Exists(settingsFile))
|
||||
{
|
||||
var yaml = MiniYaml.DictFromFile(settingsFile);
|
||||
|
||||
foreach (var kv in Sections)
|
||||
if (yaml.ContainsKey(kv.Key))
|
||||
LoadSectionYaml(yaml[kv.Key], kv.Value);
|
||||
yamlCache = MiniYaml.FromFile(settingsFile);
|
||||
foreach (var yamlSection in yamlCache)
|
||||
{
|
||||
object settingsSection;
|
||||
if (Sections.TryGetValue(yamlSection.Key, out settingsSection))
|
||||
LoadSectionYaml(yamlSection.Value, settingsSection);
|
||||
}
|
||||
}
|
||||
|
||||
// Override with commandline args
|
||||
@@ -397,11 +403,39 @@ namespace OpenRA
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
foreach (var kv in Sections)
|
||||
root.Add(new MiniYamlNode(kv.Key, FieldSaver.SaveDifferences(kv.Value, Activator.CreateInstance(kv.Value.GetType()))));
|
||||
{
|
||||
var sectionYaml = yamlCache.FirstOrDefault(x => x.Key == kv.Key);
|
||||
if (sectionYaml == null)
|
||||
{
|
||||
sectionYaml = new MiniYamlNode(kv.Key, new MiniYaml(""));
|
||||
yamlCache.Add(sectionYaml);
|
||||
}
|
||||
|
||||
root.WriteToFile(settingsFile);
|
||||
var defaultValues = Activator.CreateInstance(kv.Value.GetType());
|
||||
var fields = FieldLoader.GetTypeLoadInfo(kv.Value.GetType());
|
||||
foreach (var fli in fields)
|
||||
{
|
||||
var serialized = FieldSaver.FormatValue(kv.Value, fli.Field);
|
||||
var defaultSerialized = FieldSaver.FormatValue(defaultValues, fli.Field);
|
||||
|
||||
// Fields with their default value are not saved in the settings yaml
|
||||
// Make sure that we erase any previously defined custom values
|
||||
if (serialized == defaultSerialized)
|
||||
sectionYaml.Value.Nodes.RemoveAll(n => n.Key == fli.YamlName);
|
||||
else
|
||||
{
|
||||
// Update or add the custom value
|
||||
var fieldYaml = sectionYaml.Value.Nodes.FirstOrDefault(n => n.Key == fli.YamlName);
|
||||
if (fieldYaml != null)
|
||||
fieldYaml.Value.Value = serialized;
|
||||
else
|
||||
sectionYaml.Value.Nodes.Add(new MiniYamlNode(fli.YamlName, new MiniYaml(serialized)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yamlCache.WriteToFile(settingsFile);
|
||||
}
|
||||
|
||||
static string SanitizedName(string dirty)
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace OpenRA
|
||||
public sealed class Sound : IDisposable
|
||||
{
|
||||
readonly ISoundEngine soundEngine;
|
||||
ISoundLoader[] loaders;
|
||||
IReadOnlyFileSystem fileSystem;
|
||||
Cache<string, ISoundSource> sounds;
|
||||
ISoundSource rawSource;
|
||||
ISound music;
|
||||
@@ -52,12 +54,12 @@ namespace OpenRA
|
||||
MuteAudio();
|
||||
}
|
||||
|
||||
ISoundSource LoadSound(ISoundLoader[] loaders, IReadOnlyFileSystem fileSystem, string filename)
|
||||
T LoadSound<T>(string filename, Func<ISoundFormat, T> loadFormat)
|
||||
{
|
||||
if (!fileSystem.Exists(filename))
|
||||
{
|
||||
Log.Write("sound", "LoadSound, file does not exist: {0}", filename);
|
||||
return null;
|
||||
return default(T);
|
||||
}
|
||||
|
||||
using (var stream = fileSystem.Open(filename))
|
||||
@@ -68,8 +70,7 @@ namespace OpenRA
|
||||
stream.Position = 0;
|
||||
if (loader.TryParseSound(stream, out soundFormat))
|
||||
{
|
||||
var source = soundEngine.AddSoundSourceFromMemory(
|
||||
soundFormat.GetPCMInputStream().ReadAllBytes(), soundFormat.Channels, soundFormat.SampleBits, soundFormat.SampleRate);
|
||||
var source = loadFormat(soundFormat);
|
||||
soundFormat.Dispose();
|
||||
return source;
|
||||
}
|
||||
@@ -81,10 +82,20 @@ namespace OpenRA
|
||||
|
||||
public void Initialize(ISoundLoader[] loaders, IReadOnlyFileSystem fileSystem)
|
||||
{
|
||||
sounds = new Cache<string, ISoundSource>(s => LoadSound(loaders, fileSystem, s));
|
||||
StopMusic();
|
||||
soundEngine.StopAllSounds();
|
||||
|
||||
if (sounds != null)
|
||||
foreach (var soundSource in sounds.Values)
|
||||
if (soundSource != null)
|
||||
soundSource.Dispose();
|
||||
|
||||
this.loaders = loaders;
|
||||
this.fileSystem = fileSystem;
|
||||
Func<ISoundFormat, ISoundSource> loadIntoMemory = soundFormat => soundEngine.AddSoundSourceFromMemory(
|
||||
soundFormat.GetPCMInputStream().ReadAllBytes(), soundFormat.Channels, soundFormat.SampleBits, soundFormat.SampleRate);
|
||||
sounds = new Cache<string, ISoundSource>(filename => LoadSound(filename, loadIntoMemory));
|
||||
currentSounds = new Dictionary<uint, ISound>();
|
||||
music = null;
|
||||
currentMusic = null;
|
||||
video = null;
|
||||
}
|
||||
|
||||
@@ -163,7 +174,7 @@ namespace OpenRA
|
||||
public void Tick()
|
||||
{
|
||||
// Song finished
|
||||
if (MusicPlaying && !music.Playing)
|
||||
if (MusicPlaying && music.Complete)
|
||||
{
|
||||
StopMusic();
|
||||
onMusicComplete();
|
||||
@@ -195,11 +206,17 @@ namespace OpenRA
|
||||
|
||||
StopMusic();
|
||||
|
||||
var sound = sounds[m.Filename];
|
||||
if (sound == null)
|
||||
return;
|
||||
Func<ISoundFormat, ISound> stream = soundFormat => soundEngine.Play2DStream(
|
||||
soundFormat.GetPCMInputStream(), soundFormat.Channels, soundFormat.SampleBits, soundFormat.SampleRate,
|
||||
false, true, WPos.Zero, MusicVolume);
|
||||
music = LoadSound(m.Filename, stream);
|
||||
|
||||
if (music == null)
|
||||
{
|
||||
onMusicComplete = null;
|
||||
return;
|
||||
}
|
||||
|
||||
music = soundEngine.Play2D(sound, false, true, WPos.Zero, MusicVolume, false);
|
||||
currentMusic = m;
|
||||
MusicPlaying = true;
|
||||
}
|
||||
@@ -222,10 +239,13 @@ namespace OpenRA
|
||||
public void StopMusic()
|
||||
{
|
||||
if (music != null)
|
||||
{
|
||||
soundEngine.StopSound(music);
|
||||
music = null;
|
||||
}
|
||||
|
||||
MusicPlaying = false;
|
||||
currentMusic = null;
|
||||
MusicPlaying = false;
|
||||
}
|
||||
|
||||
public void PauseMusic()
|
||||
@@ -388,6 +408,11 @@ namespace OpenRA
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAudio();
|
||||
if (sounds != null)
|
||||
foreach (var soundSource in sounds.Values)
|
||||
if (soundSource != null)
|
||||
soundSource.Dispose();
|
||||
soundEngine.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -18,6 +19,7 @@ namespace OpenRA
|
||||
SoundDevice[] AvailableDevices();
|
||||
ISoundSource AddSoundSourceFromMemory(byte[] data, int channels, int sampleBits, int sampleRate);
|
||||
ISound Play2D(ISoundSource sound, bool loop, bool relative, WPos pos, float volume, bool attenuateVolume);
|
||||
ISound Play2DStream(Stream stream, int channels, int sampleBits, int sampleRate, bool loop, bool relative, WPos pos, float volume);
|
||||
float Volume { get; set; }
|
||||
void PauseSound(ISound sound, bool paused);
|
||||
void StopSound(ISound sound);
|
||||
@@ -39,13 +41,13 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public interface ISoundSource { }
|
||||
public interface ISoundSource : IDisposable { }
|
||||
|
||||
public interface ISound
|
||||
{
|
||||
float Volume { get; set; }
|
||||
float SeekPosition { get; }
|
||||
bool Playing { get; }
|
||||
bool Complete { get; }
|
||||
void SetPosition(WPos pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -117,7 +118,17 @@ namespace OpenRA
|
||||
public static byte[] ReadAllBytes(this Stream s)
|
||||
{
|
||||
using (s)
|
||||
return s.ReadBytes((int)(s.Length - s.Position));
|
||||
{
|
||||
if (s.CanSeek)
|
||||
return s.ReadBytes((int)(s.Length - s.Position));
|
||||
|
||||
var bytes = new List<byte>();
|
||||
var buffer = new byte[1024];
|
||||
int count;
|
||||
while ((count = s.Read(buffer, 0, buffer.Length)) > 0)
|
||||
bytes.AddRange(buffer.Take(count));
|
||||
return bytes.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this Stream s, byte[] data)
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace OpenRA
|
||||
connect = Connect;
|
||||
|
||||
if (!string.IsNullOrEmpty(URI))
|
||||
connect = URI.Replace("openra://", "").TrimEnd('/');
|
||||
connect = URI.Substring(URI.IndexOf("://", System.StringComparison.Ordinal) + 3).TrimEnd('/');
|
||||
|
||||
return connect;
|
||||
}
|
||||
|
||||
@@ -51,10 +51,13 @@ namespace OpenRA
|
||||
var exceptionName = "exception-" + DateTime.UtcNow.ToString("yyyy-MM-ddTHHmmssZ", CultureInfo.InvariantCulture) + ".log";
|
||||
Log.AddChannel("exception", exceptionName);
|
||||
|
||||
if (Game.EngineVersion != null)
|
||||
Log.Write("exception", "OpenRA engine version {0}", Game.EngineVersion);
|
||||
|
||||
if (Game.ModData != null)
|
||||
{
|
||||
var mod = Game.ModData.Manifest.Metadata;
|
||||
Log.Write("exception", "{0} Mod at Version {1}", mod.Title, mod.Version);
|
||||
Log.Write("exception", "{0} mod version {1}", mod.Title, mod.Version);
|
||||
}
|
||||
|
||||
if (Game.OrderManager != null && Game.OrderManager.World != null && Game.OrderManager.World.Map != null)
|
||||
|
||||
@@ -18,14 +18,12 @@ using Expressions = System.Linq.Expressions;
|
||||
|
||||
namespace OpenRA.Support
|
||||
{
|
||||
public class ConditionExpression
|
||||
public abstract class VariableExpression
|
||||
{
|
||||
public readonly string Expression;
|
||||
readonly HashSet<string> variables = new HashSet<string>();
|
||||
public IEnumerable<string> Variables { get { return variables; } }
|
||||
|
||||
readonly Func<IReadOnlyDictionary<string, int>, int> asFunction;
|
||||
|
||||
enum CharClass { Whitespace, Operator, Mixed, Id, Digit }
|
||||
|
||||
static CharClass CharClassOf(char c)
|
||||
@@ -92,7 +90,7 @@ namespace OpenRA.Support
|
||||
enum Associativity { Left, Right }
|
||||
|
||||
[Flags]
|
||||
enum OperandSides
|
||||
enum Sides
|
||||
{
|
||||
// Value type
|
||||
None = 0,
|
||||
@@ -161,18 +159,34 @@ namespace OpenRA.Support
|
||||
{
|
||||
public readonly string Symbol;
|
||||
public readonly Precedence Precedence;
|
||||
public readonly OperandSides OperandSides;
|
||||
public readonly Sides OperandSides;
|
||||
public readonly Sides WhitespaceSides;
|
||||
public readonly Associativity Associativity;
|
||||
public readonly Grouping Opens;
|
||||
public readonly Grouping Closes;
|
||||
|
||||
public TokenTypeInfo(string symbol, Precedence precedence, OperandSides operandSides = OperandSides.None,
|
||||
public TokenTypeInfo(string symbol, Precedence precedence, Sides operandSides = Sides.None,
|
||||
Associativity associativity = Associativity.Left,
|
||||
Grouping opens = Grouping.None, Grouping closes = Grouping.None)
|
||||
{
|
||||
Symbol = symbol;
|
||||
Precedence = precedence;
|
||||
OperandSides = operandSides;
|
||||
WhitespaceSides = Sides.None;
|
||||
Associativity = associativity;
|
||||
Opens = opens;
|
||||
Closes = closes;
|
||||
}
|
||||
|
||||
public TokenTypeInfo(string symbol, Precedence precedence, Sides operandSides,
|
||||
Sides whitespaceSides,
|
||||
Associativity associativity = Associativity.Left,
|
||||
Grouping opens = Grouping.None, Grouping closes = Grouping.None)
|
||||
{
|
||||
Symbol = symbol;
|
||||
Precedence = precedence;
|
||||
OperandSides = operandSides;
|
||||
WhitespaceSides = whitespaceSides;
|
||||
Associativity = associativity;
|
||||
Opens = opens;
|
||||
Closes = closes;
|
||||
@@ -183,10 +197,11 @@ namespace OpenRA.Support
|
||||
{
|
||||
Symbol = symbol;
|
||||
Precedence = precedence;
|
||||
WhitespaceSides = Sides.None;
|
||||
OperandSides = opens == Grouping.None ?
|
||||
(closes == Grouping.None ? OperandSides.None : OperandSides.Left)
|
||||
(closes == Grouping.None ? Sides.None : Sides.Left)
|
||||
:
|
||||
(closes == Grouping.None ? OperandSides.Right : OperandSides.Both);
|
||||
(closes == Grouping.None ? Sides.Right : Sides.Both);
|
||||
Associativity = associativity;
|
||||
Opens = opens;
|
||||
Closes = closes;
|
||||
@@ -221,52 +236,52 @@ namespace OpenRA.Support
|
||||
yield return new TokenTypeInfo(")", Precedence.Parens, Grouping.None, Grouping.Parens);
|
||||
continue;
|
||||
case TokenType.Not:
|
||||
yield return new TokenTypeInfo("!", Precedence.Unary, OperandSides.Right, Associativity.Right);
|
||||
yield return new TokenTypeInfo("!", Precedence.Unary, Sides.Right, Associativity.Right);
|
||||
continue;
|
||||
case TokenType.OnesComplement:
|
||||
yield return new TokenTypeInfo("~", Precedence.Unary, OperandSides.Right, Associativity.Right);
|
||||
yield return new TokenTypeInfo("~", Precedence.Unary, Sides.Right, Associativity.Right);
|
||||
continue;
|
||||
case TokenType.Negate:
|
||||
yield return new TokenTypeInfo("-", Precedence.Unary, OperandSides.Right, Associativity.Right);
|
||||
yield return new TokenTypeInfo("-", Precedence.Unary, Sides.Right, Associativity.Right);
|
||||
continue;
|
||||
case TokenType.And:
|
||||
yield return new TokenTypeInfo("&&", Precedence.And, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("&&", Precedence.And, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.Or:
|
||||
yield return new TokenTypeInfo("||", Precedence.Or, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("||", Precedence.Or, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.Equals:
|
||||
yield return new TokenTypeInfo("==", Precedence.Equality, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("==", Precedence.Equality, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.NotEquals:
|
||||
yield return new TokenTypeInfo("!=", Precedence.Equality, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("!=", Precedence.Equality, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.LessThan:
|
||||
yield return new TokenTypeInfo("<", Precedence.Relation, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("<", Precedence.Relation, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.LessThanOrEqual:
|
||||
yield return new TokenTypeInfo("<=", Precedence.Relation, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("<=", Precedence.Relation, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.GreaterThan:
|
||||
yield return new TokenTypeInfo(">", Precedence.Relation, OperandSides.Both);
|
||||
yield return new TokenTypeInfo(">", Precedence.Relation, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.GreaterThanOrEqual:
|
||||
yield return new TokenTypeInfo(">=", Precedence.Relation, OperandSides.Both);
|
||||
yield return new TokenTypeInfo(">=", Precedence.Relation, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.Add:
|
||||
yield return new TokenTypeInfo("+", Precedence.Addition, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("+", Precedence.Addition, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.Subtract:
|
||||
yield return new TokenTypeInfo("-", Precedence.Addition, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("-", Precedence.Addition, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.Multiply:
|
||||
yield return new TokenTypeInfo("*", Precedence.Multiplication, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("*", Precedence.Multiplication, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.Divide:
|
||||
yield return new TokenTypeInfo("/", Precedence.Multiplication, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("/", Precedence.Multiplication, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
case TokenType.Modulo:
|
||||
yield return new TokenTypeInfo("%", Precedence.Multiplication, OperandSides.Both);
|
||||
yield return new TokenTypeInfo("%", Precedence.Multiplication, Sides.Both, Sides.Both);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -279,7 +294,7 @@ namespace OpenRA.Support
|
||||
|
||||
static bool HasRightOperand(TokenType type)
|
||||
{
|
||||
return ((int)TokenTypeInfos[(int)type].OperandSides & (int)OperandSides.Right) != 0;
|
||||
return ((int)TokenTypeInfos[(int)type].OperandSides & (int)Sides.Right) != 0;
|
||||
}
|
||||
|
||||
static bool IsLeftOperandOrNone(TokenType type)
|
||||
@@ -287,6 +302,21 @@ namespace OpenRA.Support
|
||||
return type == TokenType.Invalid || HasRightOperand(type);
|
||||
}
|
||||
|
||||
static bool RequiresWhitespaceAfter(TokenType type)
|
||||
{
|
||||
return ((int)TokenTypeInfos[(int)type].WhitespaceSides & (int)Sides.Right) != 0;
|
||||
}
|
||||
|
||||
static bool RequiresWhitespaceBefore(TokenType type)
|
||||
{
|
||||
return ((int)TokenTypeInfos[(int)type].WhitespaceSides & (int)Sides.Left) != 0;
|
||||
}
|
||||
|
||||
static string GetTokenSymbol(TokenType type)
|
||||
{
|
||||
return TokenTypeInfos[(int)type].Symbol;
|
||||
}
|
||||
|
||||
class Token
|
||||
{
|
||||
public readonly TokenType Type;
|
||||
@@ -295,10 +325,10 @@ namespace OpenRA.Support
|
||||
public virtual string Symbol { get { return TokenTypeInfos[(int)Type].Symbol; } }
|
||||
|
||||
public int Precedence { get { return (int)TokenTypeInfos[(int)Type].Precedence; } }
|
||||
public OperandSides OperandSides { get { return TokenTypeInfos[(int)Type].OperandSides; } }
|
||||
public Sides OperandSides { get { return TokenTypeInfos[(int)Type].OperandSides; } }
|
||||
public Associativity Associativity { get { return TokenTypeInfos[(int)Type].Associativity; } }
|
||||
public bool LeftOperand { get { return ((int)TokenTypeInfos[(int)Type].OperandSides & (int)OperandSides.Left) != 0; } }
|
||||
public bool RightOperand { get { return ((int)TokenTypeInfos[(int)Type].OperandSides & (int)OperandSides.Right) != 0; } }
|
||||
public bool LeftOperand { get { return ((int)TokenTypeInfos[(int)Type].OperandSides & (int)Sides.Left) != 0; } }
|
||||
public bool RightOperand { get { return ((int)TokenTypeInfos[(int)Type].OperandSides & (int)Sides.Right) != 0; } }
|
||||
|
||||
public Grouping Opens { get { return TokenTypeInfos[(int)Type].Opens; } }
|
||||
public Grouping Closes { get { return TokenTypeInfos[(int)Type].Closes; } }
|
||||
@@ -322,7 +352,7 @@ namespace OpenRA.Support
|
||||
cc = CharClassOf(expression[i]);
|
||||
if (cc != CharClass.Digit)
|
||||
{
|
||||
if (cc != CharClass.Whitespace && cc != CharClass.Operator)
|
||||
if (cc != CharClass.Whitespace && cc != CharClass.Operator && cc != CharClass.Mixed)
|
||||
throw new InvalidDataException("Number {0} and variable merged at index {1}".F(
|
||||
int.Parse(expression.Substring(start, i - start)), start));
|
||||
|
||||
@@ -336,6 +366,15 @@ namespace OpenRA.Support
|
||||
return false;
|
||||
}
|
||||
|
||||
static TokenType VariableOrKeyword(string expression, int start, ref int i)
|
||||
{
|
||||
if (CharClassOf(expression[i - 1]) == CharClass.Mixed)
|
||||
throw new InvalidDataException("Invalid identifier end character at index {0} for `{1}`".F(
|
||||
i - 1, expression.Substring(start, i - start)));
|
||||
|
||||
return VariableOrKeyword(expression, start, i - start);
|
||||
}
|
||||
|
||||
static TokenType VariableOrKeyword(string expression, int start, int length)
|
||||
{
|
||||
var i = start;
|
||||
@@ -466,10 +505,10 @@ namespace OpenRA.Support
|
||||
{
|
||||
cc = CharClassOf(expression[i]);
|
||||
if (cc == CharClass.Whitespace || cc == CharClass.Operator)
|
||||
return VariableOrKeyword(expression, start, i - start);
|
||||
return VariableOrKeyword(expression, start, ref i);
|
||||
}
|
||||
|
||||
return VariableOrKeyword(expression, start, i - start);
|
||||
return VariableOrKeyword(expression, start, ref i);
|
||||
}
|
||||
|
||||
public static Token GetNext(string expression, ref int i, TokenType lastType = TokenType.Invalid)
|
||||
@@ -477,16 +516,30 @@ namespace OpenRA.Support
|
||||
if (i == expression.Length)
|
||||
return null;
|
||||
|
||||
// Ignore whitespace
|
||||
while (CharClassOf(expression[i]) == CharClass.Whitespace)
|
||||
// Check and eat whitespace
|
||||
var whitespaceBefore = false;
|
||||
if (CharClassOf(expression[i]) == CharClass.Whitespace)
|
||||
{
|
||||
if (++i == expression.Length)
|
||||
return null;
|
||||
whitespaceBefore = true;
|
||||
while (CharClassOf(expression[i]) == CharClass.Whitespace)
|
||||
{
|
||||
if (++i == expression.Length)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (lastType == TokenType.Invalid)
|
||||
whitespaceBefore = true;
|
||||
else if (RequiresWhitespaceAfter(lastType))
|
||||
throw new InvalidDataException("Missing whitespace at index {0}, after `{1}` operator."
|
||||
.F(i, GetTokenSymbol(lastType)));
|
||||
|
||||
var start = i;
|
||||
|
||||
var type = GetNextType(expression, ref i, lastType);
|
||||
if (!whitespaceBefore && RequiresWhitespaceBefore(type))
|
||||
throw new InvalidDataException("Missing whitespace at index {0}, before `{1}` operator."
|
||||
.F(i, GetTokenSymbol(type)));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case TokenType.Number:
|
||||
@@ -525,15 +578,19 @@ namespace OpenRA.Support
|
||||
}
|
||||
}
|
||||
|
||||
public ConditionExpression(string expression)
|
||||
public VariableExpression(string expression)
|
||||
{
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
Expression Build(ExpressionType resultType)
|
||||
{
|
||||
var tokens = new List<Token>();
|
||||
var currentOpeners = new Stack<Token>();
|
||||
Token lastToken = null;
|
||||
for (var i = 0;;)
|
||||
{
|
||||
var token = Token.GetNext(expression, ref i, lastToken != null ? lastToken.Type : TokenType.Invalid);
|
||||
var token = Token.GetNext(Expression, ref i, lastToken != null ? lastToken.Type : TokenType.Invalid);
|
||||
if (token == null)
|
||||
{
|
||||
// Sanity check parsed tree
|
||||
@@ -591,7 +648,20 @@ namespace OpenRA.Support
|
||||
if (currentOpeners.Count > 0)
|
||||
throw new InvalidDataException("Unclosed opening parenthesis at index {0}".F(currentOpeners.Peek().Index));
|
||||
|
||||
asFunction = new Compiler().Compile(ToPostfix(tokens).ToArray());
|
||||
return new Compiler().Build(ToPostfix(tokens).ToArray(), resultType);
|
||||
}
|
||||
|
||||
protected Func<IReadOnlyDictionary<string, int>, T> Compile<T>()
|
||||
{
|
||||
ExpressionType resultType;
|
||||
if (typeof(T) == typeof(int))
|
||||
resultType = ExpressionType.Int;
|
||||
else if (typeof(T) == typeof(bool))
|
||||
resultType = ExpressionType.Bool;
|
||||
else
|
||||
throw new InvalidCastException("Variable expressions can only be int or bool.");
|
||||
|
||||
return Expressions.Expression.Lambda<Func<IReadOnlyDictionary<string, int>, T>>(Build(resultType), SymbolsParam).Compile();
|
||||
}
|
||||
|
||||
static int ParseSymbol(string symbol, IReadOnlyDictionary<string, int> symbols)
|
||||
@@ -614,7 +684,7 @@ namespace OpenRA.Support
|
||||
while (!((temp = s.Pop()).Opens != Grouping.None))
|
||||
yield return temp;
|
||||
}
|
||||
else if (t.OperandSides == OperandSides.None)
|
||||
else if (t.OperandSides == Sides.None)
|
||||
yield return t;
|
||||
else
|
||||
{
|
||||
@@ -712,7 +782,7 @@ namespace OpenRA.Support
|
||||
{
|
||||
readonly AstStack ast = new AstStack();
|
||||
|
||||
public Func<IReadOnlyDictionary<string, int>, int> Compile(Token[] postfix)
|
||||
public Expression Build(Token[] postfix, ExpressionType resultType)
|
||||
{
|
||||
foreach (var t in postfix)
|
||||
{
|
||||
@@ -877,10 +947,34 @@ namespace OpenRA.Support
|
||||
}
|
||||
}
|
||||
|
||||
return Expressions.Expression.Lambda<Func<IReadOnlyDictionary<string, int>, int>>(
|
||||
ast.Pop(ExpressionType.Int), SymbolsParam).Compile();
|
||||
return ast.Pop(resultType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BooleanExpression : VariableExpression
|
||||
{
|
||||
readonly Func<IReadOnlyDictionary<string, int>, bool> asFunction;
|
||||
|
||||
public BooleanExpression(string expression) : base(expression)
|
||||
{
|
||||
asFunction = Compile<bool>();
|
||||
}
|
||||
|
||||
public bool Evaluate(IReadOnlyDictionary<string, int> symbols)
|
||||
{
|
||||
return asFunction(symbols);
|
||||
}
|
||||
}
|
||||
|
||||
public class IntegerExpression : VariableExpression
|
||||
{
|
||||
readonly Func<IReadOnlyDictionary<string, int>, int> asFunction;
|
||||
|
||||
public IntegerExpression(string expression) : base(expression)
|
||||
{
|
||||
asFunction = Compile<int>();
|
||||
}
|
||||
|
||||
public int Evaluate(IReadOnlyDictionary<string, int> symbols)
|
||||
{
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
@@ -113,7 +114,7 @@ namespace OpenRA.Traits
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
Enabled = self.World.LobbyInfo.IsSinglePlayer || self.World.LobbyInfo.GlobalSettings
|
||||
Enabled = self.World.LobbyInfo.NonBotPlayers.Count() == 1 || self.World.LobbyInfo.GlobalSettings
|
||||
.OptionOrDefault("cheats", info.Enabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,12 +145,11 @@ namespace OpenRA.Traits
|
||||
if (!actor.Targetables.Any(Exts.IsTraitEnabled))
|
||||
return new[] { actor.CenterPosition };
|
||||
|
||||
var targetablePositions = actor.TraitOrDefault<ITargetablePositions>();
|
||||
if (targetablePositions != null)
|
||||
var targetablePositions = actor.TraitsImplementing<ITargetablePositions>().Where(Exts.IsTraitEnabled);
|
||||
if (targetablePositions.Any())
|
||||
{
|
||||
var positions = targetablePositions.TargetablePositions(actor);
|
||||
if (positions.Any())
|
||||
return positions;
|
||||
var target = this;
|
||||
return targetablePositions.SelectMany(tp => tp.TargetablePositions(target.actor));
|
||||
}
|
||||
|
||||
return new[] { actor.CenterPosition };
|
||||
|
||||
@@ -95,6 +95,8 @@ namespace OpenRA.Traits
|
||||
public interface ITick { void Tick(Actor self); }
|
||||
public interface ITickRender { void TickRender(WorldRenderer wr, Actor self); }
|
||||
public interface IRender { IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr); }
|
||||
|
||||
public interface IAutoSelectionSizeInfo : ITraitInfoInterface { }
|
||||
public interface IAutoSelectionSize { int2 SelectionSize(Actor self); }
|
||||
|
||||
public interface IIssueOrder
|
||||
@@ -199,6 +201,11 @@ namespace OpenRA.Traits
|
||||
|
||||
public interface IRadarColorModifier { Color RadarColorOverride(Actor self, Color color); }
|
||||
|
||||
public interface ITargetableCells
|
||||
{
|
||||
IEnumerable<Pair<CPos, SubCell>> TargetableCells();
|
||||
}
|
||||
|
||||
public interface IOccupySpaceInfo : ITraitInfoInterface
|
||||
{
|
||||
IReadOnlyDictionary<CPos, SubCell> OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any);
|
||||
@@ -285,7 +292,7 @@ namespace OpenRA.Traits
|
||||
public interface IMove
|
||||
{
|
||||
Activity MoveTo(CPos cell, int nearEnough);
|
||||
Activity MoveTo(CPos cell, Actor ignoredActor);
|
||||
Activity MoveTo(CPos cell, Actor ignoreActor);
|
||||
Activity MoveWithinRange(Target target, WDist range);
|
||||
Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange);
|
||||
Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange);
|
||||
@@ -330,7 +337,12 @@ namespace OpenRA.Traits
|
||||
public interface IWorldLoaded { void WorldLoaded(World w, WorldRenderer wr); }
|
||||
public interface ICreatePlayers { void CreatePlayers(World w); }
|
||||
|
||||
public interface IBotInfo : ITraitInfoInterface { string Name { get; } }
|
||||
public interface IBotInfo : ITraitInfoInterface
|
||||
{
|
||||
string Type { get; }
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
public interface IBot
|
||||
{
|
||||
void Activate(Player p);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Linq;
|
||||
|
||||
namespace OpenRA.UtilityCommands
|
||||
{
|
||||
class ClearInvalidModRegistrationsCommand : IUtilityCommand
|
||||
{
|
||||
string IUtilityCommand.Name { get { return "--clear-invalid-mod-registrations"; } }
|
||||
bool IUtilityCommand.ValidateArguments(string[] args)
|
||||
{
|
||||
return args.Length >= 2 && new string[] { "system", "user", "both" }.Contains(args[1]);
|
||||
}
|
||||
|
||||
[Desc("(system|user|both)", "Removes invalid metadata entries for the in-game mod switcher.")]
|
||||
void IUtilityCommand.Run(Utility utility, string[] args)
|
||||
{
|
||||
ModRegistration type = 0;
|
||||
if (args[1] == "system" || args[1] == "both")
|
||||
type |= ModRegistration.System;
|
||||
|
||||
if (args[1] == "user" || args[1] == "both")
|
||||
type |= ModRegistration.User;
|
||||
|
||||
var mods = new ExternalMods();
|
||||
|
||||
ExternalMod activeMod = null;
|
||||
mods.TryGetValue(ExternalMod.MakeKey(utility.ModData.Manifest), out activeMod);
|
||||
mods.ClearInvalidRegistrations(activeMod, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
OpenRA.Game/UtilityCommands/RegisterModCommand.cs
Normal file
37
OpenRA.Game/UtilityCommands/RegisterModCommand.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Linq;
|
||||
|
||||
namespace OpenRA.UtilityCommands
|
||||
{
|
||||
class RegisterModCommand : IUtilityCommand
|
||||
{
|
||||
string IUtilityCommand.Name { get { return "--register-mod"; } }
|
||||
bool IUtilityCommand.ValidateArguments(string[] args)
|
||||
{
|
||||
return args.Length >= 3 && new string[] { "system", "user", "both" }.Contains(args[2]);
|
||||
}
|
||||
|
||||
[Desc("LAUNCHPATH (system|user|both)", "Generates a mod metadata entry for the in-game mod switcher.")]
|
||||
void IUtilityCommand.Run(Utility utility, string[] args)
|
||||
{
|
||||
ModRegistration type = 0;
|
||||
if (args[2] == "system" || args[2] == "both")
|
||||
type |= ModRegistration.System;
|
||||
|
||||
if (args[2] == "user" || args[2] == "both")
|
||||
type |= ModRegistration.User;
|
||||
|
||||
new ExternalMods().Register(utility.ModData.Manifest, args[1], type);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
OpenRA.Game/UtilityCommands/UnregisterModCommand.cs
Normal file
37
OpenRA.Game/UtilityCommands/UnregisterModCommand.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Linq;
|
||||
|
||||
namespace OpenRA.UtilityCommands
|
||||
{
|
||||
class UnregisterModCommand : IUtilityCommand
|
||||
{
|
||||
string IUtilityCommand.Name { get { return "--unregister-mod"; } }
|
||||
bool IUtilityCommand.ValidateArguments(string[] args)
|
||||
{
|
||||
return args.Length >= 2 && new string[] { "system", "user", "both" }.Contains(args[1]);
|
||||
}
|
||||
|
||||
[Desc("(system|user|both)", "Removes the mod metadata entry for the in-game mod switcher.")]
|
||||
void IUtilityCommand.Run(Utility utility, string[] args)
|
||||
{
|
||||
ModRegistration type = 0;
|
||||
if (args[1] == "system" || args[1] == "both")
|
||||
type |= ModRegistration.System;
|
||||
|
||||
if (args[1] == "user" || args[1] == "both")
|
||||
type |= ModRegistration.User;
|
||||
|
||||
new ExternalMods().Unregister(utility.ModData.Manifest, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Graphics;
|
||||
|
||||
namespace OpenRA.Widgets
|
||||
{
|
||||
public class RootWidget : ContainerWidget
|
||||
{
|
||||
public RootWidget()
|
||||
{
|
||||
IgnoreMouseOver = true;
|
||||
}
|
||||
|
||||
public override bool HandleKeyPress(KeyInput e)
|
||||
{
|
||||
if (e.Event == KeyInputEvent.Down)
|
||||
{
|
||||
var hk = Hotkey.FromKeyInput(e);
|
||||
|
||||
if (hk == Game.Settings.Keys.DevReloadChromeKey)
|
||||
{
|
||||
ChromeProvider.Initialize(Game.ModData);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hk == Game.Settings.Keys.HideUserInterfaceKey)
|
||||
{
|
||||
foreach (var child in Children)
|
||||
child.Visible ^= true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hk == Game.Settings.Keys.TakeScreenshotKey)
|
||||
{
|
||||
if (e.Event == KeyInputEvent.Down)
|
||||
Game.TakeScreenshot = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.HandleKeyPress(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace OpenRA.Widgets
|
||||
{
|
||||
public static class Ui
|
||||
{
|
||||
public static Widget Root = new RootWidget();
|
||||
public static Widget Root = new ContainerWidget();
|
||||
|
||||
public static long LastTickTime = Game.RunTime;
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace OpenRA.Widgets
|
||||
if (mi.Event == MouseInputEvent.Move)
|
||||
{
|
||||
Viewport.LastMousePos = mi.Location;
|
||||
Viewport.TicksSinceLastMove = 0;
|
||||
Viewport.LastMoveRunTime = Game.RunTime;
|
||||
}
|
||||
|
||||
if (wasMouseOver != MouseOverWidget)
|
||||
@@ -128,6 +128,29 @@ namespace OpenRA.Widgets
|
||||
/// <param name="e">Key input data</param>
|
||||
public static bool HandleKeyPress(KeyInput e)
|
||||
{
|
||||
if (e.Event == KeyInputEvent.Down)
|
||||
{
|
||||
var hk = Hotkey.FromKeyInput(e);
|
||||
|
||||
if (hk == Game.Settings.Keys.DevReloadChromeKey)
|
||||
{
|
||||
ChromeProvider.Initialize(Game.ModData);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hk == Game.Settings.Keys.HideUserInterfaceKey)
|
||||
{
|
||||
Root.Visible ^= true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hk == Game.Settings.Keys.TakeScreenshotKey)
|
||||
{
|
||||
Game.TakeScreenshot = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (KeyboardFocusWidget != null)
|
||||
return KeyboardFocusWidget.HandleKeyPressOuter(e);
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace OpenRA
|
||||
public Session LobbyInfo { get { return OrderManager.LobbyInfo; } }
|
||||
|
||||
public readonly MersenneTwister SharedRandom;
|
||||
public readonly IModelCache ModelCache;
|
||||
|
||||
public Player[] Players = new Player[0];
|
||||
|
||||
@@ -147,7 +148,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
internal World(Map map, OrderManager orderManager, WorldType type)
|
||||
internal World(ModData modData, Map map, OrderManager orderManager, WorldType type)
|
||||
{
|
||||
Type = type;
|
||||
OrderManager = orderManager;
|
||||
@@ -156,6 +157,8 @@ namespace OpenRA
|
||||
Timestep = orderManager.LobbyInfo.GlobalSettings.Timestep;
|
||||
SharedRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
|
||||
|
||||
ModelCache = modData.ModelSequenceLoader.CacheModels(map, modData, map.Rules.ModelSequences);
|
||||
|
||||
var worldActorType = type == WorldType.Editor ? "EditorWorld" : "World";
|
||||
WorldActor = CreateActor(worldActorType, new TypeDictionary());
|
||||
ActorMap = WorldActor.Trait<IActorMap>();
|
||||
@@ -437,6 +440,8 @@ namespace OpenRA
|
||||
Game.Sound.StopAudio();
|
||||
Game.Sound.StopVideo();
|
||||
|
||||
ModelCache.Dispose();
|
||||
|
||||
// Dispose newer actors first, and the world actor last
|
||||
foreach (var a in actors.Values.Reverse())
|
||||
a.Dispose();
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Media;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
class GameMonitor
|
||||
{
|
||||
static Process gameProcess;
|
||||
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var launcherPath = Assembly.GetExecutingAssembly().Location;
|
||||
var directory = Path.GetDirectoryName(launcherPath);
|
||||
var enginePath = Path.Combine(directory, "OpenRA.Game.exe");
|
||||
|
||||
Directory.SetCurrentDirectory(directory);
|
||||
|
||||
var engineArgs = args
|
||||
.Append("Engine.LaunchPath=" + launcherPath)
|
||||
.Select(arg => "\"" + arg + "\"");
|
||||
|
||||
var psi = new ProcessStartInfo(enginePath, string.Join(" ", engineArgs));
|
||||
|
||||
try
|
||||
{
|
||||
gameProcess = Process.Start(psi);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameProcess == null)
|
||||
return;
|
||||
|
||||
gameProcess.EnableRaisingEvents = true;
|
||||
gameProcess.Exited += GameProcessExited;
|
||||
|
||||
Application.Run();
|
||||
}
|
||||
|
||||
static void ShowErrorDialog()
|
||||
{
|
||||
var form = new Form
|
||||
{
|
||||
Size = new Size(315, 140),
|
||||
Text = "Fatal Error",
|
||||
MinimizeBox = false,
|
||||
MaximizeBox = false,
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||
StartPosition = FormStartPosition.CenterScreen,
|
||||
TopLevel = true,
|
||||
Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location)
|
||||
};
|
||||
|
||||
var notice = new Label
|
||||
{
|
||||
Location = new Point(10, 10),
|
||||
AutoSize = true,
|
||||
Text = "OpenRA has encountered a fatal error and must close.{0}Refer to the crash logs and FAQ for more information.".F(Environment.NewLine),
|
||||
TextAlign = ContentAlignment.TopCenter
|
||||
};
|
||||
|
||||
var viewLogs = new Button
|
||||
{
|
||||
Location = new Point(10, 80),
|
||||
Size = new Size(75, 23),
|
||||
Text = "View Logs"
|
||||
};
|
||||
|
||||
var viewFaq = new Button
|
||||
{
|
||||
Location = new Point(90, 80),
|
||||
Size = new Size(75, 23),
|
||||
Text = "View FAQ"
|
||||
};
|
||||
|
||||
var quit = new Button
|
||||
{
|
||||
Location = new Point(225, 80),
|
||||
Size = new Size(75, 23),
|
||||
Text = "Quit",
|
||||
DialogResult = DialogResult.Cancel
|
||||
};
|
||||
|
||||
form.Controls.Add(notice);
|
||||
form.Controls.Add(viewLogs);
|
||||
form.Controls.Add(viewFaq);
|
||||
form.Controls.Add(quit);
|
||||
|
||||
viewLogs.Click += ViewLogsClicked;
|
||||
viewFaq.Click += ViewFaqClicked;
|
||||
form.FormClosed += FormClosed;
|
||||
|
||||
SystemSounds.Exclamation.Play();
|
||||
form.ShowDialog();
|
||||
}
|
||||
|
||||
static void GameProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
if (gameProcess.ExitCode != (int)RunStatus.Success)
|
||||
ShowErrorDialog();
|
||||
|
||||
Exit();
|
||||
}
|
||||
|
||||
static void ViewLogsClicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(Platform.ResolvePath("^", "Logs"));
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
|
||||
static void ViewFaqClicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start("http://wiki.openra.net/FAQ");
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
|
||||
static void FormClosed(object sender, EventArgs e)
|
||||
{
|
||||
Exit();
|
||||
}
|
||||
|
||||
static void Exit()
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProjectGuid>{68295755-7902-4602-AC2C-9A8AC36D5EF7}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>OpenRA</RootNamespace>
|
||||
<AssemblyName>OpenRA</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<OutputPath>..\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>..\OpenRA.Game\OpenRA.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>..\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Drawing" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
||||
<Project>{0DFB103F-2962-400F-8C6D-E2C28CCBA633}</Project>
|
||||
<Name>OpenRA.Game</Name>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GameMonitor.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -53,11 +53,12 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
if (rearmTarget == null)
|
||||
return new Wait(20);
|
||||
|
||||
// Add a CloseEnough range of 512 to the Repair activity in order to ensure that we're at the host actor
|
||||
return ActivityUtils.SequenceActivities(
|
||||
new MoveAdjacentTo(self, Target.FromActor(rearmTarget)),
|
||||
movement.MoveTo(self.World.Map.CellContaining(rearmTarget.CenterPosition), rearmTarget),
|
||||
new Rearm(self),
|
||||
new Repair(self, rearmTarget),
|
||||
new Repair(self, rearmTarget, new WDist(512)),
|
||||
this);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
class Blowfish
|
||||
{
|
||||
@@ -12,7 +12,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
/* TODO: Convert this direct C port into readable code. */
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class that uses a lookup table to calculates CRC32
|
||||
@@ -14,7 +14,7 @@ using System;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class HvaReader
|
||||
{
|
||||
@@ -9,10 +9,9 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class IdxEntry
|
||||
{
|
||||
@@ -11,9 +11,8 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class IdxReader
|
||||
{
|
||||
@@ -13,7 +13,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public enum NormalType { TiberianSun = 2, RedAlert2 = 4 }
|
||||
public class VxlElement
|
||||
@@ -13,7 +13,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class XccGlobalDatabase : IDisposable
|
||||
{
|
||||
@@ -14,7 +14,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class XccLocalDatabase
|
||||
{
|
||||
155
OpenRA.Mods.Cnc/FileSystem/BagFile.cs
Normal file
155
OpenRA.Mods.Cnc/FileSystem/BagFile.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Text;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Mods.Cnc.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
using FS = OpenRA.FileSystem.FileSystem;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileSystem
|
||||
{
|
||||
public class AudioBagLoader : IPackageLoader
|
||||
{
|
||||
sealed class BagFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Stream s;
|
||||
readonly Dictionary<string, IdxEntry> index;
|
||||
|
||||
public BagFile(Stream s, List<IdxEntry> entries, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.s = s;
|
||||
|
||||
index = entries.ToDictionaryWithConflictLog(x => x.Filename,
|
||||
"{0} (bag format)".F(filename),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
IdxEntry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
s.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
|
||||
var waveHeaderMemoryStream = new MemoryStream();
|
||||
|
||||
var channels = (entry.Flags & 1) > 0 ? 2 : 1;
|
||||
|
||||
if ((entry.Flags & 2) > 0)
|
||||
{
|
||||
// PCM
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 36));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(16));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)1));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(2 * channels * entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)(2 * channels)));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)16));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
if ((entry.Flags & 8) > 0)
|
||||
{
|
||||
// IMA ADPCM
|
||||
var samplesPerChunk = (2 * (entry.ChunkSize - 4)) + 1;
|
||||
var bytesPerSec = (int)Math.Floor(((double)(2 * entry.ChunkSize) / samplesPerChunk) * ((double)entry.SampleRate / 2));
|
||||
var chunkSize = entry.ChunkSize > entry.Length ? entry.Length : entry.ChunkSize;
|
||||
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 52));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(20));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)17));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(bytesPerSec));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)chunkSize));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)2));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)samplesPerChunk));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fact"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4 * entry.Length));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
waveHeaderMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Construct a merged stream
|
||||
var mergedStream = new MergedStream(waveHeaderMemoryStream, s);
|
||||
mergedStream.SetLength(waveHeaderMemoryStream.Length + entry.Length);
|
||||
|
||||
return mergedStream;
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FS context)
|
||||
{
|
||||
// Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package)
|
||||
{
|
||||
if (!filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// A bag file is always accompanied with an .idx counterpart
|
||||
// For example: audio.bag requires the audio.idx file
|
||||
var indexFilename = Path.ChangeExtension(filename, ".idx");
|
||||
List<IdxEntry> entries = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Build the index and dispose the stream, it is no longer needed after this
|
||||
using (var indexStream = context.Open(indexFilename))
|
||||
entries = new IdxReader(indexStream).Entries;
|
||||
}
|
||||
catch
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new BagFile(s, entries, filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
OpenRA.Mods.Cnc/FileSystem/BigFile.cs
Normal file
130
OpenRA.Mods.Cnc/FileSystem/BigFile.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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 OpenRA.FileSystem;
|
||||
using FS = OpenRA.FileSystem.FileSystem;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileSystem
|
||||
{
|
||||
public class BigLoader : IPackageLoader
|
||||
{
|
||||
sealed class BigFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
|
||||
public BigFile(Stream s, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.s = s;
|
||||
|
||||
try
|
||||
{
|
||||
/* var signature = */ s.ReadASCII(4);
|
||||
|
||||
// Total archive size.
|
||||
s.ReadUInt32();
|
||||
|
||||
var entryCount = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
entryCount = int2.Swap(entryCount);
|
||||
|
||||
// First entry offset? This is apparently bogus for EA's .big files
|
||||
// and we don't have to try seeking there since the entries typically start next in EA's .big files.
|
||||
s.ReadUInt32();
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entry = new Entry(s);
|
||||
index.Add(entry.Path, entry);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
class Entry
|
||||
{
|
||||
readonly Stream s;
|
||||
readonly uint offset;
|
||||
readonly uint size;
|
||||
public readonly string Path;
|
||||
|
||||
public Entry(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
|
||||
offset = s.ReadUInt32();
|
||||
size = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
offset = int2.Swap(offset);
|
||||
size = int2.Swap(size);
|
||||
}
|
||||
|
||||
Path = s.ReadASCIIZ();
|
||||
}
|
||||
|
||||
public Stream GetData()
|
||||
{
|
||||
s.Position = offset;
|
||||
return new MemoryStream(s.ReadBytes((int)size));
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
return index[filename].GetData();
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FS context)
|
||||
{
|
||||
// Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package)
|
||||
{
|
||||
// Take a peek at the file signature
|
||||
var signature = s.ReadASCII(4);
|
||||
s.Position -= 4;
|
||||
|
||||
if (signature != "BIGF")
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new BigFile(s, filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
252
OpenRA.Mods.Cnc/FileSystem/MixFile.cs
Normal file
252
OpenRA.Mods.Cnc/FileSystem/MixFile.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Mods.Cnc.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
using FS = OpenRA.FileSystem.FileSystem;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileSystem
|
||||
{
|
||||
public class MixLoader : IPackageLoader
|
||||
{
|
||||
public sealed class MixFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, PackageEntry> index;
|
||||
readonly long dataStart;
|
||||
readonly Stream s;
|
||||
|
||||
public MixFile(Stream s, string filename, HashSet<string> allPossibleFilenames)
|
||||
{
|
||||
Name = filename;
|
||||
this.s = s;
|
||||
|
||||
try
|
||||
{
|
||||
// Detect format type
|
||||
var isCncMix = s.ReadUInt16() != 0;
|
||||
|
||||
// The C&C mix format doesn't contain any flags or encryption
|
||||
var isEncrypted = false;
|
||||
if (!isCncMix)
|
||||
isEncrypted = (s.ReadUInt16() & 0x2) != 0;
|
||||
|
||||
List<PackageEntry> entries;
|
||||
if (isEncrypted)
|
||||
{
|
||||
long unused;
|
||||
entries = ParseHeader(DecryptHeader(s, 4, out dataStart), 0, out unused);
|
||||
}
|
||||
else
|
||||
entries = ParseHeader(s, isCncMix ? 0 : 4, out dataStart);
|
||||
|
||||
index = ParseIndex(entries.ToDictionaryWithConflictLog(x => x.Hash,
|
||||
"{0} ({1} format, Encrypted: {2}, DataStart: {3})".F(filename, isCncMix ? "C&C" : "RA/TS/RA2", isEncrypted, dataStart),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)), allPossibleFilenames);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, PackageEntry> ParseIndex(Dictionary<uint, PackageEntry> entries, HashSet<string> allPossibleFilenames)
|
||||
{
|
||||
var classicIndex = new Dictionary<string, PackageEntry>();
|
||||
var crcIndex = new Dictionary<string, PackageEntry>();
|
||||
|
||||
// Try and find a local mix database
|
||||
var dbNameClassic = PackageEntry.HashFilename("local mix database.dat", PackageHashType.Classic);
|
||||
var dbNameCRC = PackageEntry.HashFilename("local mix database.dat", PackageHashType.CRC32);
|
||||
foreach (var kv in entries)
|
||||
{
|
||||
if (kv.Key == dbNameClassic || kv.Key == dbNameCRC)
|
||||
{
|
||||
using (var content = GetContent(kv.Value))
|
||||
{
|
||||
var db = new XccLocalDatabase(content);
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var filename in allPossibleFilenames)
|
||||
{
|
||||
var classicHash = PackageEntry.HashFilename(filename, PackageHashType.Classic);
|
||||
var crcHash = PackageEntry.HashFilename(filename, PackageHashType.CRC32);
|
||||
PackageEntry e;
|
||||
|
||||
if (entries.TryGetValue(classicHash, out e))
|
||||
classicIndex.Add(filename, e);
|
||||
|
||||
if (entries.TryGetValue(crcHash, out e))
|
||||
crcIndex.Add(filename, e);
|
||||
}
|
||||
|
||||
var bestIndex = crcIndex.Count > classicIndex.Count ? crcIndex : classicIndex;
|
||||
|
||||
var unknown = entries.Count - bestIndex.Count;
|
||||
if (unknown > 0)
|
||||
Log.Write("debug", "{0}: failed to resolve filenames for {1} unknown hashes".F(Name, unknown));
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
static List<PackageEntry> ParseHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
var numFiles = s.ReadUInt16();
|
||||
/*uint dataSize = */s.ReadUInt32();
|
||||
|
||||
var items = new List<PackageEntry>();
|
||||
for (var i = 0; i < numFiles; i++)
|
||||
items.Add(new PackageEntry(s));
|
||||
|
||||
headerEnd = offset + 6 + numFiles * PackageEntry.Size;
|
||||
return items;
|
||||
}
|
||||
|
||||
static MemoryStream DecryptHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Decrypt blowfish key
|
||||
var keyblock = s.ReadBytes(80);
|
||||
var blowfishKey = new BlowfishKeyProvider().DecryptKey(keyblock);
|
||||
var fish = new Blowfish(blowfishKey);
|
||||
|
||||
// Decrypt first block to work out the header length
|
||||
var ms = Decrypt(ReadBlocks(s, offset + 80, 1), fish);
|
||||
var numFiles = ms.ReadUInt16();
|
||||
|
||||
// Decrypt the full header - round bytes up to a full block
|
||||
var blockCount = (13 + numFiles * PackageEntry.Size) / 8;
|
||||
headerEnd = offset + 80 + blockCount * 8;
|
||||
|
||||
return Decrypt(ReadBlocks(s, offset + 80, blockCount), fish);
|
||||
}
|
||||
|
||||
static MemoryStream Decrypt(uint[] h, Blowfish fish)
|
||||
{
|
||||
var decrypted = fish.Decrypt(h);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
var writer = new BinaryWriter(ms);
|
||||
foreach (var t in decrypted)
|
||||
writer.Write(t);
|
||||
writer.Flush();
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
|
||||
static uint[] ReadBlocks(Stream s, long offset, int count)
|
||||
{
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException("offset", "Non-negative number required.");
|
||||
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count", "Non-negative number required.");
|
||||
|
||||
if (offset + (count * 2) > s.Length)
|
||||
throw new ArgumentException("Bytes to read {0} and offset {1} greater than stream length {2}.".F(count * 2, offset, s.Length));
|
||||
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// A block is a single encryption unit (represented as two 32-bit integers)
|
||||
var ret = new uint[2 * count];
|
||||
for (var i = 0; i < ret.Length; i++)
|
||||
ret[i] = s.ReadUInt32();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Stream GetContent(PackageEntry entry)
|
||||
{
|
||||
return SegmentStream.CreateWithoutOwningStream(s, dataStart + entry.Offset, (int)entry.Length);
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
return GetContent(e);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, PackageEntry> Index
|
||||
{
|
||||
get
|
||||
{
|
||||
var absoluteIndex = index.ToDictionary(e => e.Key, e => new PackageEntry(e.Value.Hash, (uint)(e.Value.Offset + dataStart), e.Value.Length));
|
||||
return new ReadOnlyDictionary<string, PackageEntry>(absoluteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FS context)
|
||||
{
|
||||
IReadOnlyPackage package;
|
||||
var childStream = GetStream(filename);
|
||||
if (childStream == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(childStream, filename, out package))
|
||||
return package;
|
||||
|
||||
childStream.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package)
|
||||
{
|
||||
if (!filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the global mix database
|
||||
Stream mixDatabase;
|
||||
var allPossibleFilenames = new HashSet<string>();
|
||||
if (context.TryOpen("global mix database.dat", out mixDatabase))
|
||||
using (var db = new XccGlobalDatabase(mixDatabase))
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
|
||||
package = new MixFile(s, filename, allPossibleFilenames);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Mods.Cnc.FileFormats;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
namespace OpenRA.Mods.Cnc.FileSystem
|
||||
{
|
||||
public enum PackageHashType { Classic, CRC32 }
|
||||
|
||||
106
OpenRA.Mods.Cnc/FileSystem/Pak.cs
Normal file
106
OpenRA.Mods.Cnc/FileSystem/Pak.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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 OpenRA.FileSystem;
|
||||
using FS = OpenRA.FileSystem.FileSystem;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileSystem
|
||||
{
|
||||
public class PakFileLoader : IPackageLoader
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public uint Offset;
|
||||
public uint Length;
|
||||
public string Filename;
|
||||
}
|
||||
|
||||
sealed class PakFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream stream;
|
||||
|
||||
public PakFile(Stream stream, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.stream = stream;
|
||||
|
||||
try
|
||||
{
|
||||
var offset = stream.ReadUInt32();
|
||||
while (offset != 0)
|
||||
{
|
||||
var file = stream.ReadASCIIZ();
|
||||
var next = stream.ReadUInt32();
|
||||
var length = (next == 0 ? (uint)stream.Length : next) - offset;
|
||||
|
||||
// Ignore duplicate files
|
||||
if (index.ContainsKey(file))
|
||||
continue;
|
||||
|
||||
index.Add(file, new Entry { Offset = offset, Length = length, Filename = file });
|
||||
offset = next;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
stream.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
var data = stream.ReadBytes((int)entry.Length);
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FS context)
|
||||
{
|
||||
// Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package)
|
||||
{
|
||||
if (!filename.EndsWith(".pak", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new PakFile(s, filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,25 +11,28 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.FileFormats;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
namespace OpenRA.Mods.Cnc.Graphics
|
||||
{
|
||||
struct Limb
|
||||
{
|
||||
public float Scale;
|
||||
public float[] Bounds;
|
||||
public byte[] Size;
|
||||
public VoxelRenderData RenderData;
|
||||
public ModelRenderData RenderData;
|
||||
}
|
||||
|
||||
public class Voxel
|
||||
public class Voxel : IModel
|
||||
{
|
||||
Limb[] limbData;
|
||||
float[] transforms;
|
||||
readonly Limb[] limbData;
|
||||
readonly float[] transforms;
|
||||
readonly uint frames;
|
||||
readonly uint limbs;
|
||||
|
||||
public readonly uint Frames;
|
||||
public readonly uint Limbs;
|
||||
uint IModel.Frames { get { return frames; } }
|
||||
uint IModel.Sections { get { return limbs; } }
|
||||
|
||||
public Voxel(VoxelLoader loader, VxlReader vxl, HvaReader hva)
|
||||
{
|
||||
@@ -37,8 +40,8 @@ namespace OpenRA.Graphics
|
||||
throw new InvalidOperationException("Voxel and hva limb counts don't match");
|
||||
|
||||
transforms = hva.Transforms;
|
||||
Frames = hva.FrameCount;
|
||||
Limbs = hva.LimbCount;
|
||||
frames = hva.FrameCount;
|
||||
limbs = hva.LimbCount;
|
||||
|
||||
limbData = new Limb[vxl.LimbCount];
|
||||
for (var i = 0; i < vxl.LimbCount; i++)
|
||||
@@ -55,14 +58,14 @@ namespace OpenRA.Graphics
|
||||
|
||||
public float[] TransformationMatrix(uint limb, uint frame)
|
||||
{
|
||||
if (frame >= Frames)
|
||||
throw new ArgumentOutOfRangeException("frame", "Only {0} frames exist.".F(Frames));
|
||||
if (limb >= Limbs)
|
||||
throw new ArgumentOutOfRangeException("limb", "Only {1} limbs exist.".F(Limbs));
|
||||
if (frame >= frames)
|
||||
throw new ArgumentOutOfRangeException("frame", "Only {0} frames exist.".F(frames));
|
||||
if (limb >= limbs)
|
||||
throw new ArgumentOutOfRangeException("limb", "Only {1} limbs exist.".F(limbs));
|
||||
|
||||
var l = limbData[limb];
|
||||
var t = new float[16];
|
||||
Array.Copy(transforms, 16 * (Limbs * frame + limb), t, 0, 16);
|
||||
Array.Copy(transforms, 16 * (limbs * frame + limb), t, 0, 16);
|
||||
|
||||
// Fix limb position
|
||||
t[12] *= l.Scale * (l.Bounds[3] - l.Bounds[0]) / l.Size[0];
|
||||
@@ -76,7 +79,7 @@ namespace OpenRA.Graphics
|
||||
return t;
|
||||
}
|
||||
|
||||
public VoxelRenderData RenderData(uint limb)
|
||||
public ModelRenderData RenderData(uint limb)
|
||||
{
|
||||
return limbData[limb].RenderData;
|
||||
}
|
||||
@@ -100,7 +103,7 @@ namespace OpenRA.Graphics
|
||||
var ret = new float[] { float.MaxValue, float.MaxValue, float.MaxValue,
|
||||
float.MinValue, float.MinValue, float.MinValue };
|
||||
|
||||
for (uint j = 0; j < Limbs; j++)
|
||||
for (uint j = 0; j < limbs; j++)
|
||||
{
|
||||
var l = limbData[j];
|
||||
var b = new float[]
|
||||
@@ -13,26 +13,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
namespace OpenRA.Mods.Cnc.Graphics
|
||||
{
|
||||
public struct VoxelRenderData
|
||||
{
|
||||
public readonly int Start;
|
||||
public readonly int Count;
|
||||
public readonly Sheet Sheet;
|
||||
|
||||
public VoxelRenderData(int start, int count, Sheet sheet)
|
||||
{
|
||||
Start = start;
|
||||
Count = count;
|
||||
Sheet = sheet;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class VoxelLoader : IDisposable
|
||||
{
|
||||
static readonly float[] ChannelSelect = { 0.75f, 0.25f, -0.25f, -0.75f };
|
||||
@@ -182,7 +169,7 @@ namespace OpenRA.Graphics
|
||||
(u, v) => new float3(u, v, z));
|
||||
}
|
||||
|
||||
public VoxelRenderData GenerateRenderData(VxlLimb l)
|
||||
public ModelRenderData GenerateRenderData(VxlLimb l)
|
||||
{
|
||||
Vertex[] v;
|
||||
try
|
||||
@@ -203,14 +190,14 @@ namespace OpenRA.Graphics
|
||||
var start = totalVertexCount;
|
||||
var count = v.Length;
|
||||
totalVertexCount += count;
|
||||
return new VoxelRenderData(start, count, sheetBuilder.Current);
|
||||
return new ModelRenderData(start, count, sheetBuilder.Current);
|
||||
}
|
||||
|
||||
public void RefreshBuffer()
|
||||
{
|
||||
if (vertexBuffer != null)
|
||||
vertexBuffer.Dispose();
|
||||
vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(totalVertexCount);
|
||||
vertexBuffer = Game.Renderer.CreateVertexBuffer(totalVertexCount);
|
||||
vertexBuffer.SetData(vertices.SelectMany(v => v).ToArray(), totalVertexCount);
|
||||
cachedVertexCount = totalVertexCount;
|
||||
}
|
||||
119
OpenRA.Mods.Cnc/Graphics/VoxelModelSequenceLoader.cs
Normal file
119
OpenRA.Mods.Cnc/Graphics/VoxelModelSequenceLoader.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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 OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Graphics
|
||||
{
|
||||
public class VoxelModelSequenceLoader : IModelSequenceLoader
|
||||
{
|
||||
public Action<string> OnMissingModelError { get; set; }
|
||||
|
||||
public VoxelModelSequenceLoader(ModData modData) { }
|
||||
|
||||
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
{
|
||||
var cache = new VoxelModelCache(fileSystem);
|
||||
foreach (var kv in modelSequences)
|
||||
{
|
||||
modData.LoadScreen.Display();
|
||||
try
|
||||
{
|
||||
cache.CacheModel(kv.Key, kv.Value.Value);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
|
||||
// Eat the FileNotFound exceptions from missing sprites
|
||||
OnMissingModelError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
cache.LoadComplete();
|
||||
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
public class VoxelModelCache : IModelCache
|
||||
{
|
||||
readonly VoxelLoader loader;
|
||||
readonly Dictionary<string, Dictionary<string, IModel>> models = new Dictionary<string, Dictionary<string, IModel>>();
|
||||
|
||||
public VoxelModelCache(IReadOnlyFileSystem fileSystem)
|
||||
{
|
||||
loader = new VoxelLoader(fileSystem);
|
||||
}
|
||||
|
||||
public void CacheModel(string model, MiniYaml definition)
|
||||
{
|
||||
models.Add(model, definition.ToDictionary(my => LoadVoxel(model, my)));
|
||||
}
|
||||
|
||||
public void LoadComplete()
|
||||
{
|
||||
loader.RefreshBuffer();
|
||||
loader.Finish();
|
||||
}
|
||||
|
||||
IModel LoadVoxel(string unit, MiniYaml info)
|
||||
{
|
||||
var vxl = unit;
|
||||
var hva = unit;
|
||||
if (info.Value != null)
|
||||
{
|
||||
var fields = info.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (fields.Length >= 1)
|
||||
vxl = hva = fields[0].Trim();
|
||||
|
||||
if (fields.Length >= 2)
|
||||
hva = fields[1].Trim();
|
||||
}
|
||||
|
||||
return loader.Load(vxl, hva);
|
||||
}
|
||||
|
||||
public IModel GetModelSequence(string model, string sequence)
|
||||
{
|
||||
try { return models[model][sequence]; }
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
if (models.ContainsKey(model))
|
||||
throw new InvalidOperationException(
|
||||
"Model `{0}` does not have a sequence `{1}`".F(model, sequence));
|
||||
else
|
||||
throw new InvalidOperationException(
|
||||
"Model `{0}` does not have any sequences defined.".F(model));
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasModelSequence(string model, string sequence)
|
||||
{
|
||||
if (!models.ContainsKey(model))
|
||||
throw new InvalidOperationException(
|
||||
"Model `{0}` does not have any sequences defined.".F(model));
|
||||
|
||||
return models[model].ContainsKey(sequence);
|
||||
}
|
||||
|
||||
public IVertexBuffer<Vertex> VertexBuffer { get { return loader.VertexBuffer; } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
loader.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,11 +72,12 @@
|
||||
<Compile Include="Traits\Render\WithReloadingSpriteTurret.cs" />
|
||||
<Compile Include="Traits\Render\WithRoof.cs" />
|
||||
<Compile Include="Traits\SupportPowers\IonCannonPower.cs" />
|
||||
<Compile Include="ImportTiberianDawnLegacyMapCommand.cs" />
|
||||
<Compile Include="UtilityCommands\ImportTiberianDawnLegacyMapCommand.cs" />
|
||||
<Compile Include="Projectiles\IonCannon.cs" />
|
||||
<Compile Include="Activities\VoxelHarvesterDockSequence.cs" />
|
||||
<Compile Include="SpriteLoaders\TmpTSLoader.cs" />
|
||||
<Compile Include="Traits\Buildings\TiberianSunRefinery.cs" />
|
||||
<Compile Include="Traits\Render\WithBuildingBib.cs" />
|
||||
<Compile Include="Traits\Render\WithDockingOverlay.cs" />
|
||||
<Compile Include="Traits\Render\WithPermanentInjury.cs" />
|
||||
<Compile Include="Traits\Render\WithVoxelWalkerBody.cs" />
|
||||
@@ -109,6 +110,7 @@
|
||||
<Compile Include="Traits\Chronoshiftable.cs" />
|
||||
<Compile Include="Traits\Cloneable.cs" />
|
||||
<Compile Include="Traits\Disguise.cs" />
|
||||
<Compile Include="Traits\EnergyWall.cs" />
|
||||
<Compile Include="Traits\FrozenUnderFogUpdatedByGps.cs" />
|
||||
<Compile Include="Traits\HarvesterHuskModifier.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForCash.cs" />
|
||||
@@ -130,9 +132,28 @@
|
||||
<Compile Include="Traits\GpsWatcher.cs" />
|
||||
<Compile Include="Scripting\Properties\ChronosphereProperties.cs" />
|
||||
<Compile Include="Traits\Render\WithDisguisingInfantryBody.cs" />
|
||||
<Compile Include="ImportRedAlertLegacyMapCommand.cs" />
|
||||
<Compile Include="UtilityCommands\ImportRedAlertLegacyMapCommand.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForDecoration.cs" />
|
||||
<Compile Include="TraitsInterfaces.cs" />
|
||||
<Compile Include="FileSystem\BagFile.cs" />
|
||||
<Compile Include="FileSystem\BigFile.cs" />
|
||||
<Compile Include="FileSystem\MixFile.cs" />
|
||||
<Compile Include="FileSystem\PackageEntry.cs" />
|
||||
<Compile Include="FileSystem\Pak.cs" />
|
||||
<Compile Include="UtilityCommands\ListMixContentsCommand.cs" />
|
||||
<Compile Include="FileFormats\Blowfish.cs" />
|
||||
<Compile Include="FileFormats\BlowfishKeyProvider.cs" />
|
||||
<Compile Include="FileFormats\IdxEntry.cs" />
|
||||
<Compile Include="FileFormats\IdxReader.cs" />
|
||||
<Compile Include="FileFormats\XccGlobalDatabase.cs" />
|
||||
<Compile Include="FileFormats\XccLocalDatabase.cs" />
|
||||
<Compile Include="FileFormats\CRC32.cs" />
|
||||
<Compile Include="Graphics\VoxelModelSequenceLoader.cs" />
|
||||
<Compile Include="Graphics\VoxelLoader.cs" />
|
||||
<Compile Include="Graphics\Voxel.cs" />
|
||||
<Compile Include="FileFormats\HvaReader.cs" />
|
||||
<Compile Include="FileFormats\VxlReader.cs" />
|
||||
<Compile Include="Traits\World\VoxelNormalsPalette.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
||||
@@ -170,17 +191,17 @@
|
||||
SourceFiles="$(TargetPath)"
|
||||
DestinationFolder="$(SolutionDir)mods/common/"/>
|
||||
<!--
|
||||
We need to copy OpenRA.Mods.Cnc.pdb (not `.dll.pdb`). This is only necessary on Windows.
|
||||
Mono outputs a `.dll.mdb` so we can just append `.mdb` directly.
|
||||
csc generates .pdb symbol files (not `.dll.pdb`).
|
||||
mcs generates .dll.mdb symbol files.
|
||||
-->
|
||||
<Copy
|
||||
SourceFiles="$(TargetDir)$(TargetName).pdb"
|
||||
DestinationFolder="$(SolutionDir)mods/common/"
|
||||
Condition="'$(OS)' == 'Windows_NT'"/>
|
||||
Condition="Exists('$(TargetDir)$(TargetName).pdb')"/>
|
||||
<Copy
|
||||
SourceFiles="$(TargetPath).mdb"
|
||||
DestinationFolder="$(SolutionDir)mods/common/"
|
||||
Condition="'$(OS)' == 'Unix'"/>
|
||||
Condition="Exists('$(TargetPath).mdb')"/>
|
||||
<!-- Uncomment these lines when debugging or adding to this target
|
||||
<Message Text="DEBUG OS: $(OS)"/>
|
||||
<Message Text="DEBUG SolutionDir: $(SolutionDir)"/>
|
||||
|
||||
@@ -32,46 +32,48 @@ namespace OpenRA.Mods.Cnc.Projectiles
|
||||
|
||||
public readonly int Duration = 2;
|
||||
|
||||
public readonly int DamageDuration = 1;
|
||||
|
||||
public readonly bool TrackTarget = true;
|
||||
|
||||
public IProjectile Create(ProjectileArgs args) { return new TeslaZap(this, args); }
|
||||
}
|
||||
|
||||
public class TeslaZap : IProjectile
|
||||
public class TeslaZap : IProjectile, ISync
|
||||
{
|
||||
readonly ProjectileArgs args;
|
||||
readonly TeslaZapInfo info;
|
||||
TeslaZapRenderable zap;
|
||||
int timeUntilRemove; // # of frames
|
||||
bool doneDamage = false;
|
||||
bool initialized = false;
|
||||
int ticksUntilRemove;
|
||||
int damageDuration;
|
||||
[Sync] WPos target;
|
||||
|
||||
public TeslaZap(TeslaZapInfo info, ProjectileArgs args)
|
||||
{
|
||||
this.args = args;
|
||||
this.info = info;
|
||||
this.timeUntilRemove = info.Duration;
|
||||
ticksUntilRemove = info.Duration;
|
||||
damageDuration = info.DamageDuration > info.Duration ? info.Duration : info.DamageDuration;
|
||||
target = args.PassiveTarget;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (timeUntilRemove-- <= 0)
|
||||
if (ticksUntilRemove-- <= 0)
|
||||
world.AddFrameEndTask(w => w.Remove(this));
|
||||
|
||||
if (!doneDamage)
|
||||
{
|
||||
var pos = args.GuidedTarget.IsValidFor(args.SourceActor) ? args.GuidedTarget.CenterPosition : args.PassiveTarget;
|
||||
args.Weapon.Impact(Target.FromPos(pos), args.SourceActor, args.DamageModifiers);
|
||||
doneDamage = true;
|
||||
}
|
||||
// Zap tracks target
|
||||
if (info.TrackTarget && args.GuidedTarget.IsValidFor(args.SourceActor))
|
||||
target = args.Weapon.TargetActorCenter ? args.GuidedTarget.CenterPosition : args.GuidedTarget.Positions.PositionClosestTo(args.Source);
|
||||
|
||||
if (damageDuration-- > 0)
|
||||
args.Weapon.Impact(Target.FromPos(target), args.SourceActor, args.DamageModifiers);
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
var pos = args.GuidedTarget.IsValidFor(args.SourceActor) ? args.GuidedTarget.CenterPosition : args.PassiveTarget;
|
||||
zap = new TeslaZapRenderable(args.Source, 0, pos - args.Source,
|
||||
info.Image, info.BrightSequence, info.BrightZaps, info.DimSequence, info.DimZaps, info.Palette);
|
||||
}
|
||||
zap = new TeslaZapRenderable(args.Source, 0, target - args.Source,
|
||||
info.Image, info.BrightSequence, info.BrightZaps, info.DimSequence, info.DimZaps, info.Palette);
|
||||
|
||||
yield return zap;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using OpenRA.Traits;
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Implements the charge-then-burst attack logic specific to the RA tesla coil.")]
|
||||
class AttackTeslaInfo : AttackOmniInfo
|
||||
class AttackTeslaInfo : AttackBaseInfo
|
||||
{
|
||||
[Desc("How many charges this actor has to attack with, once charged.")]
|
||||
public readonly int MaxCharges = 1;
|
||||
@@ -37,7 +37,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public override object Create(ActorInitializer init) { return new AttackTesla(init.Self, this); }
|
||||
}
|
||||
|
||||
class AttackTesla : AttackOmni, ITick, INotifyAttack
|
||||
class AttackTesla : AttackBase, ITick, INotifyAttack
|
||||
{
|
||||
readonly AttackTeslaInfo info;
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (state == PopupState.Transitioning || !building.Value.BuildComplete)
|
||||
if (state == PopupState.Transitioning || !building.BuildComplete)
|
||||
return false;
|
||||
|
||||
if (!base.CanAttack(self, target))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user