Compare commits
443 Commits
playtest-2
...
playtest-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
046d6be402 | ||
|
|
cb6b4c3b93 | ||
|
|
04ae39caa5 | ||
|
|
63a1377aca | ||
|
|
450fbded3f | ||
|
|
e805dff16f | ||
|
|
8f087878be | ||
|
|
716b470911 | ||
|
|
05030e6b98 | ||
|
|
e6eac5a6c0 | ||
|
|
ec7bbd6c18 | ||
|
|
528198977a | ||
|
|
fd660c7fa0 | ||
|
|
fe6c6a3017 | ||
|
|
f4d37857aa | ||
|
|
3e2f7b41d8 | ||
|
|
d7a580584d | ||
|
|
f7f8740745 | ||
|
|
7eb579cb6c | ||
|
|
8307014de9 | ||
|
|
daf94e61e0 | ||
|
|
07689efc6e | ||
|
|
14a952169b | ||
|
|
646f7c1362 | ||
|
|
faa745e566 | ||
|
|
3485a5e855 | ||
|
|
79779d69ba | ||
|
|
a6e5a0b53f | ||
|
|
6cd69f5c05 | ||
|
|
251c316120 | ||
|
|
8856a1444c | ||
|
|
791cdeba4d | ||
|
|
39e97ee767 | ||
|
|
7e7d48444b | ||
|
|
bd7e8da81d | ||
|
|
1fce900801 | ||
|
|
859af00f1c | ||
|
|
6e4cf37e44 | ||
|
|
1cd3adef5e | ||
|
|
557492cc72 | ||
|
|
c0e0efd0ef | ||
|
|
185e9b3f08 | ||
|
|
c886253738 | ||
|
|
f52a1c1521 | ||
|
|
fc6a38182d | ||
|
|
5dca3742f1 | ||
|
|
9f1d9e153a | ||
|
|
836a0ffe4b | ||
|
|
c7ccdfde2d | ||
|
|
d18289d5bc | ||
|
|
fcf987a4dd | ||
|
|
52069c6788 | ||
|
|
c20df5874d | ||
|
|
e6bdacce9c | ||
|
|
2b84dbfccf | ||
|
|
efc494b859 | ||
|
|
6a50c760ae | ||
|
|
c18b5e424e | ||
|
|
8ddc840370 | ||
|
|
dab3d99890 | ||
|
|
b93e9a5945 | ||
|
|
c9e46ee0f6 | ||
|
|
c94e80d682 | ||
|
|
2e2e95ef57 | ||
|
|
0029cb8aec | ||
|
|
0424b56af1 | ||
|
|
80c3cf479d | ||
|
|
6d24eb14d7 | ||
|
|
76efea72d9 | ||
|
|
37bc3766a5 | ||
|
|
9adfb56f65 | ||
|
|
b95cd0c91f | ||
|
|
7bce2017c3 | ||
|
|
23d9d611c1 | ||
|
|
13fbcc56ee | ||
|
|
e9b2bcc579 | ||
|
|
65088ea2d5 | ||
|
|
897cb13626 | ||
|
|
574ab7281b | ||
|
|
a13ce8cd40 | ||
|
|
ed8f6d9691 | ||
|
|
29c93182f1 | ||
|
|
c5a8af9885 | ||
|
|
c40fd80ae1 | ||
|
|
90d7f230ed | ||
|
|
88fd5ba094 | ||
|
|
3095c6ea5f | ||
|
|
fc33c56d61 | ||
|
|
5084b30f3f | ||
|
|
5ee9b5cab4 | ||
|
|
1a3a4bd7ba | ||
|
|
ac0094176a | ||
|
|
5734131310 | ||
|
|
e99d0a0b43 | ||
|
|
f376eb9b2a | ||
|
|
98039abf1b | ||
|
|
9a6b5e21dc | ||
|
|
979ac4f91f | ||
|
|
425aa46476 | ||
|
|
5a0bd24a4e | ||
|
|
d60b33b159 | ||
|
|
74ddb432aa | ||
|
|
a0e224ec71 | ||
|
|
b7f1711497 | ||
|
|
66383a57d1 | ||
|
|
d7bafdf0c8 | ||
|
|
698efb4995 | ||
|
|
321ed4ea3d | ||
|
|
6c1d4efb07 | ||
|
|
708e91145c | ||
|
|
6b261534d8 | ||
|
|
627a892097 | ||
|
|
c862ba6e03 | ||
|
|
7e39b2546b | ||
|
|
659600db23 | ||
|
|
875ac468d3 | ||
|
|
b01d55f1f6 | ||
|
|
734f12bbc6 | ||
|
|
6f3cd5dff2 | ||
|
|
787fde31ed | ||
|
|
c273a02b94 | ||
|
|
6735fd593b | ||
|
|
154cdc8709 | ||
|
|
cec62bd356 | ||
|
|
b34a31b5be | ||
|
|
5d733b8e45 | ||
|
|
2deef04095 | ||
|
|
e03734f2b4 | ||
|
|
ce01eca4d7 | ||
|
|
ae8c556187 | ||
|
|
11755798cc | ||
|
|
e229194b62 | ||
|
|
82059dca6d | ||
|
|
1eb04a70a5 | ||
|
|
886099a948 | ||
|
|
71616201ff | ||
|
|
2eeabfe668 | ||
|
|
0c4df26ed0 | ||
|
|
cdeea80037 | ||
|
|
9fb98f04d4 | ||
|
|
85da51ca09 | ||
|
|
d98a766362 | ||
|
|
659e56f0fa | ||
|
|
924aba49ea | ||
|
|
0e21107694 | ||
|
|
f918d1d0b2 | ||
|
|
52de4e1796 | ||
|
|
52335a37bf | ||
|
|
4c22193446 | ||
|
|
c5337cdcf3 | ||
|
|
2215f74959 | ||
|
|
7a71f87d9f | ||
|
|
18311be3ae | ||
|
|
7d09e78655 | ||
|
|
7beef85a64 | ||
|
|
e5bcb88b0e | ||
|
|
9b576d3fdd | ||
|
|
4152f61999 | ||
|
|
5f0ab1f62d | ||
|
|
37770a4e47 | ||
|
|
013ad0617e | ||
|
|
22e6966c8e | ||
|
|
4c8c010506 | ||
|
|
0e1c12131a | ||
|
|
cd268c11ee | ||
|
|
da8202a15e | ||
|
|
ef95faa9b9 | ||
|
|
b2201a0463 | ||
|
|
ebe0b0323e | ||
|
|
2b8319090e | ||
|
|
bad3bd5fbb | ||
|
|
06b797fd14 | ||
|
|
5d37438561 | ||
|
|
1593c94d90 | ||
|
|
c2e450986f | ||
|
|
6b44d232e7 | ||
|
|
01a625691a | ||
|
|
5334589922 | ||
|
|
17a3c0773b | ||
|
|
06375fe1a3 | ||
|
|
e3bde3ccfe | ||
|
|
6a5f1b0f63 | ||
|
|
10e5558812 | ||
|
|
2301c11309 | ||
|
|
db0829e6a0 | ||
|
|
c5f491dfc4 | ||
|
|
3513e6538a | ||
|
|
6ca4f0797a | ||
|
|
18a64cb0e5 | ||
|
|
ec9345b051 | ||
|
|
0a8e47c357 | ||
|
|
59d10cfc5d | ||
|
|
ae809ce39f | ||
|
|
5bc47f4834 | ||
|
|
a9b6a94ade | ||
|
|
1258a6186c | ||
|
|
b91c8f0b6a | ||
|
|
1b4521fd51 | ||
|
|
f871998a35 | ||
|
|
0554ef35b7 | ||
|
|
1ea322ff9b | ||
|
|
5ea2598ea9 | ||
|
|
6a1b37b5b7 | ||
|
|
801f293948 | ||
|
|
c9466f995b | ||
|
|
d898899de7 | ||
|
|
54439f8af8 | ||
|
|
eb159afb64 | ||
|
|
e7fcb758e3 | ||
|
|
22a8af34ea | ||
|
|
5184cee3ca | ||
|
|
fd3f48b359 | ||
|
|
d040153017 | ||
|
|
77f66fa8b8 | ||
|
|
20058b137e | ||
|
|
11d8b4e747 | ||
|
|
82128be9e2 | ||
|
|
51fcb8d679 | ||
|
|
0acfb77be9 | ||
|
|
e4cd7220cc | ||
|
|
cdb74fd547 | ||
|
|
aecc3624ba | ||
|
|
fdfd2eacea | ||
|
|
9383e0572d | ||
|
|
18f1683968 | ||
|
|
980c44bf62 | ||
|
|
1043f58cc4 | ||
|
|
7fb643f962 | ||
|
|
5cfde95358 | ||
|
|
235042ea65 | ||
|
|
82faf7e929 | ||
|
|
a00696ec3b | ||
|
|
6cef4290df | ||
|
|
af6791d942 | ||
|
|
992db08f71 | ||
|
|
656a529249 | ||
|
|
f6264eeba4 | ||
|
|
1b34c7d6b9 | ||
|
|
9566385aac | ||
|
|
064938378f | ||
|
|
fb3e776cb9 | ||
|
|
eff46cf40d | ||
|
|
9ed7d05ca7 | ||
|
|
1456cd6254 | ||
|
|
8c9fb382e3 | ||
|
|
06d11c4fa9 | ||
|
|
50dff05675 | ||
|
|
5692b95ad2 | ||
|
|
130300074e | ||
|
|
0cccfa4f8c | ||
|
|
ee058e72a9 | ||
|
|
10acb115da | ||
|
|
61c5b99dc5 | ||
|
|
edf604e080 | ||
|
|
6d8df80664 | ||
|
|
dd23e9598a | ||
|
|
6edde6c4ac | ||
|
|
93b606da2c | ||
|
|
a3729a11c7 | ||
|
|
646b94e7cb | ||
|
|
bd72179cb2 | ||
|
|
4552bbf19f | ||
|
|
8123a383b6 | ||
|
|
81991ffae0 | ||
|
|
02a8b05aff | ||
|
|
5f3563d83e | ||
|
|
733f29d184 | ||
|
|
92d72cc725 | ||
|
|
cc9a45daff | ||
|
|
ec36279ecb | ||
|
|
403f3745d9 | ||
|
|
bf3d337913 | ||
|
|
c149898592 | ||
|
|
53aa698491 | ||
|
|
e7aa6ce998 | ||
|
|
07f3c0171d | ||
|
|
1d5f67cb6a | ||
|
|
13f8d944d2 | ||
|
|
e029347355 | ||
|
|
0d6f02abcc | ||
|
|
cae9bef807 | ||
|
|
1a307b45e4 | ||
|
|
e885e428ae | ||
|
|
f22216aeb5 | ||
|
|
7a4d06efb7 | ||
|
|
d4b21ce206 | ||
|
|
592c76eb6e | ||
|
|
5ef3a49485 | ||
|
|
f31912aa42 | ||
|
|
84ab4ec854 | ||
|
|
beb73befa3 | ||
|
|
a0fa99037b | ||
|
|
1c6a98ba93 | ||
|
|
2958c80e51 | ||
|
|
33376b52bd | ||
|
|
5af277d20e | ||
|
|
a863f556a6 | ||
|
|
629fe95ebd | ||
|
|
547e64724f | ||
|
|
afb67f8595 | ||
|
|
413b476c01 | ||
|
|
113968aaa7 | ||
|
|
770477f7a5 | ||
|
|
90dc755ad4 | ||
|
|
c74fa90305 | ||
|
|
93c89a6ef5 | ||
|
|
9d074c6721 | ||
|
|
835dfd0780 | ||
|
|
bd360dab54 | ||
|
|
81563ee5d6 | ||
|
|
0dd8d7f7b6 | ||
|
|
a11e403084 | ||
|
|
d048d083c4 | ||
|
|
437de8e884 | ||
|
|
25dd8d9fa7 | ||
|
|
debfdfbad2 | ||
|
|
923df5eafe | ||
|
|
38fcfe9a83 | ||
|
|
f85fce2ed1 | ||
|
|
e1669ecd70 | ||
|
|
3cc3a7dd65 | ||
|
|
2abb6421fe | ||
|
|
579bafd439 | ||
|
|
0719b89cdf | ||
|
|
e3391d1535 | ||
|
|
04fd2e6d0f | ||
|
|
fbe06df9e6 | ||
|
|
4e7f04da5f | ||
|
|
4e3da1c081 | ||
|
|
d9fab238d5 | ||
|
|
de3d4da000 | ||
|
|
9067c2c842 | ||
|
|
69ae57b246 | ||
|
|
6d3cf83052 | ||
|
|
e6745f80ce | ||
|
|
f7aca32e0e | ||
|
|
ac430bd3bc | ||
|
|
be3b18057a | ||
|
|
9772f9b770 | ||
|
|
4d85605cca | ||
|
|
96ecb7c232 | ||
|
|
112034a41d | ||
|
|
9dc3f4bf2d | ||
|
|
974e9b3325 | ||
|
|
47f078ec3a | ||
|
|
8817fd2cc5 | ||
|
|
50b87b580a | ||
|
|
170e14546a | ||
|
|
4b4c1b71df | ||
|
|
e122797a68 | ||
|
|
4d8dd2db7d | ||
|
|
3e1c1096c2 | ||
|
|
c6e6977bf6 | ||
|
|
85056e1c6c | ||
|
|
57142fbb8d | ||
|
|
e382dc9b71 | ||
|
|
e1290dca47 | ||
|
|
ca5f2c3785 | ||
|
|
9b7aaebcbc | ||
|
|
462478afdf | ||
|
|
d7e6125dd7 | ||
|
|
79cbb7f729 | ||
|
|
fe716e76a7 | ||
|
|
fb17654ea0 | ||
|
|
d878c96343 | ||
|
|
d103a187f6 | ||
|
|
7719ad1f2d | ||
|
|
cfcccb590d | ||
|
|
8b4814e399 | ||
|
|
3f1805c1ea | ||
|
|
56067e4d4f | ||
|
|
7e3f90ee1d | ||
|
|
c71bdf32e1 | ||
|
|
6e5d58379f | ||
|
|
a2a3def685 | ||
|
|
01e6c14025 | ||
|
|
2d1bc7bac8 | ||
|
|
6262aa846f | ||
|
|
8f822f6cad | ||
|
|
f14441d2f4 | ||
|
|
2762c245ee | ||
|
|
0c1b6f21b9 | ||
|
|
7f40f59d85 | ||
|
|
6a6776754b | ||
|
|
4a1ebb69c3 | ||
|
|
3ae61c5f8c | ||
|
|
881fcf1191 | ||
|
|
ce021a4f62 | ||
|
|
2207c391d6 | ||
|
|
e97638ce86 | ||
|
|
f5d59fab22 | ||
|
|
4ebe547a05 | ||
|
|
28d4df355d | ||
|
|
891158ca44 | ||
|
|
c13c989fe8 | ||
|
|
7d543abb92 | ||
|
|
6dd62d7806 | ||
|
|
d70a5aff6a | ||
|
|
ccbfacec62 | ||
|
|
cca784752b | ||
|
|
c91f0dfd2f | ||
|
|
b7ea36b858 | ||
|
|
484b726373 | ||
|
|
44336b1ea5 | ||
|
|
63c71bf3f5 | ||
|
|
3500f4b3e3 | ||
|
|
942946f1b1 | ||
|
|
e8db3c2e77 | ||
|
|
c14dbb29c9 | ||
|
|
f05760b986 | ||
|
|
0576937fd6 | ||
|
|
2c250ed700 | ||
|
|
95ec993134 | ||
|
|
f4a4afbb69 | ||
|
|
05d187d327 | ||
|
|
904c6b76b5 | ||
|
|
a4c69f0a74 | ||
|
|
b301bc1d79 | ||
|
|
c9255a2873 | ||
|
|
ca4e99993d | ||
|
|
37fe6009ee | ||
|
|
37069652c0 | ||
|
|
0c12dcb3c3 | ||
|
|
a606ea4a80 | ||
|
|
792d61ad8c | ||
|
|
84d595bcde | ||
|
|
426467a38f | ||
|
|
6aae5d2ad6 | ||
|
|
e38aea276c | ||
|
|
99d4d07cd5 | ||
|
|
c75f758f8d | ||
|
|
21775df1b8 | ||
|
|
0a49153dbf | ||
|
|
e10a5e591e | ||
|
|
022aeec41a | ||
|
|
a396d1e170 | ||
|
|
f7668f3b9d | ||
|
|
37f5b471fc | ||
|
|
956d68a84b | ||
|
|
7fb85e7abb | ||
|
|
2d685ab07d | ||
|
|
52797e8b79 | ||
|
|
3c4a68bfbc |
@@ -6,17 +6,18 @@ language: c
|
||||
|
||||
# Make sure build dependencies are installed.
|
||||
install:
|
||||
- sudo apt-get install mono-gmcs cli-common-dev libgl1-mesa-dev libsdl1.2-dev libopenal-dev
|
||||
- sudo apt-get update && sudo apt-get install mono-gmcs cli-common-dev libgl1-mesa-dev libsdl1.2-dev libopenal-dev
|
||||
|
||||
# Run the build script which will automatically call RALint.
|
||||
# Run the build script which will automatically call RALint and ensure that the IDE project files are still valid.
|
||||
script:
|
||||
- make all
|
||||
- xbuild
|
||||
|
||||
# Only watch the development branch.
|
||||
branches:
|
||||
only:
|
||||
- bleed
|
||||
|
||||
# Notify developers when needed.
|
||||
# Notify developers when build passed/failed.
|
||||
notifications:
|
||||
irc: "irc.freenode.net#openra"
|
||||
118
AUTHORS
118
AUTHORS
@@ -1,55 +1,73 @@
|
||||
AUTHORS
|
||||
OpenRA wouldn't be where it is today without the
|
||||
hard work of many contributors.
|
||||
|
||||
The OpenRA developers are:
|
||||
* Alli Witheford (alzeih)
|
||||
* Caleb Anderson (RobotCaleb)
|
||||
* Chris Forbes (chrisf)
|
||||
* Curtis Shmyr (hamb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Matthew Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Chris Forbes (chrisf)
|
||||
* Curtis Shmyr (hamb)
|
||||
* Paul Chote (pchote)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* ScottNZ
|
||||
|
||||
Previous developers included:
|
||||
* Alli Witheford (alzeih)
|
||||
* Caleb Anderson (RobotCaleb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Megan Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Robert Pepperell (ytinasni)
|
||||
|
||||
Also thanks to:
|
||||
* Akseli Virtanen (RAGEQUIT)
|
||||
* Andrew Riedi
|
||||
* Barnaby Smith (mvi)
|
||||
* Bellator
|
||||
* Bugra Cuhadaroglu (BugraC)
|
||||
* Christer Ulfsparre (Holloweye)
|
||||
* Cody Brittain (Generalcamo)
|
||||
* Daniel Derejvanik (Harisson)
|
||||
* Danny (Dan9550)
|
||||
* Erasmus Schroder (rasco)
|
||||
* Igor Popov (ihptru)
|
||||
* Iran
|
||||
* James Dunne (jsd)
|
||||
* Jeff Harris (jeff_1amstudios)
|
||||
* Jes (-Jes-)
|
||||
* Joakim Lindberg (booom3)
|
||||
* JOo
|
||||
* Kenny Hoxworth (hoxworth)
|
||||
* Krishnakanth Mallik
|
||||
* Kyrre Soerensen (zypres)
|
||||
* Lawrence Wang
|
||||
* Lesueur Benjamin (Valkirie)
|
||||
* Mark Olson (markolson)
|
||||
* Matthew Gatland (mgatland)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Maarten Meuris (Nyerguds)
|
||||
* Max Ugrumov (katzsmile)
|
||||
* Max621
|
||||
* Nukem
|
||||
* Paolo Chiodi (paolochiodi)
|
||||
* Paul Dovydaitis (pdovy)
|
||||
* Psydev
|
||||
* Raymond Martineau (mart0258)
|
||||
* Riderr3
|
||||
* Sascha Biedermann (bidifx)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tirili
|
||||
* Tristan Keating (Kilkakon)
|
||||
* Vladimir Komarov (VrKomarov)
|
||||
* Akseli Virtanen (RAGEQUIT)
|
||||
* Andrew Perkins
|
||||
* Andrew Riedi
|
||||
* Andreas Beck (baxtor)
|
||||
* Barnaby Smith (mvi)
|
||||
* Bellator
|
||||
* Bugra Cuhadaroglu (BugraC)
|
||||
* Christer Ulfsparre (Holloweye)
|
||||
* Cody Brittain (Generalcamo)
|
||||
* D2k Sardaukar
|
||||
* Daniel Derejvanik (Harisson)
|
||||
* Danny (Dan9550)
|
||||
* Erasmus Schroder (rasco)
|
||||
* Frank Razenberg (zzattack)
|
||||
* Igor Popov (ihptru)
|
||||
* Iran
|
||||
* James Dunne (jsd)
|
||||
* Jeff Harris (jeff_1amstudios)
|
||||
* Jes
|
||||
* Joakim Lindberg (booom3)
|
||||
* JOo
|
||||
* Kenny Hoxworth (hoxworth)
|
||||
* Krishnakanth Mallik
|
||||
* Kyrre Soerensen (zypres)
|
||||
* Lawrence Wang
|
||||
* Lesueur Benjamin (Valkirie)
|
||||
* Mark Olson (markolson)
|
||||
* Matthew Gatland (mgatland)
|
||||
* Maarten Meuris (Nyerguds)
|
||||
* Max Ugrumov (katzsmile)
|
||||
* Max621
|
||||
* Nukem
|
||||
* Olaf van der Spek
|
||||
* Paolo Chiodi (paolochiodi)
|
||||
* Paul Dovydaitis (pdovy)
|
||||
* Psydev
|
||||
* Raymond Martineau (mart0258)
|
||||
* Reaperrr
|
||||
* Riderr3
|
||||
* Sascha Biedermann (bidifx)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tirili
|
||||
* Tristan Keating (Kilkakon)
|
||||
* Vladimir Komarov (VrKomarov)
|
||||
* Wuschel
|
||||
|
||||
Finally, special thanks goes to the original teams
|
||||
at Westwood Studios and EA for creating the classic
|
||||
games that inspired the creation of OpenRA.
|
||||
|
||||
Red Alert, Command and Conquer, and related
|
||||
trademarks belong to Electronic Arts Inc. and are
|
||||
used without permission.
|
||||
|
||||
Past developers included:
|
||||
* Paul Chote (pchote)
|
||||
* Robert Pepperell (ytinasni)
|
||||
|
||||
576
CHANGELOG
576
CHANGELOG
@@ -1,312 +1,274 @@
|
||||
NEW:
|
||||
Engine:
|
||||
Changed OpenRA now uses Mono.Nat 1.1.0 instead of it's own limited UPnP router detection and port forwarding code (should support more devices now)
|
||||
Changed Detect NAT devices only once on startup (removes lag when creating a new game). Disable the Automatic portforwarding checkbox if no UPnP enabled devices are discovered
|
||||
Added more UPnP user settings: boolean Server.VerboseNatDiscovery for additional debug messages in server.log, integer Server.NatDiscoveryTimeout in miliseconds
|
||||
Now using SharpFont instead of a custom patched Tao.FreeType in close collaboration with upstream
|
||||
Dedicated servers:
|
||||
Misc dedicated server improvements
|
||||
Fixed AI orders misinterpreted as an exploit on dedicated servers
|
||||
Added option to disable bots on servers (including dedicated)
|
||||
Added a dedicated server launch script
|
||||
Fix bots being able to become admin status on dedicated servers
|
||||
Allow MOTD to be fetched from file for dedicated servers
|
||||
Fixed issue with pinging the master-server when dedicated machines did not show up until players connected
|
||||
Allow classic left-click mouse orders (optional)
|
||||
New hotkeys:
|
||||
Hotkeys are now user-configurable via settings.yaml and the GUI
|
||||
Move viewport to selection (default: home or double tap group number)
|
||||
Switch cursor to sell / repair / powerdown
|
||||
Added per player shrouds (units can't shoot enemies covered in unexplored terrain)
|
||||
General game lobby improvements:
|
||||
Add "Assign Teams" lobby drop down button for admins to randomize teams
|
||||
Removed mouse capture while composing text in chat
|
||||
Asynchronous loading of minimaps in the map chooser dialog
|
||||
Map chooser polish - added map type and author to lobby
|
||||
Spawnpoint map tooltips now display players name in lobby
|
||||
Player colors are now reverted after changing to a map which does not lock colors
|
||||
Fixed word wrap when chatting ingame
|
||||
Added a tooltip that shows provided and drained power
|
||||
Changed scroll velocity in map chooser to be better suited for mouse scrolling
|
||||
Fixed grounded airplane not landing properly when ordered to another airstrip or service depot
|
||||
Don't hardcode READY/HOLD and custom prerequisites anymore for easier translation
|
||||
Fixed (removed) sound attentuation for PlayNotification sounds - fixes the Extreme cashtick sounds
|
||||
Replaced admin indicator icon with bold formatted player name
|
||||
Connecting to a different mod will show an appropriate error message (was unknown map before)
|
||||
Fix problems with multi tap detection
|
||||
Fixed floating point inconsistencies to reduce desyncs
|
||||
Civilians now panic when in combat
|
||||
Units with turrets are now displayed correctly when parachuting
|
||||
Print the total number of values extracted from random number generator stream to sync report for debugging purposes
|
||||
Allow strides for sequence definitions (if you don't want to use the full SHP animation length, optional)
|
||||
Fixed a crash when multiple clients on the same machine attempted to write replay files at once
|
||||
Refactored palette remapping rendering code as a preparation for upcoming Voxel support
|
||||
Fixed crashes when attempting to watch replays in non-RA mods
|
||||
Added support for randomized weapon reports
|
||||
Added pathfinder Debug overlay visualization A* search cost per cell in player color tints
|
||||
Attempt to improve A* algorithm
|
||||
Added hidden settings to the GUI:
|
||||
Cap Framerate checkbox and FPS limit
|
||||
Autoplay music after map load
|
||||
Sound engine backend drop down selection box
|
||||
Performance graph text update rate
|
||||
Show bot debug message checkbox
|
||||
Slow trait report threshold slider
|
||||
We now use Travis Continous Integration and enforce a strict no compiler warnings policy
|
||||
Adapted to dpkg's "--instdir"-like options to allow installations of different openra next to each other on Debian GNU/Linux
|
||||
Better AI Bots:
|
||||
Removed the AI attack bottleneck where the whole squad had to be killed completely before a new one was started
|
||||
Units will attack/rush/retreat based on own and opponents health/speed using fuzzy logic while staying in squads
|
||||
Protect harvesters and base buildings when they are attacked
|
||||
Place defense building in the direction of the enemy
|
||||
Place refineries in vicinity to the nearest ressource fields
|
||||
Support for aircraft and ships
|
||||
Try to kill off unprotected enemy targets with aircraft
|
||||
Don't ignore percentage to build for units in yaml configuration
|
||||
Will maintain adequate numbers of refineries and silos
|
||||
Will now try to use super weapons
|
||||
Added a cap for maximum building count
|
||||
Give the users some hints in exception.log when crashing due to desyncs
|
||||
Log projectile destination (innaccuracy calculation) and nextScanTime (auto-target) to debug.log to investigate desyncs
|
||||
Added Shroud, CreatesShroud and RevealsShroud as well as AutoTarget and ScaredyCat (panicking infantry) to syncreport.log
|
||||
Refactoring: cleaned up code duplication to ease the addition of new renderers
|
||||
Added a new reset exploration developer cheat (like hide-map crate, opposite of give exploration)
|
||||
Support for classic RA and C&C mods (not yet packaged):
|
||||
Speedup production queues if multiple of the same building exist
|
||||
Engineer capture rules which require/do damage at first
|
||||
Fixed CustomBuildTimeValue related desync
|
||||
Added a trait to turn off fog of war
|
||||
Separated spy disguise and infiltration for thief
|
||||
Fixed tooltips for fake buildings
|
||||
Fixed a desync with regards to auto-target and Cloak.IsVisible
|
||||
Fixed crash when performing keyboard orders on destroyed actors
|
||||
Added additional server lobby buttons
|
||||
Added burning trees support for RA and C&C
|
||||
Fixed unit ranks not showing in replay viewer
|
||||
Fixed crash caused by capturing a dead actor
|
||||
AI support power activiation needs to go through order manager
|
||||
Ship a proper SDK for modders
|
||||
Added documentation within .cs files for modders
|
||||
Documentation set to push itself to github wiki for new playtests or releases
|
||||
Added a DrawRangeCircleWithContrast method to go along with DrawRangeCircle
|
||||
Added cancel chat with escape
|
||||
Added double click to join servers in browser
|
||||
Desura compatiblity stuff
|
||||
Added sync to carpet bombing
|
||||
Added announcer voice upon game start
|
||||
Added engine support to automatically download a missing map from content.open-ra.org when joining a lobby
|
||||
Added work towards an ingame IRC client
|
||||
Engine changes in preparation for Voxel support - removed Turret and PVecFloat cruft (code refactoring)
|
||||
Changed ThrowsParticle now uses world coordinates and implements a simpler model using a cubic lerp
|
||||
Fixed a crash that may occur in lobbies
|
||||
Made the version mismatch mechanic configurable in the options
|
||||
Fixed color selector/team selector closing when another player in the lobby made a similar change
|
||||
Fixed LaserZap and TeslaZap to account for altitude
|
||||
Send players and bot counts to master server as different fields
|
||||
Sort servers by players in the server browser
|
||||
Fixed cloak units being invis to spectators
|
||||
Fixed paratroopers revealing shroud after death
|
||||
Added a Force Desync cheat for the upcoming automatic crash reporter
|
||||
Changed throw specific exception on missing sound definitions
|
||||
Fixed MCV create not being given if normal SelectionShares was zero
|
||||
Changed split multiple-production-queue widget logic into its own file
|
||||
Added moving average to contrails to smooth them when aircraft are circling
|
||||
Added attack traits into the syncreport.log
|
||||
Added a --docs flag for OpenRA.Utility
|
||||
Removed the tileset builder again from packages
|
||||
Editor:
|
||||
Map editor now saves and loads from OpenRA user folder
|
||||
Fixed map editor failing to import legacy maps
|
||||
Template categories are now ordered by what is specified in the tileset yaml
|
||||
Added terrain category types to RA Snow tileset
|
||||
Added terrain category types to RA Desert tileset
|
||||
Fixed terrain categories for two river tiles in RA Temperate tileset
|
||||
Fixed wrong palette remapping for neutral buildings
|
||||
Show full name + version of the loaded mod in the toolbar
|
||||
Added a help menu with useful tips
|
||||
Added icons to the menu with tooltips
|
||||
Added a toolbar to the editor
|
||||
Added total cash count to editor status bar
|
||||
Added a ruler to the editor
|
||||
Added an eraser tool
|
||||
All mods:
|
||||
Added an ingame credits menu.
|
||||
Added descriptive error messages when connecting to a server fails.
|
||||
Added guard ability to combat units ('d').
|
||||
Added force-move ability (hold alt to prioritize move/crush over attack/harvest).
|
||||
Added change-mod button to the asset download screen.
|
||||
Added kick-ban to the server lobby.
|
||||
Added a button to fill empty lobby slots with bots.
|
||||
Added a renderer geometry visualization to the debug/cheats menu.
|
||||
Fixed exploit allowing arbitrary building placement.
|
||||
Fixed exploit allowing a malicious client to steal admin rights.
|
||||
Fixed glitches relating to passenger-carrying units being killed.
|
||||
Fixed a crash related to bridges on certain maps.
|
||||
Fixed a crash related to the color picker.
|
||||
Fixed a glitch where aircraft circle in the air after being killed.
|
||||
Fixed a glitch where an uncontrollable MCV is left after a construction yard is killed.
|
||||
Fixed civilians panicking when healed.
|
||||
Fixed visibility of additional spectator information (progress bars, spy ownership, etc).
|
||||
Fixed excessive "Building" notifications when rapidly pressing icons in the sidebar.
|
||||
Fixed units continuing to attack a building after it was captured.
|
||||
Fixed units chasing a chronoshifted unit across the map.
|
||||
Fixed units not being able to attack buildings from some angles.
|
||||
Removed some developer-specific options from the settings menu.
|
||||
Red Alert:
|
||||
Automatic pausing when in single player missions
|
||||
Added Allies03 mission
|
||||
Added Allies04 mission (has sunset weather)
|
||||
Added Soviet01Classic mission
|
||||
Added MonsterTankMadness mission
|
||||
Added Survival01 mission
|
||||
Added Desert tileset - original and new tiles ported from Tiberium Dawn by Harrison, code by Matt
|
||||
Fixed empty camo pillboxes in shellmap
|
||||
Fixed graphics for craters for Snow maps (art by MrFibble)
|
||||
Ice Floe actors work in maps now (sequences were missing)
|
||||
Brought back music track "Mud" into the playlist
|
||||
Balance:
|
||||
Base defenses now do less damage to main base structures
|
||||
Pillbox armor changed from wood to heavy
|
||||
Construction Yard armor changed from heavy to wood with a health boost
|
||||
Construction Yard sell ability removed to combat MCV crate exploit
|
||||
Superweapons now have more health
|
||||
Tanya no longer fires her pistols at main base structures
|
||||
Tanya no longer has a C4 demolition cursor for barrels
|
||||
Added missing line of sight for Chronotank
|
||||
Chronotank damage upped from 20 to 30 per rocket and damage improved vs heavy armor
|
||||
Demo truck cost from 1500 to 2500
|
||||
Demo truck health from 110 to 50
|
||||
Demo truck speed from 8 to 6
|
||||
Disguised spies are now invisible under enemy GPS
|
||||
Heroes (Tanya, Volkov) and missile silos are now limited to one per player
|
||||
Volkov got a new armor type: Cybernetic, for individual balancing
|
||||
More health points for Volkov to make him the 4tnk equivalent of infantry
|
||||
Medics can't heal Volkov, send him to repair pad or mechanic instead
|
||||
Dogs can't eat Volkov and tanks need two attempts to crush him
|
||||
Added Mechanic to Allies
|
||||
Added Mobile Gap Generator to Allies
|
||||
Oil derricks are now repairable by engineers
|
||||
Placed mines (AP andAT) now use the Cloak trait, and minelayers will detect mines
|
||||
Added duration indicators for units under Chronosphere and Iron Curtain effects
|
||||
Nukes can destroy ore wells
|
||||
Reworked Volkov death sounds/animations (Cyborg version from German RA)
|
||||
Replaced supply truck graphics with aftermath SHP which has a shadow (via Arda)
|
||||
Added hotkeys for aircraft, vehicles, ships, infantry
|
||||
Modified the hotkeys
|
||||
Fixed visual glitch where war factory closed its door prematurely
|
||||
Fixed broken shadows on GPS dot
|
||||
Fixed Chronotank teleport causing a desync
|
||||
Fixed replay crash when a Chronotank tries to teleport
|
||||
Fixed Chronotank death causing a crash before teleporting
|
||||
Fixed crash caused by Demo Truck destruction
|
||||
Added in-game player statistics for observers
|
||||
Added support for map difficulties
|
||||
Renamed airstrip to airfield in tooltip.
|
||||
Don't remap civilian buildings/fields in-game.
|
||||
Added Random Map button to the lobby
|
||||
Added Crates on/off checkbox to the lobby
|
||||
Gems are now rendered slate blue on the radar minimap
|
||||
Shroud creators now temporarily create shroud
|
||||
Added support for Nyerguds music upgrade pack
|
||||
Added unused actors for legacy map import (thief, chan, general, mobile radar jammer)
|
||||
You can now define the LoadScreenImage in mod.yaml and easily replace it with your own
|
||||
Made conyard buildable with Build Everything cheat
|
||||
Brought back the diplomacy menu, however diplomacy can only be changed in free for all games (Fragile Alliances option)
|
||||
Added a shroud selector when in spectate mode
|
||||
Added an improved ingame chat
|
||||
Fixed maps Chaos Canyon and Bomber John
|
||||
Added map: Room Convergence (Sunny Sproket)
|
||||
Added map: Ghost Town (hamb)
|
||||
Added map: Bad Neighbors (Nukem)
|
||||
Updated map: Bombardment Islands (czech army)
|
||||
Added external capturing for engineers (like C&C Generals).
|
||||
Added collection animation for parabomb crate.
|
||||
Fixed artwork glitches in several units and buildings.
|
||||
Fixed incorrect player color on captured oil derricks on some maps.
|
||||
Fixed support powers being available after winning a game.
|
||||
Fixed sniper damage vs walls.
|
||||
Fixed destroyable ore mines.
|
||||
Tweaked some weapon explosions.
|
||||
Sniper health reduced.
|
||||
Iron curtain duration increased to 20 seconds.
|
||||
Chronoshift duration decreased to 20 seconds.
|
||||
Mobile Radar Jammer range increased.
|
||||
Mechanic repair amount increased.
|
||||
Decrease the infantry run animation speed.
|
||||
Increased oil derrick health and cash rate. Added initial capture bonus.
|
||||
Added maps: Artemis, Fort Lonestar, Pluto, Sahara, Survival02, Tournament Island, Zeus.
|
||||
Updated maps: Ares, Apollo, Dionysus, Doughnut, Poseidon.
|
||||
Renamed maps: Ares: National Park -> Dionysus.
|
||||
Removed maps: Hotzone, Mjolnir-2, Seaside.
|
||||
C&C:
|
||||
Construction yard now has a build placement range around it where structures must be placed within, and must be within 5 tiles of another structure
|
||||
Removed custom build time for tiberium refinery to avoid desync crashes
|
||||
Explosions given names instead of just numbers. Added some explosions that came with the game but were unused.
|
||||
Corrected music.yaml track titles
|
||||
Fix wrong civilian field remapping in-game
|
||||
Slowed down structure building animations
|
||||
Changed sounds for cloaking and building destruction
|
||||
Tanks UnitExplodeSmall now by default
|
||||
Blue tiberium now poisons infantry
|
||||
Levelup crate removed
|
||||
Crate lifetime increased
|
||||
Added Hard AI
|
||||
Temporarily disabled AI building of HPAD and FIX since they can't use them
|
||||
AI modified to cope with construction yard build radius
|
||||
AI to build maximum of 1 silos to build to save space
|
||||
Hospital fan spins slower
|
||||
Slowed down *make animations to 80-tick
|
||||
Fixed infantry running anim
|
||||
Added explosions for flametank and helis
|
||||
Added prison and tech center icon/build-up animations
|
||||
Added @Nyerguds TD Deluxe Cameo Pack
|
||||
Tweaked harvester capacity & unload time
|
||||
Fixed visceroid doesn't show control group number when selected
|
||||
Fixed options menu fade effect
|
||||
Polished map chooser layout
|
||||
Added game type filter to map chooser
|
||||
Developer Cheats menu got renamed to Debug Options and moved to in-game settings
|
||||
Fixed tooltip flickering (increased default tooltip delay to 200 ms)
|
||||
Fixed a bug with shift queueing production on extra production tabs
|
||||
Implemented observer shroud selector
|
||||
Shortened the names of some units in tooltips
|
||||
Balance:
|
||||
Tier 2 infantry and vehicles can now still be built as long as you have the GDI advanced comm center or temple of NOD
|
||||
Added blue tiberium trees
|
||||
Tiberium blossom trees are indestructible
|
||||
Blue tiberium blossom trees grows new resources 50% the speed of the regular green one
|
||||
Crates no longer provide MCVs if you already got one
|
||||
Odds to get a MCV crate without base are now 80% to prevent gambling for it in early game
|
||||
Husks burn 10 seconds now instead of 40
|
||||
Infantry detect cloaked units in adjacent tile
|
||||
Infantry run faster on clear terrain (90% -> 100% speed)
|
||||
Vehicle speeds adjusted, now 80% on clear, like tanks
|
||||
Airstrike cooldown time reduced to 3 minutes (from 4)
|
||||
Construction Yard HP 2000 -> 1500
|
||||
Communications Center HP reduced so it can be nuked
|
||||
Airfield HP reduced so it can be destroyed by simultaneous airstrike + ion cannon
|
||||
Reduced Helipad price reduced to $1000 (was $1500)
|
||||
WEAP/AFLD HP reduced
|
||||
Guard Tower range 6 -> 7, gun damage slightly increased
|
||||
Advantaced Tower range 8 -> 9 and HP 800 -> 600
|
||||
Obelisk Range + 1
|
||||
Obelisk now AutoTargeted properly by units
|
||||
Scaled back tank firing rate a bit (was 2x, now 1.5x)
|
||||
Reduced Artillery damage vs. wood from 75% to 50%.
|
||||
Rotation speeds increased for: bike, buggy, jeep, APC, stnk, ftnk, ltnk
|
||||
Bike speed increased on clear, reduced on road
|
||||
Slight buff to recon bike damage
|
||||
Increased shroud for Buggy, Bike, LTNK and APC.
|
||||
NOD buggy hitpoints reduced 140 -> 120
|
||||
Flamethrower range 3 -> 2.5
|
||||
Halved Chinook price from $1500 to $750
|
||||
Orca/Apache now require any tech building instead of the specific GDI/NOD one
|
||||
Apache ammo 10 -> 6 range 8 -> 10
|
||||
Orca ammo 10 -> 5 range 8 -> 10, ROF decreased 10 -> 15
|
||||
A10 range 8 -> 12
|
||||
Mammoth tank speed 3 -> 4, HP 600 -> 800, turret rotation 2 -> 3
|
||||
Mammoth missile spread increased
|
||||
Apache shoots at air units again
|
||||
STNK range - 1
|
||||
STNK cloak delay slightly increased
|
||||
Slight tweak to Stealth Tank damage vs. heavy armor (100% -> 90%)
|
||||
GUN/GTWR range + 1
|
||||
MCV now slightly slower
|
||||
MCV no longer requires FIX
|
||||
MASM given to Nod - speed and HP slightly lowered - uses patriot missile now
|
||||
APC damage slightly increased, ROF decreased
|
||||
APC speed reduced 10 -> 9
|
||||
APC gun recoil and muzzle flash made single shot instead of double
|
||||
MLRS speed increased 6 -> 7
|
||||
Increased speed and burst delay of TowerMissile
|
||||
Increased nuke damage vs heavy armor
|
||||
Increased ion damage vs heavy armor
|
||||
Fixed stealth tank not holding fire by default
|
||||
Added map: Rock Canyon (psydev)
|
||||
Added map: No Escapism (psydev)
|
||||
Added map: Skull Valley (psydev)
|
||||
Added map: Slippery Slopes (psydev)
|
||||
Added map: Bifurcation (psydev)
|
||||
Added map: Dead in Motion redux (psydev)
|
||||
Added map: East vs West redux (psydev)
|
||||
Removed map: Delta Dunes
|
||||
Restored "classic" multi-engineer behavior. Buildings above 50% health will be damaged by 50%; buildings below this will be captured.
|
||||
Added some texture to the UI background.
|
||||
Fixed veterancy chevrons on cloaked units.
|
||||
Increased aggressiveness of Viceroids.
|
||||
Reduced the pip-count on silos.
|
||||
Orca range increased, reload time decreased.
|
||||
Sam Site missile spread increased.
|
||||
Added maps: Deterring Democracy, Deterring Democracy +
|
||||
Removed maps: East vs West Redux.
|
||||
Updated maps: Skull Valley, Slippery Slopes, The Hourglass.
|
||||
Renamed maps: Rock Canyon -> Manufacturing Consent, Bialystok -> Lessons from Kosovo.
|
||||
Dune 2000:
|
||||
Merged the Rounded Edges Mod by Jes and Bellator:
|
||||
Bibs have been removed
|
||||
Building offset and selection boxes adapted
|
||||
Units and turrets leave husks
|
||||
Siege tank barrel won't rotate anymore
|
||||
AI capable of building all units using all queues
|
||||
Bullet traces
|
||||
Added Windtrap and IX Research building animations
|
||||
New unit balance (mix of Dune II, Dune 2000 and Red Alert)
|
||||
Repair pad animation
|
||||
No more obsessive "silos needed"
|
||||
Defense building queue → heavy armor queue
|
||||
Medics (using thumper graphics)
|
||||
Aircraft are no longer buildable
|
||||
Harvesting has been slowed down
|
||||
Replace blank shellmap with random multiplayer stills
|
||||
Starport requires energy to operate
|
||||
Corrino palace made unbuildable
|
||||
Added new title font.
|
||||
Added maps: Imperial Basin.
|
||||
Fixed harvester docking animation.
|
||||
Added harvesting animation.
|
||||
Uses original (better quality) audio.
|
||||
Updated maps: Black Mesa, Brimstone, Death Depths, Dune Boogie.
|
||||
Engine:
|
||||
Fixed a crash with server hosting under Windows.
|
||||
Reduced order lag for singleplayer games
|
||||
Added support for TS/RA2 mix files.
|
||||
Added support for TS/RA2 SHP images.
|
||||
Added support for TS/RA2 voxel models.
|
||||
Added support for D2K RS archives.
|
||||
Added support for WAV audio files.
|
||||
Added support for XCC mix databases.
|
||||
Added support for the D2K InstallShield archive.
|
||||
Added support for classic production-acceleration behavior.
|
||||
Added support for additional damage states on buildings.
|
||||
Added additional debug information when yaml merging fails.
|
||||
Added additional customization options for laser weapons.
|
||||
Added additional logging for server errors.
|
||||
Additional refactoring working towards TS/RA2 terrain support.
|
||||
Build system and packages:
|
||||
Host windows OpenAL and SDL dependencies on our own servers to avoid excessive downtime.
|
||||
Map Editor and Tools:
|
||||
Added an ingame asset viewer / converter to D2K and RA (enable via the Debug tab in the settings menu).
|
||||
|
||||
20130514:
|
||||
All mods:
|
||||
Destroyed bridges can now be repaired with an engineer.
|
||||
Significantly improved AI.
|
||||
Added (beta) support for left-click mouse orders via the settings menu.
|
||||
Added "move viewport to group" hotkey (home key or double tap group number).
|
||||
Exposed several new and/or hidden preferences in the settings menu.
|
||||
Games can now be paused with a hotkey (F9 by default) or by opening the menu in a single player game.
|
||||
New/improved UI for spectators and defeated players.
|
||||
Improved server list and lobby UI.
|
||||
Fixed units attacking enemy units hidden by fog/shroud.
|
||||
Fixed several other visibility-related bugs.
|
||||
Improved range circle contrast against terrain.
|
||||
Fixed UI notification sound issues.
|
||||
Added "Reset Exploration" developer option (same effect as the hide-map crate).
|
||||
Fixed the visibility of peripheral effects for spectators/replays (ranks, production bars, etc).
|
||||
Improved aircraft contrail rendering.
|
||||
Fixed issue with harvesters not undocking when a refinery is destroyed.
|
||||
Fixed civilian building rendering.
|
||||
Added in-game player statistics for spectators (RA and D2K only).
|
||||
Added "Fragile Alliances" option, allowing alliances to be changed during free-for-all games (RA and D2K only).
|
||||
Added a tooltip that shows provided and drained power (RA and D2K - C&C already had this)
|
||||
Improved ingame chat dialog (RA and D2K only).
|
||||
Red Alert:
|
||||
Added Mobile Gap Generator.
|
||||
Added Mobile Radar Jammer (jams radar and deflects enemy missiles).
|
||||
Added desert theater (ported from C&C plus additional custom tiles by Harrison).
|
||||
Removed Volkov.
|
||||
Added announcer voice upon game start.
|
||||
Civilians now panic when attacked.
|
||||
Added a new desert-themed shellmap inspired by C&C Generals.
|
||||
Added ice floe actors for use on snow maps.
|
||||
Several improvements to the original shellmap.
|
||||
New missions: Allies03, Allies04, Soviet01Classic, MonsterTankMadness, Survival01.
|
||||
New maps: Room Convergence, Ghost Town, Bad Neighbors.
|
||||
Improved maps: Chaos Canyon, Bomber John, Bombardment Islands.
|
||||
Fixed Mechanic repair cursor.
|
||||
Fixed graphics for craters for snow maps.
|
||||
Re-enabled "Mud" music track.
|
||||
Fixed supply track shadow.
|
||||
Added unit production hotkeys.
|
||||
Fixed Airfield tooltip.
|
||||
Changed minimap color of gems to blue.
|
||||
Gap Generator shroud now disappears on low-power/death.
|
||||
Added support for Nyerguds music upgrade pack.
|
||||
Fixed target line/flash for Demo Truck targets.
|
||||
Added building death frames for Construction Yard, Power Plants and Ore Refinery.
|
||||
Improved Ore Silo artwork (more fill states).
|
||||
Improved Weapon Factory door animation and prevented the door closing before units exited.
|
||||
Changed building placement color to black on snow maps for increased contrast.
|
||||
Fixed Attack Dog sound.
|
||||
Added duration indicators for units under Chronosphere and Iron Curtain effects.
|
||||
Fixed Tanya shooting buildings and using C4 on barrels.
|
||||
Fixed Chrono Tank being unable to crush infantry/sandbags or trigger mines.
|
||||
Balance changes:
|
||||
Base defense damage reduced against structures.
|
||||
Pillbox armor increased from wood to heavy.
|
||||
Construction Yard armor reduced/health increased.
|
||||
Construction Yard is now unsellable to prevent Mobile Construction Vehicle crate exploit.
|
||||
Superweapon health increased.
|
||||
Chrono Tank damage increased/health increased/Chrono-shift range limit added/Chronosphere prerequisite added.
|
||||
Demo Truck cost increased/health reduced/speed reduced.
|
||||
Disguised Spies are now hidden from enemy GPS.
|
||||
Added build limit to Missile Silo and Tanya.
|
||||
Oil derricks are now repairable by engineers.
|
||||
Minelayers can now detect mines, allowing other units to target and destroy them.
|
||||
Nukes can destroy ore wells and trees.
|
||||
Tesla Tank health increased.
|
||||
Medium Tank damage increased.
|
||||
Mammoth Tank turret rotation speed increased/damage increased.
|
||||
Badger bomber health increased.
|
||||
Cruiser speed increased.
|
||||
Missile Sub accuracy increased.
|
||||
C&C:
|
||||
Building range and a short construction delay added to Construction Yards to prevent base walking.
|
||||
Mobile SAM launcher given to Nod as dedicated mobile AA.
|
||||
Improved explosions.
|
||||
Corrected music.yaml track titles.
|
||||
Tweaked building construction animation speed.
|
||||
Improved cloak and building destruction sounds.
|
||||
Added blue tiberium trees.
|
||||
Fixed blue tiberium not poisoning infantry.
|
||||
Increased crate lifetime and removed level-up crate effect.
|
||||
Fixed infantry running animations.
|
||||
Fixed Viceroid unit decorations (veterancy/group number).
|
||||
Fixed tooltip flickering when moving the mouse.
|
||||
Fixed mouse-interaction bugs in the production palette.
|
||||
Improved tooltip names.
|
||||
Added maps: The Sentinel, Bialystok, No Escapism, Rock Canyon, Slippery Slopes, Skull Valley, Dead in Motion Redux, East vs West redux, Drop Zone, The Hourglass.
|
||||
Added EVA notification for nuclear missile launches.
|
||||
Husk lifetime reduced to 10 seconds.
|
||||
Obelisk is now automatically targeted by units.
|
||||
Balance:
|
||||
Harvester capacity increased.
|
||||
Advanced Comm-Center and Temple of Nod act as a substitute prerequisite of the Comm-Center.
|
||||
Reduced MCV crate probability to 80% to solve Construction Yard sell exploit.
|
||||
MCV can be constructed without a service depot.
|
||||
Construction Yard health reduced.
|
||||
Infantry detect nearby cloaked units.
|
||||
Infantry and vehicle speed on clear terrain increased.
|
||||
Airstrike regen reduced to three minutes.
|
||||
Comm-Center health reduced.
|
||||
Airfield health reduced.
|
||||
Helipad price reduced.
|
||||
Advanced Guard Tower range increased/health reduced/power consumption increased.
|
||||
Advanced Guard Tower missiles improved vs aircraft and vehicles/reduced vs infantry
|
||||
Obelisk range increased.
|
||||
Tank firing rate increased/damage vs light armor increased.
|
||||
Light Tank speed increased/turn rate increased/ROF increased/damage reduced.
|
||||
Turn speeds increased for most units.
|
||||
Recon Bike damage reduced/sight increased/speed increased on clear, reduced on road.
|
||||
Buggy health reduced/sight increased.
|
||||
Flamethrower range reduced/damage increased vs wood.
|
||||
Flame Tank significantly buffed.
|
||||
Chinook price reduced.
|
||||
Helicopter prerequisites changed to Comm-Center/Advanced Tech building.
|
||||
Apache sight increased.
|
||||
Orca ROF reduced/ammo increased/damage reduced/sight increased.
|
||||
Stealth Tank initial stance changed to "hold-fire."
|
||||
Increased sight range for Light Tank and APC.
|
||||
A10 sight range increased.
|
||||
Mammoth Tank speed increased/health increased/turret rotation increased/missile spread increased.
|
||||
Stealth Tank range reduced/cloak delay increased/damage vs heavy armor reduced.
|
||||
Turret and Guard Tower range increased.
|
||||
APC damage increased/speed reduced.
|
||||
Artillery range reduced/damage vs wood reduced.
|
||||
Rocket Infantry damage increased vs armor.
|
||||
Increased nuke and Ion Cannon damage vs heavy armor.
|
||||
Grenadier's grenade speed increased.
|
||||
SAM site pop-up speed increased.
|
||||
Humvee / Buggy damage increased.
|
||||
MLRS range reduced/ROF reduced/damage reduced.
|
||||
Recon Bike damage increased vs heavy armor/burst delay added/range reduced.
|
||||
Dune 2000:
|
||||
Removed shroud (uses fog-of-war instead). Buildings are hidden under the fog until they are first seen.
|
||||
Added Medic.
|
||||
Added Stealth Raider.
|
||||
Removed Deviator.
|
||||
Aircraft are now unbuildable.
|
||||
Fixed building positioning and selection boxes.
|
||||
Added husks for units and turrets.
|
||||
Added trails to shells and missiles.
|
||||
Fixed animations for Windtrap, Repair Pad, IX Research.
|
||||
Reduced "silos needed" warnings.
|
||||
Husk lifetime reduced to 20 seconds.
|
||||
Added large building radius to construction yard.
|
||||
Improved explosions.
|
||||
Crate probabilities adjusted.
|
||||
New UI button artwork.
|
||||
Added maps: Black Mesa, Black Mesa (large), Dune Boogie, Dune Boogie (large), Brimstone, Death Depths - Modded, Tuck's Sietch.
|
||||
Complete rebalancing from the ground up.
|
||||
Engine:
|
||||
Fixed several desync crashes.
|
||||
Fixed a crash involving keyboard shortcuts and dead units.
|
||||
Fixed a crash involving replays and multiple game sessions on a single machine.
|
||||
Fixed a rare loadscreen crash.
|
||||
Improved logging on game desync.
|
||||
Improved UPnP support via Mono.Nat.
|
||||
Improved freetype support via SharpFont (replaces custom patched Tao.Freetype).
|
||||
Improved dedicated server support.
|
||||
Improved mix file decryption (fixes a crash when playing the "Mud" audio track).
|
||||
Introduced a new coordinate model to simplify eventual TS/RA2 support.
|
||||
Initial work to port existing code to new coordinates model.
|
||||
Added a muzzle positioning debug visualization.
|
||||
Added a pathfinder debug visualization.
|
||||
Removed player-configurable range behind the scenes of the color picker. Fixes "radioactive" color exploit.
|
||||
Show an improved error message when connecting to a server with incompatible mods.
|
||||
Added automatic map downloads from content.open-ra.org when joining a server with an unknown map.
|
||||
Added support for randomized weapon reports.
|
||||
Improved loading times by removing unnecessary map indexing.
|
||||
Scripted maps can now require human players in specified slots.
|
||||
Allow mods to independently enable/disable shroud and fog of war.
|
||||
Minor pathfinding improvements.
|
||||
Other misc refactoring and code cleanup.
|
||||
Build system and packages:
|
||||
Added a `version` rule to the makefile for setting mod version strings on local development builds.
|
||||
Added a `docs` rule for generating trait documentation.
|
||||
Fixed permission errors in the .deb package.
|
||||
Support `--instdir` options for parallel installation of .deb packages.
|
||||
Added Desura compatibility for Linux.
|
||||
Map Editor and Tools:
|
||||
Added a toolstrip with new and improved tools.
|
||||
Added support for terrain categories.
|
||||
Fixed wrong palette remapping for neutral buildings.
|
||||
Maps are now saved/loaded from the custom maps directory.
|
||||
Fixed legacy map importer.
|
||||
Added a --docs flag for OpenRA.Utility for generating trait documentation.
|
||||
|
||||
20121019:
|
||||
Engine:
|
||||
@@ -1254,11 +1216,11 @@ NEW:
|
||||
Added scroll speed control (thanks, Gecko)
|
||||
|
||||
20100922 through 20100922-4
|
||||
Allow queueing of buildings when a building is ready to place
|
||||
Allow queuing of buildings when a building is ready to place
|
||||
Change the pathfinder to use integers instead of floating point (fixes desync)
|
||||
Create ui widgets on demand instead of at gamestart
|
||||
Begin refactoring activity queuing for cleaner code
|
||||
Reenable crates
|
||||
Re-enable crates
|
||||
Remove desync debug logging
|
||||
Fix editor crash
|
||||
Fix crash when a building being repaired is killed
|
||||
|
||||
BIN
Dune2k.ttf
Normal file
BIN
Dune2k.ttf
Normal file
Binary file not shown.
4
Makefile
4
Makefile
@@ -93,8 +93,8 @@ STD_MOD_DEPS = $(STD_MOD_LIBS) $(ralint_TARGET)
|
||||
mod_ra_SRCS := $(shell find OpenRA.Mods.RA/ -iname '*.cs')
|
||||
mod_ra_TARGET = mods/ra/OpenRA.Mods.RA.dll
|
||||
mod_ra_KIND = library
|
||||
mod_ra_DEPS = $(STD_MOD_DEPS)
|
||||
mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS)
|
||||
mod_ra_DEPS = $(STD_MOD_DEPS) $(utility_TARGET)
|
||||
mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(utility_TARGET)
|
||||
mod_ra_EXTRA_CMDS = mono --debug RALint.exe ra
|
||||
PROGRAMS += mod_ra
|
||||
mod_ra: $(mod_ra_TARGET)
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace OpenRA.Editor
|
||||
|
||||
public static ActorTemplate RenderActor(ActorInfo info, TileSet tileset, Palette p)
|
||||
{
|
||||
var image = RenderSimple.GetImage(info);
|
||||
var image = RenderSprites.GetImage(info);
|
||||
|
||||
using (var s = FileSystem.OpenWithExts(image, tileset.Extensions))
|
||||
{
|
||||
|
||||
@@ -41,41 +41,6 @@ namespace OpenRA
|
||||
return a.GetTypes().Select(t => t.Namespace).Distinct().Where(n => n != null);
|
||||
}
|
||||
|
||||
public static string ReadAllText(this Stream s)
|
||||
{
|
||||
using (s)
|
||||
using (var sr = new StreamReader(s))
|
||||
return sr.ReadToEnd();
|
||||
}
|
||||
|
||||
public static byte[] ReadAllBytes(this Stream s)
|
||||
{
|
||||
using (s)
|
||||
{
|
||||
var data = new byte[s.Length - s.Position];
|
||||
s.Read(data, 0, data.Length);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this Stream s, byte[] data)
|
||||
{
|
||||
s.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ReadAllLines(this Stream s)
|
||||
{
|
||||
using (var sr = new StreamReader(s))
|
||||
for (; ; )
|
||||
{
|
||||
var line = sr.ReadLine();
|
||||
if (line == null)
|
||||
yield break;
|
||||
else
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasAttribute<T>(this MemberInfo mi)
|
||||
{
|
||||
return mi.GetCustomAttributes(typeof(T), true).Length != 0;
|
||||
@@ -172,6 +137,11 @@ namespace OpenRA
|
||||
return v;
|
||||
}
|
||||
|
||||
public static bool IsPowerOf2(int v)
|
||||
{
|
||||
return (v & (v - 1)) == 0;
|
||||
}
|
||||
|
||||
public static Size NextPowerOf2(this Size s) { return new Size(NextPowerOf2(s.Width), NextPowerOf2(s.Height)); }
|
||||
|
||||
public static string JoinWith<T>(this IEnumerable<T> ts, string j)
|
||||
@@ -255,6 +225,20 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public static Rectangle Bounds(this Bitmap b) { return new Rectangle(0, 0, b.Width, b.Height); }
|
||||
|
||||
public static int ToBits(this IEnumerable<bool> bits)
|
||||
{
|
||||
var i = 0;
|
||||
var result = 0;
|
||||
foreach (var b in bits)
|
||||
if (b)
|
||||
result |= (1 << i++);
|
||||
else
|
||||
i++;
|
||||
if (i > 33)
|
||||
throw new InvalidOperationException("ToBits only accepts up to 32 values.");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Enum<T>
|
||||
|
||||
125
OpenRA.FileFormats/FileFormats/CRC32.cs
Normal file
125
OpenRA.FileFormats/FileFormats/CRC32.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Static class that uses a lookup table to calculates CRC32
|
||||
/// checksums of input strings.
|
||||
/// </summary>
|
||||
public static class CRC32
|
||||
{
|
||||
/// <summary>
|
||||
/// The CRC32 lookup table
|
||||
/// </summary>
|
||||
static uint[] lookUp = new uint[256]
|
||||
{
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
||||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
|
||||
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
||||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
|
||||
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
|
||||
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
||||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
||||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
|
||||
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
|
||||
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
||||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
|
||||
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
||||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
||||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
|
||||
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
|
||||
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
||||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
||||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
|
||||
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
||||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
|
||||
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
|
||||
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
||||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
||||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
|
||||
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
|
||||
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
||||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
|
||||
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
||||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
||||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
|
||||
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
|
||||
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
||||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A fast (native) CRC32 implementation that can be used on a regular byte arrays.
|
||||
/// </summary>
|
||||
/// <param name="data">The data from which to calculate the checksum.</param>
|
||||
/// <param name="polynomal">The polynomal.</param>
|
||||
/// <returns>
|
||||
/// The calculated checksum.
|
||||
/// </returns>
|
||||
public static uint Calculate(byte[] data, uint polynomal = 0xFFFFFFFF)
|
||||
{
|
||||
uint crc = polynomal;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
crc = (crc >> 8) ^ lookUp[(crc & 0xFF) ^ data[i]];
|
||||
crc ^= polynomal;
|
||||
return crc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A fast (native) CRC32 implementation that can be used on a pinned byte array using
|
||||
/// default polynomal.
|
||||
/// </summary>
|
||||
/// <param name="data"> [in,out] If non-null, the.</param>
|
||||
/// <param name="len"> The length of the data data.</param>
|
||||
/// <param name="polynomal">The polynomal to xor with.</param>
|
||||
/// <returns>The calculated checksum.</returns>
|
||||
public static unsafe uint Calculate(byte* data, uint len, uint polynomal = 0xFFFFFFFF)
|
||||
{
|
||||
uint crc = polynomal;
|
||||
for (int i = 0; i < len; i++)
|
||||
crc = (crc >> 8) ^ lookUp[(crc & 0xFF) ^ *data++];
|
||||
crc ^= polynomal;
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
OpenRA.FileFormats/FileFormats/WavLoader.cs
Normal file
75
OpenRA.FileFormats/FileFormats/WavLoader.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class WavLoader
|
||||
{
|
||||
public readonly int FileSize;
|
||||
public readonly string Format;
|
||||
|
||||
public readonly int FmtChunkSize;
|
||||
public readonly int AudioFormat;
|
||||
public readonly int Channels;
|
||||
public readonly int SampleRate;
|
||||
public readonly int ByteRate;
|
||||
public readonly int BlockAlign;
|
||||
public readonly int BitsPerSample;
|
||||
|
||||
public readonly int DataSize;
|
||||
public readonly byte[] RawOutput;
|
||||
|
||||
public WavLoader(Stream s)
|
||||
{
|
||||
while (s.Position < s.Length)
|
||||
{
|
||||
if ((s.Position & 1) == 1)
|
||||
s.ReadByte(); // Alignment
|
||||
|
||||
var type = s.ReadASCII(4);
|
||||
switch (type)
|
||||
{
|
||||
case "RIFF":
|
||||
FileSize = s.ReadInt32();
|
||||
Format = s.ReadASCII(4);
|
||||
if (Format != "WAVE")
|
||||
throw new NotSupportedException("Not a canonical WAVE file.");
|
||||
break;
|
||||
case "fmt ":
|
||||
FmtChunkSize = s.ReadInt32();
|
||||
if (FmtChunkSize != 16)
|
||||
throw new NotSupportedException("{0} fmt chunk size is not a supported encoding scheme.".F(FmtChunkSize));
|
||||
AudioFormat = s.ReadInt16();
|
||||
if (AudioFormat != 1)
|
||||
throw new NotSupportedException("Non-PCM compression is not supported.");
|
||||
Channels = s.ReadInt16();
|
||||
SampleRate = s.ReadInt32();
|
||||
ByteRate = s.ReadInt32();
|
||||
BlockAlign = s.ReadInt16();
|
||||
BitsPerSample = s.ReadInt16();
|
||||
break;
|
||||
case "data":
|
||||
DataSize = s.ReadInt32();
|
||||
RawOutput = s.ReadBytes(DataSize);
|
||||
break;
|
||||
default:
|
||||
// Ignore unknown chunks
|
||||
var chunkSize = s.ReadInt32();
|
||||
s.ReadBytes(chunkSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
OpenRA.FileFormats/FileFormats/XccGlobalDatabase.cs
Normal file
44
OpenRA.FileFormats/FileFormats/XccGlobalDatabase.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class XccGlobalDatabase
|
||||
{
|
||||
public readonly string[] Entries;
|
||||
public XccGlobalDatabase(Stream s)
|
||||
{
|
||||
var entries = new List<string>();
|
||||
var reader = new BinaryReader(s);
|
||||
while (reader.PeekChar() > -1)
|
||||
{
|
||||
var count = reader.ReadInt32();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var chars = new List<char>();
|
||||
char c;
|
||||
|
||||
// Read filename
|
||||
while ((c = reader.ReadChar()) != 0)
|
||||
chars.Add(c);
|
||||
entries.Add(new string(chars.ToArray()));
|
||||
|
||||
// Skip comment
|
||||
while ((c = reader.ReadChar()) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
Entries = entries.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
67
OpenRA.FileFormats/FileFormats/XccLocalDatabase.cs
Normal file
67
OpenRA.FileFormats/FileFormats/XccLocalDatabase.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class XccLocalDatabase
|
||||
{
|
||||
public readonly string[] Entries;
|
||||
public XccLocalDatabase(Stream s)
|
||||
{
|
||||
// Skip unnecessary header data
|
||||
s.Seek(48, SeekOrigin.Begin);
|
||||
var reader = new BinaryReader(s);
|
||||
var count = reader.ReadInt32();
|
||||
Entries = new string[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var chars = new List<char>();
|
||||
char c;
|
||||
while ((c = reader.ReadChar()) != 0)
|
||||
chars.Add(c);
|
||||
|
||||
Entries[i] = new string(chars.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public XccLocalDatabase(IEnumerable<string> filenames)
|
||||
{
|
||||
Entries = filenames.ToArray();
|
||||
}
|
||||
|
||||
public byte[] Data()
|
||||
{
|
||||
var data = new MemoryStream();
|
||||
using (var writer = new BinaryWriter(data))
|
||||
{
|
||||
writer.Write(Encoding.ASCII.GetBytes("XCC by Olaf van der Spek"));
|
||||
writer.Write(new byte[] {0x1A,0x04,0x17,0x27,0x10,0x19,0x80,0x00});
|
||||
|
||||
writer.Write((int)(Entries.Aggregate(Entries.Length, (a,b) => a + b.Length) + 52)); // Size
|
||||
writer.Write((int)0); // Type
|
||||
writer.Write((int)0); // Version
|
||||
writer.Write((int)0); // Game/Format (0 == TD)
|
||||
writer.Write((int)Entries.Length); // Entries
|
||||
foreach (var e in Entries)
|
||||
{
|
||||
writer.Write(Encoding.ASCII.GetBytes(e));
|
||||
writer.Write((byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
102
OpenRA.FileFormats/Filesystem/D2kSoundResources.cs
Normal file
102
OpenRA.FileFormats/Filesystem/D2kSoundResources.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class D2kSoundResources : IFolder
|
||||
{
|
||||
readonly Stream s;
|
||||
|
||||
readonly string filename;
|
||||
readonly List<string> filenames;
|
||||
readonly int priority;
|
||||
|
||||
readonly Dictionary<uint, PackageEntry> index = new Dictionary<uint, PackageEntry>();
|
||||
|
||||
public D2kSoundResources(string filename, int priority)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
|
||||
s = FileSystem.Open(filename);
|
||||
s.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
filenames = new List<string>();
|
||||
|
||||
var headerLength = s.ReadUInt32();
|
||||
while (s.Position < headerLength + 4)
|
||||
{
|
||||
var name = s.ReadASCIIZ();
|
||||
var offset = s.ReadUInt32();
|
||||
var length = s.ReadUInt32();
|
||||
|
||||
var hash = PackageEntry.HashFilename(name, PackageHashType.Classic);
|
||||
if (!index.ContainsKey(hash))
|
||||
index.Add(hash, new PackageEntry(hash, offset, length));
|
||||
|
||||
filenames.Add(name);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetContent(uint hash)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(hash, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(e.Offset, SeekOrigin.Begin);
|
||||
var data = new byte[e.Length];
|
||||
s.Read(data, 0, (int)e.Length);
|
||||
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
|
||||
public Stream GetContent(string filename)
|
||||
{
|
||||
return GetContent(PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return index.ContainsKey(PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
return filenames;
|
||||
}
|
||||
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
public int Priority { get { return 1000 + priority; }}
|
||||
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
return index.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
throw new NotImplementedException("Cannot save Dune 2000 Sound Resources.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,29 +18,33 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class FileSystem
|
||||
{
|
||||
static List<IFolder> mountedFolders = new List<IFolder>();
|
||||
public static List<IFolder> MountedFolders = new List<IFolder>();
|
||||
static Cache<uint, List<IFolder>> classicHashIndex = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
|
||||
static Cache<uint, List<IFolder>> crcHashIndex = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
|
||||
|
||||
static Cache<uint, List<IFolder>> allFiles = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
|
||||
public static List<string> FolderPaths = new List<string>();
|
||||
|
||||
static void MountInner(IFolder folder)
|
||||
{
|
||||
mountedFolders.Add(folder);
|
||||
MountedFolders.Add(folder);
|
||||
|
||||
foreach( var hash in folder.AllFileHashes() )
|
||||
foreach (var hash in folder.ClassicHashes())
|
||||
{
|
||||
var l = allFiles[hash];
|
||||
if( !l.Contains( folder ) )
|
||||
l.Add( folder );
|
||||
var l = classicHashIndex[hash];
|
||||
if (!l.Contains(folder))
|
||||
l.Add(folder);
|
||||
}
|
||||
|
||||
foreach (var hash in folder.CrcHashes())
|
||||
{
|
||||
var l = crcHashIndex[hash];
|
||||
if (!l.Contains(folder))
|
||||
l.Add(folder);
|
||||
}
|
||||
}
|
||||
|
||||
static int order = 0;
|
||||
|
||||
static IFolder OpenPackage(string filename)
|
||||
{
|
||||
return OpenPackage(filename, order++);
|
||||
}
|
||||
|
||||
public static IFolder CreatePackage(string filename, int order, Dictionary<string, byte[]> content)
|
||||
{
|
||||
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
@@ -49,20 +53,28 @@ namespace OpenRA.FileFormats
|
||||
return new ZipFile(filename, order, content);
|
||||
else if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(filename, order, content);
|
||||
else if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new NotImplementedException("Creating .RS archives is unsupported");
|
||||
else if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new NotImplementedException("Creating .Z archives is unsupported");
|
||||
else
|
||||
return new Folder(filename, order, content);
|
||||
}
|
||||
|
||||
public static IFolder OpenPackage(string filename, int order)
|
||||
public static IFolder OpenPackage(string filename, string annotation, int order)
|
||||
{
|
||||
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new MixFile(filename, order);
|
||||
{
|
||||
var type = string.IsNullOrEmpty(annotation) ? PackageHashType.Classic :
|
||||
FieldLoader.GetValue<PackageHashType>("(value)", annotation);
|
||||
return new MixFile(filename, type, order);
|
||||
}
|
||||
else if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(filename, order);
|
||||
else if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(filename, order);
|
||||
else if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new D2kSoundResources(filename, order);
|
||||
else if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new InstallShieldPackage(filename, order);
|
||||
else
|
||||
@@ -70,6 +82,11 @@ namespace OpenRA.FileFormats
|
||||
}
|
||||
|
||||
public static void Mount(string name)
|
||||
{
|
||||
Mount(name, null);
|
||||
}
|
||||
|
||||
public static void Mount(string name, string annotation)
|
||||
{
|
||||
var optional = name.StartsWith("~");
|
||||
if (optional) name = name.Substring(1);
|
||||
@@ -78,7 +95,8 @@ namespace OpenRA.FileFormats
|
||||
if (name.StartsWith("^"))
|
||||
name = Platform.SupportDir+name.Substring(1);
|
||||
|
||||
var a = (Action)(() => FileSystem.MountInner(OpenPackage(name)));
|
||||
FolderPaths.Add(name);
|
||||
Action a = () => FileSystem.MountInner(OpenPackage(name, annotation, order++));
|
||||
|
||||
if (optional)
|
||||
try { a(); }
|
||||
@@ -89,30 +107,36 @@ namespace OpenRA.FileFormats
|
||||
|
||||
public static void UnmountAll()
|
||||
{
|
||||
mountedFolders.Clear();
|
||||
allFiles = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
|
||||
MountedFolders.Clear();
|
||||
FolderPaths.Clear();
|
||||
classicHashIndex = new Cache<uint, List<IFolder>>(_ => new List<IFolder>());
|
||||
crcHashIndex = new Cache<uint, List<IFolder>>(_ => new List<IFolder>());
|
||||
}
|
||||
|
||||
public static bool Unmount(IFolder mount)
|
||||
{
|
||||
return (mountedFolders.RemoveAll(f => f == mount) > 0);
|
||||
return (MountedFolders.RemoveAll(f => f == mount) > 0);
|
||||
}
|
||||
|
||||
public static void Mount(IFolder mount)
|
||||
{
|
||||
if (!mountedFolders.Contains(mount)) mountedFolders.Add(mount);
|
||||
if (!MountedFolders.Contains(mount)) MountedFolders.Add(mount);
|
||||
}
|
||||
|
||||
public static void LoadFromManifest( Manifest manifest )
|
||||
public static void LoadFromManifest(Manifest manifest)
|
||||
{
|
||||
UnmountAll();
|
||||
foreach (var dir in manifest.Folders) Mount(dir);
|
||||
foreach (var pkg in manifest.Packages) Mount(pkg);
|
||||
foreach (var dir in manifest.Folders)
|
||||
Mount(dir);
|
||||
|
||||
foreach (var pkg in manifest.Packages)
|
||||
Mount(pkg.Key, pkg.Value);
|
||||
}
|
||||
|
||||
static Stream GetFromCache( Cache<uint, List<IFolder>> index, string filename )
|
||||
static Stream GetFromCache(PackageHashType type, string filename)
|
||||
{
|
||||
var folder = index[PackageEntry.HashFilename(filename)]
|
||||
var index = type == PackageHashType.CRC32 ? crcHashIndex : classicHashIndex;
|
||||
var folder = index[PackageEntry.HashFilename(filename, type)]
|
||||
.Where(x => x.Exists(filename))
|
||||
.OrderBy(x => x.Priority)
|
||||
.FirstOrDefault();
|
||||
@@ -125,21 +149,25 @@ namespace OpenRA.FileFormats
|
||||
|
||||
public static Stream Open(string filename) { return OpenWithExts(filename, ""); }
|
||||
|
||||
public static Stream OpenWithExts( string filename, params string[] exts )
|
||||
public static Stream OpenWithExts(string filename, params string[] exts)
|
||||
{
|
||||
if( filename.IndexOfAny( new char[] { '/', '\\' } ) == -1 )
|
||||
if (filename.IndexOfAny(new char[] { '/', '\\' } ) == -1)
|
||||
{
|
||||
foreach( var ext in exts )
|
||||
foreach (var ext in exts)
|
||||
{
|
||||
var s = GetFromCache( allFiles, filename + ext );
|
||||
if( s != null )
|
||||
var s = GetFromCache(PackageHashType.Classic, filename + ext);
|
||||
if (s != null)
|
||||
return s;
|
||||
|
||||
s = GetFromCache(PackageHashType.CRC32, filename + ext);
|
||||
if (s != null)
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var ext in exts )
|
||||
foreach (var ext in exts)
|
||||
{
|
||||
var folder = mountedFolders
|
||||
var folder = MountedFolders
|
||||
.Where(x => x.Exists(filename + ext))
|
||||
.OrderByDescending(x => x.Priority)
|
||||
.FirstOrDefault();
|
||||
@@ -151,7 +179,7 @@ namespace OpenRA.FileFormats
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
}
|
||||
|
||||
public static bool Exists(string filename) { return mountedFolders.Any(f => f.Exists(filename)); }
|
||||
public static bool Exists(string filename) { return MountedFolders.Any(f => f.Exists(filename)); }
|
||||
|
||||
static Dictionary<string, Assembly> assemblyCache = new Dictionary<string, Assembly>();
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ namespace OpenRA.FileFormats
|
||||
public class Folder : IFolder
|
||||
{
|
||||
readonly string path;
|
||||
|
||||
int priority;
|
||||
readonly int priority;
|
||||
|
||||
// Create a new folder package
|
||||
public Folder(string path, int priority, Dictionary<string, byte[]> contents)
|
||||
@@ -45,22 +44,30 @@ namespace OpenRA.FileFormats
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
public IEnumerable<uint> AllFileHashes()
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
foreach( var filename in Directory.GetFiles( path, "*", SearchOption.TopDirectoryOnly ) )
|
||||
yield return PackageEntry.HashFilename( Path.GetFileName(filename) );
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return PackageEntry.HashFilename(Path.GetFileName(filename), PackageHashType.Classic);
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return Path.GetFileName(filename);
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return File.Exists(Path.Combine(path,filename));
|
||||
return File.Exists(Path.Combine(path, filename));
|
||||
}
|
||||
|
||||
|
||||
public int Priority
|
||||
{
|
||||
get { return priority; }
|
||||
}
|
||||
public int Priority { get { return priority; } }
|
||||
public string Name { get { return path; } }
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
|
||||
@@ -18,13 +18,17 @@ namespace OpenRA.FileFormats
|
||||
public class InstallShieldPackage : IFolder
|
||||
{
|
||||
readonly Dictionary<uint, PackageEntry> index = new Dictionary<uint, PackageEntry>();
|
||||
readonly List<string> filenames;
|
||||
readonly Stream s;
|
||||
readonly long dataStart = 255;
|
||||
int priority;
|
||||
readonly int priority;
|
||||
readonly string filename;
|
||||
|
||||
public InstallShieldPackage(string filename, int priority)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
filenames = new List<string>();
|
||||
s = FileSystem.Open(filename);
|
||||
|
||||
// Parse package header
|
||||
@@ -45,11 +49,20 @@ namespace OpenRA.FileFormats
|
||||
// Parse the directory list
|
||||
s.Seek(TOCAddress, SeekOrigin.Begin);
|
||||
BinaryReader TOCreader = new BinaryReader(s);
|
||||
|
||||
var fileCountInDirs = new List<uint>();
|
||||
// Parse directories
|
||||
for (var i = 0; i < DirCount; i++)
|
||||
ParseDirectory(TOCreader);
|
||||
fileCountInDirs.Add(ParseDirectory(TOCreader));
|
||||
|
||||
// Parse files
|
||||
foreach (var fileCount in fileCountInDirs)
|
||||
for (var i = 0; i < fileCount; i++)
|
||||
ParseFile(reader);
|
||||
|
||||
}
|
||||
|
||||
void ParseDirectory(BinaryReader reader)
|
||||
uint ParseDirectory(BinaryReader reader)
|
||||
{
|
||||
// Parse directory header
|
||||
var FileCount = reader.ReadUInt16();
|
||||
@@ -59,10 +72,7 @@ namespace OpenRA.FileFormats
|
||||
|
||||
// Skip to the end of the chunk
|
||||
reader.ReadBytes(ChunkSize - NameLength - 6);
|
||||
|
||||
// Parse files
|
||||
for (var i = 0; i < FileCount; i++)
|
||||
ParseFile(reader);
|
||||
return FileCount;
|
||||
}
|
||||
|
||||
uint AccumulatedData = 0;
|
||||
@@ -76,8 +86,10 @@ namespace OpenRA.FileFormats
|
||||
var NameLength = reader.ReadByte();
|
||||
var FileName = new String(reader.ReadChars(NameLength));
|
||||
|
||||
var hash = PackageEntry.HashFilename(FileName);
|
||||
index.Add(hash, new PackageEntry(hash,AccumulatedData, CompressedSize));
|
||||
var hash = PackageEntry.HashFilename(FileName, PackageHashType.Classic);
|
||||
if (!index.ContainsKey(hash))
|
||||
index.Add(hash, new PackageEntry(hash,AccumulatedData, CompressedSize));
|
||||
filenames.Add(FileName);
|
||||
AccumulatedData += CompressedSize;
|
||||
|
||||
// Skip to the end of the chunk
|
||||
@@ -90,33 +102,40 @@ namespace OpenRA.FileFormats
|
||||
if (!index.TryGetValue(hash, out e))
|
||||
return null;
|
||||
|
||||
s.Seek( dataStart + e.Offset, SeekOrigin.Begin );
|
||||
byte[] data = new byte[ e.Length ];
|
||||
s.Read( data, 0, (int)e.Length );
|
||||
s.Seek(dataStart + e.Offset, SeekOrigin.Begin);
|
||||
var data = new byte[e.Length];
|
||||
s.Read(data, 0, (int)e.Length);
|
||||
|
||||
return new MemoryStream(Blast.Decompress(data));
|
||||
}
|
||||
|
||||
public Stream GetContent(string filename)
|
||||
{
|
||||
return GetContent(PackageEntry.HashFilename(filename));
|
||||
return GetContent(PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
public IEnumerable<uint> AllFileHashes()
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
return index.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
return filenames;
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return index.ContainsKey(PackageEntry.HashFilename(filename));
|
||||
return index.ContainsKey(PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
|
||||
public int Priority
|
||||
{
|
||||
get { return 2000 + priority; }
|
||||
}
|
||||
public int Priority { get { return 2000 + priority; }}
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
@@ -19,9 +20,12 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
Stream GetContent(string filename);
|
||||
bool Exists(string filename);
|
||||
IEnumerable<uint> AllFileHashes();
|
||||
IEnumerable<uint> ClassicHashes();
|
||||
IEnumerable<uint> CrcHashes();
|
||||
IEnumerable<string> AllFileNames();
|
||||
void Write(Dictionary<string, byte[]> contents);
|
||||
int Priority { get; }
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
public class MixFile : IFolder
|
||||
@@ -29,35 +33,41 @@ namespace OpenRA.FileFormats
|
||||
readonly Dictionary<uint, PackageEntry> index;
|
||||
readonly long dataStart;
|
||||
readonly Stream s;
|
||||
int priority;
|
||||
readonly int priority;
|
||||
readonly string filename;
|
||||
readonly PackageHashType type;
|
||||
|
||||
// Save a mix to disk with the given contents
|
||||
public MixFile(string filename, int priority, Dictionary<string, byte[]> contents)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
this.type = PackageHashType.Classic;
|
||||
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
s = File.Create(filename);
|
||||
|
||||
// TODO: Add a local mix database.dat for compatibility with XCC Mixer
|
||||
index = new Dictionary<uint, PackageEntry>();
|
||||
contents.Add("local mix database.dat", new XccLocalDatabase(contents.Keys.Append("local mix database.dat")).Data());
|
||||
Write(contents);
|
||||
}
|
||||
|
||||
public MixFile(string filename, int priority)
|
||||
public MixFile(string filename, PackageHashType type, int priority)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
this.type = type;
|
||||
s = FileSystem.Open(filename);
|
||||
|
||||
// Detect format type
|
||||
s.Seek(0, SeekOrigin.Begin);
|
||||
var reader = new BinaryReader(s);
|
||||
var isCncMix = reader.ReadUInt16() != 0;
|
||||
var isCncMix = s.ReadUInt16() != 0;
|
||||
|
||||
// The C&C mix format doesn't contain any flags or encryption
|
||||
var isEncrypted = false;
|
||||
if (!isCncMix)
|
||||
isEncrypted = (reader.ReadUInt16() & 0x2) != 0;
|
||||
isEncrypted = (s.ReadUInt16() & 0x2) != 0;
|
||||
|
||||
List<PackageEntry> entries;
|
||||
if (isEncrypted)
|
||||
@@ -77,13 +87,12 @@ namespace OpenRA.FileFormats
|
||||
List<PackageEntry> ParseHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
var reader = new BinaryReader(s);
|
||||
var numFiles = reader.ReadUInt16();
|
||||
/*uint dataSize = */reader.ReadUInt32();
|
||||
var numFiles = s.ReadUInt16();
|
||||
/*uint dataSize = */s.ReadUInt32();
|
||||
|
||||
var items = new List<PackageEntry>();
|
||||
for (var i = 0; i < numFiles; i++)
|
||||
items.Add(new PackageEntry(reader));
|
||||
items.Add(new PackageEntry(s));
|
||||
|
||||
headerEnd = offset + 6 + numFiles*PackageEntry.Size;
|
||||
return items;
|
||||
@@ -92,16 +101,15 @@ namespace OpenRA.FileFormats
|
||||
MemoryStream DecryptHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
var reader = new BinaryReader(s);
|
||||
|
||||
// Decrypt blowfish key
|
||||
var keyblock = reader.ReadBytes(80);
|
||||
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 = new BinaryReader(ms).ReadUInt16();
|
||||
var numFiles = ms.ReadUInt16();
|
||||
|
||||
// Decrypt the full header - round bytes up to a full block
|
||||
var blockCount = (13 + numFiles*PackageEntry.Size)/8;
|
||||
@@ -127,16 +135,32 @@ namespace OpenRA.FileFormats
|
||||
uint[] ReadBlocks(Stream s, long offset, int count)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
var r = new BinaryReader(s);
|
||||
|
||||
// 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] = r.ReadUInt32();
|
||||
ret[i] = s.ReadUInt32();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint? FindMatchingHash(string filename)
|
||||
{
|
||||
var hash = PackageEntry.HashFilename(filename, type);
|
||||
if (index.ContainsKey(hash))
|
||||
return hash;
|
||||
|
||||
// Maybe we were given a raw hash?
|
||||
uint raw;
|
||||
if (!uint.TryParse(filename, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out raw))
|
||||
return null;
|
||||
|
||||
if ("{0:X}".F(raw) == filename && index.ContainsKey(raw))
|
||||
return raw;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream GetContent(uint hash)
|
||||
{
|
||||
PackageEntry e;
|
||||
@@ -151,25 +175,63 @@ namespace OpenRA.FileFormats
|
||||
|
||||
public Stream GetContent(string filename)
|
||||
{
|
||||
return GetContent(PackageEntry.HashFilename(filename));
|
||||
var hash = FindMatchingHash(filename);
|
||||
return hash.HasValue ? GetContent(hash.Value) : null;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> AllFileHashes()
|
||||
static readonly uint[] Nothing = {};
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
return index.Keys;
|
||||
if (type == PackageHashType.Classic)
|
||||
return index.Keys;
|
||||
|
||||
return Nothing;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
if (type == PackageHashType.CRC32)
|
||||
return index.Keys;
|
||||
|
||||
return Nothing;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
var lookup = new Dictionary<uint, string>();
|
||||
if (Exists("local mix database.dat"))
|
||||
{
|
||||
var db = new XccLocalDatabase(GetContent("local mix database.dat"));
|
||||
foreach (var e in db.Entries)
|
||||
{
|
||||
var hash = PackageEntry.HashFilename(e, type);
|
||||
if (!lookup.ContainsKey(hash))
|
||||
lookup.Add(hash, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (FileSystem.Exists("global mix database.dat"))
|
||||
{
|
||||
var db = new XccGlobalDatabase(FileSystem.Open("global mix database.dat"));
|
||||
foreach (var e in db.Entries)
|
||||
{
|
||||
var hash = PackageEntry.HashFilename(e, type);
|
||||
if (!lookup.ContainsKey(hash))
|
||||
lookup.Add(hash, e);
|
||||
}
|
||||
}
|
||||
|
||||
return index.Keys.Select(k => lookup.ContainsKey(k) ? lookup[k] : "{0:X}".F(k));
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return index.ContainsKey(PackageEntry.HashFilename(filename));
|
||||
}
|
||||
|
||||
|
||||
public int Priority
|
||||
{
|
||||
get { return 1000 + priority; }
|
||||
return FindMatchingHash(filename).HasValue;
|
||||
}
|
||||
|
||||
public int Priority { get { return 1000 + priority; } }
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
// Cannot modify existing mixfile - rename existing file and
|
||||
@@ -186,7 +248,7 @@ namespace OpenRA.FileFormats
|
||||
foreach (var kv in contents)
|
||||
{
|
||||
var length = (uint)kv.Value.Length;
|
||||
var hash = PackageEntry.HashFilename(Path.GetFileName(kv.Key));
|
||||
var hash = PackageEntry.HashFilename(Path.GetFileName(kv.Key), type);
|
||||
items.Add(new PackageEntry(hash, dataSize, length));
|
||||
dataSize += length;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ namespace OpenRA.FileFormats
|
||||
|
||||
public Stream GetContent(string filename)
|
||||
{
|
||||
|
||||
using (var z = pkg.GetInputStream(pkg.GetEntry(filename)))
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
@@ -65,10 +64,21 @@ namespace OpenRA.FileFormats
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<uint> AllFileHashes()
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
foreach(ZipEntry entry in pkg)
|
||||
yield return PackageEntry.HashFilename(entry.Name);
|
||||
yield return PackageEntry.HashFilename(entry.Name, PackageHashType.Classic);
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
foreach(ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
@@ -76,10 +86,8 @@ namespace OpenRA.FileFormats
|
||||
return pkg.GetEntry(filename) != null;
|
||||
}
|
||||
|
||||
public int Priority
|
||||
{
|
||||
get { return 500 + priority; }
|
||||
}
|
||||
public int Priority { get { return 500 + priority; } }
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
|
||||
@@ -36,23 +36,23 @@ namespace OpenRA.FileFormats
|
||||
public readonly byte[] LookupTable;
|
||||
public byte[] Image;
|
||||
|
||||
public Dune2ImageHeader(BinaryReader reader)
|
||||
public Dune2ImageHeader(Stream s)
|
||||
{
|
||||
Flags = (Dune2ImageFlags)reader.ReadUInt16();
|
||||
Slices = reader.ReadByte();
|
||||
Width = reader.ReadUInt16();
|
||||
Height = reader.ReadByte();
|
||||
FileSize = reader.ReadUInt16();
|
||||
DataSize = reader.ReadUInt16();
|
||||
Flags = (Dune2ImageFlags)s.ReadUInt16();
|
||||
Slices = s.ReadUInt8();
|
||||
Width = s.ReadUInt16();
|
||||
Height = s.ReadUInt8();
|
||||
FileSize = s.ReadUInt16();
|
||||
DataSize = s.ReadUInt16();
|
||||
|
||||
if (Flags == Dune2ImageFlags.L16_F80_F2_1 ||
|
||||
Flags == Dune2ImageFlags.L16_F80_F2_2 ||
|
||||
Flags == Dune2ImageFlags.Ln_F80_F2)
|
||||
{
|
||||
int n = Flags == Dune2ImageFlags.Ln_F80_F2 ? reader.ReadByte() : (byte)16;
|
||||
int n = Flags == Dune2ImageFlags.Ln_F80_F2 ? s.ReadUInt8() : (byte)16;
|
||||
LookupTable = new byte[n];
|
||||
for (int i = 0; i < n; i++)
|
||||
LookupTable[i] = reader.ReadByte();
|
||||
LookupTable[i] = s.ReadUInt8();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -78,16 +78,14 @@ namespace OpenRA.FileFormats
|
||||
|
||||
List<Dune2ImageHeader> headers = new List<Dune2ImageHeader>();
|
||||
|
||||
public Dune2ShpReader(Stream stream)
|
||||
public Dune2ShpReader(Stream s)
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
|
||||
ImageCount = reader.ReadUInt16();
|
||||
ImageCount = s.ReadUInt16();
|
||||
|
||||
//Last offset is pointer to end of file.
|
||||
uint[] offsets = new uint[ImageCount + 1];
|
||||
|
||||
uint temp = reader.ReadUInt32();
|
||||
uint temp = s.ReadUInt32();
|
||||
|
||||
//If fourth byte in file is non-zero, the offsets are two bytes each.
|
||||
bool twoByteOffsets = (temp & 0xFF0000) > 0;
|
||||
@@ -100,13 +98,13 @@ namespace OpenRA.FileFormats
|
||||
offsets[0] = temp + 2;
|
||||
|
||||
for (int i = twoByteOffsets ? 2 : 1; i < ImageCount + 1; i++)
|
||||
offsets[i] = (twoByteOffsets ? reader.ReadUInt16() : reader.ReadUInt32()) + 2;
|
||||
offsets[i] = (twoByteOffsets ? s.ReadUInt16() : s.ReadUInt32()) + 2;
|
||||
|
||||
for (int i = 0; i < ImageCount; i++)
|
||||
{
|
||||
reader.BaseStream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
Dune2ImageHeader header = new Dune2ImageHeader(reader);
|
||||
byte[] imgData = reader.ReadBytes(header.FileSize);
|
||||
s.Seek(offsets[i], SeekOrigin.Begin);
|
||||
Dune2ImageHeader header = new Dune2ImageHeader(s);
|
||||
byte[] imgData = s.ReadBytes(header.FileSize);
|
||||
header.Image = new byte[header.Height * header.Width];
|
||||
|
||||
//Decode image data
|
||||
|
||||
57
OpenRA.FileFormats/Graphics/HvaReader.cs
Normal file
57
OpenRA.FileFormats/Graphics/HvaReader.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class HvaReader
|
||||
{
|
||||
public readonly uint FrameCount;
|
||||
public readonly uint LimbCount;
|
||||
public readonly float[] Transforms;
|
||||
|
||||
public HvaReader(Stream s)
|
||||
{
|
||||
// Index swaps for transposing a matrix
|
||||
var ids = new byte[]{0,4,8,12,1,5,9,13,2,6,10,14};
|
||||
|
||||
s.Seek(16, SeekOrigin.Begin);
|
||||
FrameCount = s.ReadUInt32();
|
||||
LimbCount = s.ReadUInt32();
|
||||
|
||||
// Skip limb names
|
||||
s.Seek(16*LimbCount, SeekOrigin.Current);
|
||||
Transforms = new float[16*FrameCount*LimbCount];
|
||||
for (var j = 0; j < FrameCount; j++)
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
// Convert to column-major matrices and add the final matrix row
|
||||
var c = 16*(LimbCount*j + i);
|
||||
Transforms[c + 3] = 0;
|
||||
Transforms[c + 7] = 0;
|
||||
Transforms[c + 11] = 0;
|
||||
Transforms[c + 15] = 1;
|
||||
|
||||
for (var k = 0; k < 12; k++)
|
||||
Transforms[c + ids[k]] = s.ReadFloat();
|
||||
}
|
||||
}
|
||||
|
||||
public static HvaReader Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return new HvaReader(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ namespace OpenRA.FileFormats.Graphics
|
||||
IVertexBuffer<Vertex> CreateVertexBuffer( int length );
|
||||
ITexture CreateTexture( Bitmap bitmap );
|
||||
ITexture CreateTexture();
|
||||
IFrameBuffer CreateFrameBuffer(Size s);
|
||||
IShader CreateShader( string name );
|
||||
|
||||
Size WindowSize { get; }
|
||||
@@ -50,6 +51,12 @@ namespace OpenRA.FileFormats.Graphics
|
||||
void SetLineWidth( float width );
|
||||
void EnableScissor( int left, int top, int width, int height );
|
||||
void DisableScissor();
|
||||
|
||||
void EnableDepthBuffer();
|
||||
void DisableDepthBuffer();
|
||||
|
||||
void EnableAlphaBlending();
|
||||
void DisableAlphaBlending();
|
||||
}
|
||||
|
||||
public interface IVertexBuffer<T>
|
||||
@@ -60,8 +67,11 @@ namespace OpenRA.FileFormats.Graphics
|
||||
|
||||
public interface IShader
|
||||
{
|
||||
void SetVec(string name, float x);
|
||||
void SetVec(string name, float x, float y);
|
||||
void SetVec(string name, float[] vec, int length);
|
||||
void SetTexture(string param, ITexture texture);
|
||||
void SetMatrix(string param, float[] mtx);
|
||||
void Render(Action a);
|
||||
}
|
||||
|
||||
@@ -70,6 +80,15 @@ namespace OpenRA.FileFormats.Graphics
|
||||
void SetData(Bitmap bitmap);
|
||||
void SetData(uint[,] colors);
|
||||
void SetData(byte[] colors, int width, int height);
|
||||
byte[] GetData();
|
||||
Size Size { get; }
|
||||
}
|
||||
|
||||
public interface IFrameBuffer
|
||||
{
|
||||
void Bind();
|
||||
void Unbind();
|
||||
ITexture Texture { get; }
|
||||
}
|
||||
|
||||
public enum PrimitiveType
|
||||
|
||||
@@ -62,9 +62,9 @@ namespace OpenRA.FileFormats
|
||||
|
||||
int recurseDepth = 0;
|
||||
|
||||
public ShpReader( Stream stream )
|
||||
public ShpReader(Stream stream)
|
||||
{
|
||||
using( var reader = new BinaryReader( stream ) )
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
ImageCount = reader.ReadUInt16();
|
||||
reader.ReadUInt16();
|
||||
@@ -73,60 +73,60 @@ namespace OpenRA.FileFormats
|
||||
Height = reader.ReadUInt16();
|
||||
reader.ReadUInt32();
|
||||
|
||||
for( int i = 0 ; i < ImageCount ; i++ )
|
||||
headers.Add( new ImageHeader( reader ) );
|
||||
for (int i = 0 ; i < ImageCount ; i++)
|
||||
headers.Add(new ImageHeader(reader));
|
||||
|
||||
new ImageHeader( reader ); // end-of-file header
|
||||
new ImageHeader( reader ); // all-zeroes header
|
||||
new ImageHeader(reader); // end-of-file header
|
||||
new ImageHeader(reader); // all-zeroes header
|
||||
|
||||
var offsets = headers.ToDictionary(h => h.Offset, h =>h);
|
||||
|
||||
for( int i = 0 ; i < ImageCount ; i++ )
|
||||
for (int i = 0 ; i < ImageCount ; i++)
|
||||
{
|
||||
var h = headers[ i ];
|
||||
if( h.Format == Format.Format20 )
|
||||
h.RefImage = headers[ i - 1 ];
|
||||
if (h.Format == Format.Format20)
|
||||
h.RefImage = headers[i - 1];
|
||||
|
||||
else if( h.Format == Format.Format40 )
|
||||
if( !offsets.TryGetValue( h.RefOffset, out h.RefImage ) )
|
||||
throw new InvalidDataException( "Reference doesnt point to image data {0}->{1}".F(h.Offset, h.RefOffset) );
|
||||
else if (h.Format == Format.Format40)
|
||||
if (!offsets.TryGetValue(h.RefOffset, out h.RefImage))
|
||||
throw new InvalidDataException("Reference doesnt point to image data {0}->{1}".F(h.Offset, h.RefOffset));
|
||||
}
|
||||
|
||||
foreach( ImageHeader h in headers )
|
||||
Decompress( stream, h );
|
||||
foreach (ImageHeader h in headers)
|
||||
Decompress(stream, h);
|
||||
}
|
||||
}
|
||||
|
||||
public ImageHeader this[ int index ]
|
||||
public ImageHeader this[int index]
|
||||
{
|
||||
get { return headers[ index ]; }
|
||||
get { return headers[index]; }
|
||||
}
|
||||
|
||||
void Decompress( Stream stream, ImageHeader h )
|
||||
void Decompress(Stream stream, ImageHeader h)
|
||||
{
|
||||
if( recurseDepth > ImageCount )
|
||||
throw new InvalidDataException( "Format20/40 headers contain infinite loop" );
|
||||
if (recurseDepth > ImageCount)
|
||||
throw new InvalidDataException("Format20/40 headers contain infinite loop");
|
||||
|
||||
switch( h.Format )
|
||||
switch(h.Format)
|
||||
{
|
||||
case Format.Format20:
|
||||
case Format.Format40:
|
||||
{
|
||||
if( h.RefImage.Image == null )
|
||||
if (h.RefImage.Image == null)
|
||||
{
|
||||
++recurseDepth;
|
||||
Decompress( stream, h.RefImage );
|
||||
Decompress(stream, h.RefImage);
|
||||
--recurseDepth;
|
||||
}
|
||||
|
||||
h.Image = CopyImageData( h.RefImage.Image );
|
||||
h.Image = CopyImageData(h.RefImage.Image);
|
||||
Format40.DecodeInto(ReadCompressedData(stream, h), h.Image);
|
||||
break;
|
||||
}
|
||||
case Format.Format80:
|
||||
{
|
||||
var imageBytes = new byte[ Width * Height ];
|
||||
Format80.DecodeInto( ReadCompressedData( stream, h ), imageBytes );
|
||||
var imageBytes = new byte[Width * Height];
|
||||
Format80.DecodeInto(ReadCompressedData(stream, h), imageBytes);
|
||||
h.Image = imageBytes;
|
||||
break;
|
||||
}
|
||||
@@ -135,11 +135,11 @@ namespace OpenRA.FileFormats
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] ReadCompressedData( Stream stream, ImageHeader h )
|
||||
static byte[] ReadCompressedData(Stream stream, ImageHeader h)
|
||||
{
|
||||
stream.Position = h.Offset;
|
||||
// Actually, far too big. There's no length field with the correct length though :(
|
||||
var compressedLength = (int)( stream.Length - stream.Position );
|
||||
// TODO: Actually, far too big. There's no length field with the correct length though :(
|
||||
var compressedLength = (int)(stream.Length - stream.Position);
|
||||
|
||||
var compressedBytes = new byte[ compressedLength ];
|
||||
stream.Read( compressedBytes, 0, compressedLength );
|
||||
@@ -147,11 +147,11 @@ namespace OpenRA.FileFormats
|
||||
return compressedBytes;
|
||||
}
|
||||
|
||||
byte[] CopyImageData( byte[] baseImage )
|
||||
byte[] CopyImageData(byte[] baseImage)
|
||||
{
|
||||
var imageData = new byte[ Width * Height ];
|
||||
for( int i = 0 ; i < Width * Height ; i++ )
|
||||
imageData[ i ] = baseImage[ i ];
|
||||
var imageData = new byte[Width * Height];
|
||||
for (int i = 0 ; i < Width * Height ; i++)
|
||||
imageData[i] = baseImage[i];
|
||||
|
||||
return imageData;
|
||||
}
|
||||
|
||||
615
OpenRA.FileFormats/Graphics/ShpTSReader.cs
Normal file
615
OpenRA.FileFormats/Graphics/ShpTSReader.cs
Normal file
@@ -0,0 +1,615 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see LICENSE.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
|
||||
public struct Header
|
||||
{
|
||||
public ushort A;
|
||||
// Unknown
|
||||
// Width and Height of the images
|
||||
public ushort Width;
|
||||
public ushort Height;
|
||||
public ushort NumImages;
|
||||
}
|
||||
|
||||
public class HeaderImage
|
||||
{
|
||||
public ushort x;
|
||||
public ushort y;
|
||||
public ushort cx;
|
||||
public ushort cy;
|
||||
// cx and cy are width n height of stored image
|
||||
public byte compression;
|
||||
public byte[] align;
|
||||
public byte[] transparent;
|
||||
public int zero;
|
||||
public int offset;
|
||||
public byte[] Image;
|
||||
}
|
||||
|
||||
public struct SHPData
|
||||
{
|
||||
public HeaderImage HeaderImage;
|
||||
public byte[] Databuffer;
|
||||
public byte[] FrameImage;
|
||||
}
|
||||
|
||||
public struct SHP
|
||||
{
|
||||
public Header Header;
|
||||
public SHPData[] Data;
|
||||
}
|
||||
|
||||
|
||||
public class ShpTSReader : IEnumerable<HeaderImage>
|
||||
{
|
||||
public readonly int ImageCount;
|
||||
public readonly ushort Width;
|
||||
public readonly ushort Height;
|
||||
public readonly ushort Width2;
|
||||
public readonly ushort Height2;
|
||||
public int arroff = 0;
|
||||
public int erri = 0;
|
||||
public int errj = 0;
|
||||
public int errk = 0;
|
||||
public int errl = 0;
|
||||
|
||||
public static int FindNextOffsetFrom(SHP SHP, int Init, int Last)
|
||||
{
|
||||
int result;
|
||||
result = 0;
|
||||
Last++;
|
||||
while ((result == 0) && (Init < Last))
|
||||
{
|
||||
result = SHP.Data[Init].HeaderImage.offset;
|
||||
Init++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private readonly List<HeaderImage> headers = new List<HeaderImage>();
|
||||
|
||||
public ShpTSReader(Stream s)
|
||||
{
|
||||
|
||||
SHP SHP = new SHP();
|
||||
int FileSize;
|
||||
int x;
|
||||
int k = 0;
|
||||
int l = 0;
|
||||
|
||||
int ImageSize;
|
||||
int NextOffset;
|
||||
|
||||
byte[] FData;
|
||||
byte cp;
|
||||
byte[] Databuffer;
|
||||
|
||||
FileSize = (int)s.Length;
|
||||
// Get Header
|
||||
SHP.Header.A = s.ReadUInt16();
|
||||
SHP.Header.Width = s.ReadUInt16();
|
||||
SHP.Header.Height = s.ReadUInt16();
|
||||
SHP.Header.NumImages = s.ReadUInt16();
|
||||
|
||||
SHP.Data = new SHPData[SHP.Header.NumImages + 1];
|
||||
|
||||
ImageCount = SHP.Header.NumImages;
|
||||
|
||||
for (x = 1; x <= SHP.Header.NumImages; x++)
|
||||
{
|
||||
SHP.Data[x].HeaderImage = new HeaderImage();
|
||||
|
||||
SHP.Data[x].HeaderImage.x = s.ReadUInt16();
|
||||
SHP.Data[x].HeaderImage.y = s.ReadUInt16();
|
||||
SHP.Data[x].HeaderImage.cx = s.ReadUInt16();
|
||||
SHP.Data[x].HeaderImage.cy = s.ReadUInt16();
|
||||
|
||||
SHP.Data[x].HeaderImage.compression = s.ReadUInt8();
|
||||
SHP.Data[x].HeaderImage.align = s.ReadBytes(3);
|
||||
s.ReadInt32();
|
||||
SHP.Data[x].HeaderImage.zero = s.ReadUInt8();
|
||||
SHP.Data[x].HeaderImage.transparent = s.ReadBytes(3);
|
||||
|
||||
SHP.Data[x].HeaderImage.offset = s.ReadInt32();
|
||||
|
||||
}
|
||||
|
||||
Width = SHP.Header.Width;
|
||||
Height = SHP.Header.Height;
|
||||
|
||||
for (int i = 0; i < ImageCount; i++)
|
||||
{
|
||||
headers.Add(SHP.Data[i+1].HeaderImage);
|
||||
}
|
||||
|
||||
// Read and decode each image from the file
|
||||
for (x = 1; x <= SHP.Header.NumImages; x++)
|
||||
{
|
||||
headers[x - 1].Image = new byte[(Width * Height)];
|
||||
for (int i = 0; i < headers[x - 1].Image.Length; i++)
|
||||
headers[x - 1].Image[i] = 0;
|
||||
|
||||
FData = new byte[(Width * Height)];
|
||||
|
||||
// Does it really reads the frame?
|
||||
if (SHP.Data[x].HeaderImage.offset != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Now it checks the compression:
|
||||
if ((SHP.Data[x].HeaderImage.compression == 3))
|
||||
{
|
||||
// decode it
|
||||
// Compression 3
|
||||
NextOffset = FindNextOffsetFrom(SHP, x + 1, SHP.Header.NumImages);
|
||||
if (NextOffset != 0)
|
||||
{
|
||||
|
||||
ImageSize = NextOffset - SHP.Data[x].HeaderImage.offset;
|
||||
Databuffer = new byte[ImageSize];
|
||||
for (int i = 0; i < ImageSize; i++)
|
||||
{
|
||||
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
|
||||
Databuffer[i] = s.ReadUInt8();
|
||||
}
|
||||
SHP.Data[x].Databuffer = new byte[(SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy)];
|
||||
Decode3(Databuffer, ref SHP.Data[x].Databuffer, SHP.Data[x].HeaderImage.cx, SHP.Data[x].HeaderImage.cy, ref FileSize);
|
||||
|
||||
k = 0;
|
||||
l = 0;
|
||||
for (int i = 0; i < Height; i++)
|
||||
{
|
||||
erri = i;
|
||||
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
|
||||
{
|
||||
errj = j;
|
||||
errl = l;
|
||||
errk = k;
|
||||
arroff = i + j + l;
|
||||
|
||||
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
|
||||
cp = 0;
|
||||
else
|
||||
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
|
||||
|
||||
FData[i + j + k] = cp;
|
||||
|
||||
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
|
||||
l = l + (SHP.Data[x].HeaderImage.cx - 1);
|
||||
|
||||
if (j == (Width - 1))
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
//FData = headers[x - 1].Image;
|
||||
k = 0;
|
||||
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
|
||||
{
|
||||
for (int j = 0; j < Width; j++)
|
||||
{
|
||||
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
|
||||
if (j == (Width - 1))
|
||||
{
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
ImageSize = 0;
|
||||
ImageSize = FileSize - SHP.Data[x].HeaderImage.offset;
|
||||
Databuffer = new byte[ImageSize];
|
||||
for (int i = 0; i < ImageSize; i++)
|
||||
{
|
||||
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
|
||||
Databuffer[i] = s.ReadUInt8();
|
||||
}
|
||||
SHP.Data[x].Databuffer = new byte[((SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy))];
|
||||
|
||||
Decode3(Databuffer, ref SHP.Data[x].Databuffer, SHP.Data[x].HeaderImage.cx, SHP.Data[x].HeaderImage.cy, ref ImageSize);
|
||||
|
||||
k = 0;
|
||||
l = 0;
|
||||
for (int i = 0; i < Height; i++)
|
||||
{
|
||||
erri = i;
|
||||
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
|
||||
{
|
||||
errj = j;
|
||||
errl = l;
|
||||
errk = k;
|
||||
arroff = i + j + l;
|
||||
|
||||
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
|
||||
cp = 0;
|
||||
else
|
||||
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
|
||||
|
||||
FData[i + j + k] = cp;
|
||||
|
||||
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
|
||||
l = l + (SHP.Data[x].HeaderImage.cx - 1);
|
||||
|
||||
if (j == (Width - 1))
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
//FData = headers[x - 1].Image;
|
||||
k = 0;
|
||||
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
|
||||
{
|
||||
for (int j = 0; j < Width; j++)
|
||||
{
|
||||
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
|
||||
if (j == (Width - 1))
|
||||
{
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((SHP.Data[x].HeaderImage.compression == 2))
|
||||
{
|
||||
NextOffset = FindNextOffsetFrom(SHP, x + 1, SHP.Header.NumImages);
|
||||
if (NextOffset != 0)
|
||||
{
|
||||
ImageSize = NextOffset - SHP.Data[x].HeaderImage.offset;
|
||||
SHP.Data[x].Databuffer = new byte[(SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy)];
|
||||
Databuffer = new byte[ImageSize];
|
||||
for (int i = 0; i < ImageSize; i++)
|
||||
{
|
||||
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
|
||||
Databuffer[i] = s.ReadUInt8();
|
||||
}
|
||||
|
||||
Decode2(Databuffer, ref SHP.Data[x].Databuffer, SHP.Data[x].HeaderImage.cx, SHP.Data[x].HeaderImage.cy, ref ImageSize);
|
||||
|
||||
k = 0;
|
||||
l = 0;
|
||||
for (int i = 0; i < Height; i++)
|
||||
{
|
||||
erri = i;
|
||||
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
|
||||
{
|
||||
errj = j;
|
||||
errl = l;
|
||||
errk = k;
|
||||
arroff = i + j + l;
|
||||
|
||||
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
|
||||
cp = 0;
|
||||
else
|
||||
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
|
||||
|
||||
FData[i + j + k] = cp;
|
||||
|
||||
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
|
||||
l = l + (SHP.Data[x].HeaderImage.cx - 1);
|
||||
|
||||
if (j == (Width - 1))
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
//FData = headers[x - 1].Image;
|
||||
k = 0;
|
||||
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
|
||||
{
|
||||
for (int j = 0; j < Width; j++)
|
||||
{
|
||||
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
|
||||
if (j == (Width - 1))
|
||||
{
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compression 2
|
||||
}
|
||||
else
|
||||
{
|
||||
ImageSize = 0;
|
||||
ImageSize = FileSize - SHP.Data[x].HeaderImage.offset;
|
||||
Databuffer = new byte[ImageSize];
|
||||
for (int i = 0; i < ImageSize; i++)
|
||||
{
|
||||
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
|
||||
Databuffer[i] = s.ReadUInt8();
|
||||
}
|
||||
SHP.Data[x].Databuffer = new byte[((SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy))];
|
||||
Decode2(Databuffer, ref SHP.Data[x].Databuffer, SHP.Data[x].HeaderImage.cx, SHP.Data[x].HeaderImage.cy, ref ImageSize);
|
||||
|
||||
k = 0;
|
||||
l = 0;
|
||||
for (int i = 0; i < Height; i++)
|
||||
{
|
||||
erri = i;
|
||||
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
|
||||
{
|
||||
errj = j;
|
||||
errl = l;
|
||||
errk = k;
|
||||
arroff = i + j + l;
|
||||
|
||||
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
|
||||
cp = 0;
|
||||
else
|
||||
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
|
||||
|
||||
FData[i + j + k] = cp;
|
||||
|
||||
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
|
||||
l = l + (SHP.Data[x].HeaderImage.cx - 1);
|
||||
|
||||
if (j == (Width - 1))
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
//FData = headers[x - 1].Image;
|
||||
k = 0;
|
||||
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
|
||||
{
|
||||
for (int j = 0; j < Width; j++)
|
||||
{
|
||||
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
|
||||
if (j == (Width - 1))
|
||||
{
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Compression 2
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compression 1
|
||||
ImageSize = (int)(SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy);
|
||||
Databuffer = new byte[ImageSize];
|
||||
for (int i = 0; i < ImageSize; i++)
|
||||
{
|
||||
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
|
||||
Databuffer[i] = s.ReadUInt8();
|
||||
}
|
||||
SHP.Data[x].Databuffer = new byte[(SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy)];
|
||||
SHP.Data[x].Databuffer = Databuffer;
|
||||
|
||||
k = 0;
|
||||
l = 0;
|
||||
for (int i = 0; i < Height; i++)
|
||||
{
|
||||
erri = i;
|
||||
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
|
||||
{
|
||||
errj = j;
|
||||
errl = l;
|
||||
errk = k;
|
||||
arroff = i + j + l;
|
||||
|
||||
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
|
||||
cp = 0;
|
||||
else
|
||||
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
|
||||
|
||||
FData[i + j + k] = cp;
|
||||
|
||||
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
|
||||
l = l + (SHP.Data[x].HeaderImage.cx - 1);
|
||||
|
||||
if (j == (Width - 1))
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
//FData = headers[x - 1].Image;
|
||||
k = 0;
|
||||
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
|
||||
{
|
||||
for (int j = 0; j < Width; j++)
|
||||
{
|
||||
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
|
||||
if (j == (Width - 1))
|
||||
{
|
||||
k = k + (Width - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
}
|
||||
// Set the shp's databuffer to the result after decompression
|
||||
}
|
||||
//Width = Width2;
|
||||
//Height = Height2;
|
||||
}
|
||||
|
||||
public HeaderImage this[int index]
|
||||
{
|
||||
get { return headers[index]; }
|
||||
}
|
||||
|
||||
public static void ReInterpretWordFromBytes(byte Byte1, byte Byte2, ref ushort FullValue)
|
||||
{
|
||||
FullValue = (ushort)((Byte2 * 256) + Byte1);
|
||||
}
|
||||
|
||||
public static void ReInterpretWordFromBytes(byte Byte1, byte Byte2, ref uint FullValue)
|
||||
{
|
||||
FullValue = (uint)((Byte2 * 256) + Byte1);
|
||||
}
|
||||
|
||||
// Compression 3:
|
||||
public static void Decode3(byte[] Source, ref byte[] Dest, int cx, int cy, ref int max)
|
||||
{
|
||||
int SP;
|
||||
int DP;
|
||||
int x;
|
||||
int y;
|
||||
int Count;
|
||||
int v;
|
||||
int maxdp;
|
||||
ushort Pos;
|
||||
maxdp = cx * cy;
|
||||
SP = 0;
|
||||
DP = 0;
|
||||
Pos = 0;
|
||||
try
|
||||
{
|
||||
for (y = 1; y <= cy; y++)
|
||||
{
|
||||
ReInterpretWordFromBytes(Source[SP], Source[SP + 1], ref Pos);
|
||||
|
||||
Count = Pos - 2;
|
||||
|
||||
SP = SP + 2;
|
||||
|
||||
x = 0;
|
||||
while (Count > 0)
|
||||
{
|
||||
Count = Count - 1;
|
||||
if ((SP > max) || (DP > maxdp))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// SP has reached max value, exit
|
||||
v = Source[SP];
|
||||
SP++;
|
||||
if (v != 0)
|
||||
{
|
||||
if ((SP > max) || (DP > maxdp))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
x++;
|
||||
Dest[DP] += (byte)v;
|
||||
}
|
||||
DP++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Count -= 1;
|
||||
v = Source[SP];
|
||||
|
||||
SP++;
|
||||
if ((x + v) > cx)
|
||||
{
|
||||
v = cx - x;
|
||||
}
|
||||
x = x + v;
|
||||
while (v > 0)
|
||||
{
|
||||
if ((SP > max) || (DP > maxdp))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
v -= 1;
|
||||
Dest[DP] = 0;
|
||||
|
||||
}
|
||||
DP++;
|
||||
// SP has reached max value, exit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((SP >= max) || (DP >= maxdp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// SP has reached max value, exit
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void Decode2(byte[] Source, ref byte[] Dest, int cx, int cy, ref int max)
|
||||
{
|
||||
int SP;
|
||||
int DP;
|
||||
int y;
|
||||
int Count;
|
||||
int maxdp;
|
||||
ushort Pos;
|
||||
maxdp = cx * cy;
|
||||
SP = 0;
|
||||
DP = 0;
|
||||
Pos = 0;
|
||||
try
|
||||
{
|
||||
for (y = 1; y <= cy; y++)
|
||||
{
|
||||
ReInterpretWordFromBytes(Source[SP], Source[SP + 1], ref Pos);
|
||||
Count = Pos - 2;
|
||||
SP += 2;
|
||||
while (Count > 0)
|
||||
{
|
||||
Count -= 1;
|
||||
if ((SP > max) || (DP > maxdp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// SP has reached max value, exit
|
||||
Dest[DP] = Source[SP];
|
||||
SP++;
|
||||
DP++;
|
||||
}
|
||||
if ((SP >= max) || (DP >= maxdp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// SP has reached max value, exit
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<HeaderImage> GetEnumerator()
|
||||
{
|
||||
return headers.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public Size Size { get { return new Size(Width, Height); } }
|
||||
}
|
||||
}
|
||||
@@ -24,5 +24,12 @@ namespace OpenRA.FileFormats.Graphics
|
||||
this.u = uv.X; this.v = uv.Y;
|
||||
this.p = pc.X; this.c = pc.Y;
|
||||
}
|
||||
|
||||
public Vertex(float[] xyz, float2 uv, float2 pc)
|
||||
{
|
||||
this.x = xyz[0]; this.y = xyz[1]; this.z = xyz[2];
|
||||
this.u = uv.X; this.v = uv.Y;
|
||||
this.p = pc.X; this.c = pc.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,67 +46,66 @@ namespace OpenRA.FileFormats
|
||||
public byte[] AudioData { get { return audioData; } }
|
||||
public int CurrentFrame { get { return currentFrame; } }
|
||||
|
||||
public VqaReader( Stream stream )
|
||||
public VqaReader(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
BinaryReader reader = new BinaryReader( stream );
|
||||
|
||||
// Decode FORM chunk
|
||||
if (new String(reader.ReadChars(4)) != "FORM")
|
||||
if (stream.ReadASCII(4) != "FORM")
|
||||
throw new InvalidDataException("Invalid vqa (invalid FORM section)");
|
||||
/*var length = */ reader.ReadUInt32();
|
||||
/*var length = */ stream.ReadUInt32();
|
||||
|
||||
if (new String(reader.ReadChars(8)) != "WVQAVQHD")
|
||||
if (stream.ReadASCII(8) != "WVQAVQHD")
|
||||
throw new InvalidDataException("Invalid vqa (not WVQAVQHD)");
|
||||
/* var length = */reader.ReadUInt32();
|
||||
/* var length = */stream.ReadUInt32();
|
||||
|
||||
/*var version = */reader.ReadUInt16();
|
||||
/*var flags = */reader.ReadUInt16();
|
||||
Frames = reader.ReadUInt16();
|
||||
Width = reader.ReadUInt16();
|
||||
Height = reader.ReadUInt16();
|
||||
/*var version = */stream.ReadUInt16();
|
||||
/*var flags = */stream.ReadUInt16();
|
||||
Frames = stream.ReadUInt16();
|
||||
Width = stream.ReadUInt16();
|
||||
Height = stream.ReadUInt16();
|
||||
|
||||
blockWidth = reader.ReadByte();
|
||||
blockHeight = reader.ReadByte();
|
||||
Framerate = reader.ReadByte();
|
||||
cbParts = reader.ReadByte();
|
||||
blockWidth = stream.ReadUInt8();
|
||||
blockHeight = stream.ReadUInt8();
|
||||
Framerate = stream.ReadUInt8();
|
||||
cbParts = stream.ReadUInt8();
|
||||
blocks = new int2(Width / blockWidth, Height / blockHeight);
|
||||
|
||||
numColors = reader.ReadUInt16();
|
||||
/*var maxBlocks = */reader.ReadUInt16();
|
||||
/*var unknown1 = */reader.ReadUInt16();
|
||||
/*var unknown2 = */reader.ReadUInt32();
|
||||
numColors = stream.ReadUInt16();
|
||||
/*var maxBlocks = */stream.ReadUInt16();
|
||||
/*var unknown1 = */stream.ReadUInt16();
|
||||
/*var unknown2 = */stream.ReadUInt32();
|
||||
|
||||
// Audio
|
||||
/*var freq = */reader.ReadUInt16();
|
||||
/*var channels = */reader.ReadByte();
|
||||
/*var bits = */reader.ReadByte();
|
||||
/*var unknown3 = */reader.ReadChars(14);
|
||||
/*var freq = */stream.ReadUInt16();
|
||||
/*var channels = */stream.ReadByte();
|
||||
/*var bits = */stream.ReadByte();
|
||||
/*var unknown3 = */stream.ReadBytes(14);
|
||||
|
||||
|
||||
var frameSize = Exts.NextPowerOf2(Math.Max(Width,Height));
|
||||
var frameSize = Exts.NextPowerOf2(Math.Max(Width, Height));
|
||||
cbf = new byte[Width*Height];
|
||||
cbp = new byte[Width*Height];
|
||||
palette = new uint[numColors];
|
||||
origData = new byte[2*blocks.X*blocks.Y];
|
||||
frameData = new uint[frameSize,frameSize];
|
||||
frameData = new uint[frameSize, frameSize];
|
||||
|
||||
var type = new String(reader.ReadChars(4));
|
||||
var type = stream.ReadASCII(4);
|
||||
if (type != "FINF")
|
||||
{
|
||||
reader.ReadBytes(27);
|
||||
type = new String(reader.ReadChars(4));
|
||||
stream.Seek(27, SeekOrigin.Current);
|
||||
type = stream.ReadASCII(4);
|
||||
}
|
||||
|
||||
/*var length = */reader.ReadUInt16();
|
||||
/*var unknown4 = */reader.ReadUInt16();
|
||||
/*var length = */stream.ReadUInt16();
|
||||
/*var unknown4 = */stream.ReadUInt16();
|
||||
|
||||
// Frame offsets
|
||||
offsets = new UInt32[Frames];
|
||||
for (int i = 0; i < Frames; i++)
|
||||
{
|
||||
offsets[i] = reader.ReadUInt32();
|
||||
if (offsets[i] > 0x40000000) offsets[i] -= 0x40000000;
|
||||
offsets[i] = stream.ReadUInt32();
|
||||
if (offsets[i] > 0x40000000)
|
||||
offsets[i] -= 0x40000000;
|
||||
offsets[i] <<= 1;
|
||||
}
|
||||
|
||||
@@ -130,28 +129,28 @@ namespace OpenRA.FileFormats
|
||||
for (var i = 0; i < Frames; i++)
|
||||
{
|
||||
stream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
var end = (i < Frames - 1) ? offsets[i + 1] : stream.Length;
|
||||
|
||||
while (reader.BaseStream.Position < end)
|
||||
while (stream.Position < end)
|
||||
{
|
||||
var type = new String(reader.ReadChars(4));
|
||||
var length = int2.Swap(reader.ReadUInt32());
|
||||
var type = stream.ReadASCII(4);
|
||||
var length = int2.Swap(stream.ReadUInt32());
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "SND0":
|
||||
case "SND2":
|
||||
var rawAudio = reader.ReadBytes((int)length);
|
||||
var rawAudio = stream.ReadBytes((int)length);
|
||||
ms.Write(rawAudio);
|
||||
compressed = (type == "SND2");
|
||||
break;
|
||||
default:
|
||||
reader.ReadBytes((int)length);
|
||||
stream.ReadBytes((int)length);
|
||||
break;
|
||||
}
|
||||
|
||||
if (reader.PeekChar() == 0) reader.ReadByte();
|
||||
// Chunks are aligned on even bytes; advance by a byte if the next one is null
|
||||
if (stream.Peek() == 0) stream.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,48 +170,47 @@ namespace OpenRA.FileFormats
|
||||
|
||||
// Seek to the start of the frame
|
||||
stream.Seek(offsets[currentFrame], SeekOrigin.Begin);
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
var end = (currentFrame < Frames - 1) ? offsets[currentFrame+1] : stream.Length;
|
||||
|
||||
while(reader.BaseStream.Position < end)
|
||||
while (stream.Position < end)
|
||||
{
|
||||
var type = new String(reader.ReadChars(4));
|
||||
var length = int2.Swap(reader.ReadUInt32());
|
||||
var type = stream.ReadASCII(4);
|
||||
var length = int2.Swap(stream.ReadUInt32());
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case "VQFR":
|
||||
DecodeVQFR(reader);
|
||||
DecodeVQFR(stream);
|
||||
break;
|
||||
default:
|
||||
// Don't parse sound here.
|
||||
reader.ReadBytes((int)length);
|
||||
stream.ReadBytes((int)length);
|
||||
break;
|
||||
}
|
||||
|
||||
// Chunks are aligned on even bytes; advance by a byte if the next one is null
|
||||
if (reader.PeekChar() == 0) reader.ReadByte();
|
||||
if (stream.Peek() == 0) stream.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
// VQA Frame
|
||||
public void DecodeVQFR(BinaryReader reader)
|
||||
public void DecodeVQFR(Stream s)
|
||||
{
|
||||
while(true)
|
||||
while (true)
|
||||
{
|
||||
// Chunks are aligned on even bytes; may be padded with a single null
|
||||
if (reader.PeekChar() == 0) reader.ReadByte();
|
||||
var type = new String(reader.ReadChars(4));
|
||||
int subchunkLength = (int)int2.Swap(reader.ReadUInt32());
|
||||
if (s.Peek() == 0) s.ReadByte();
|
||||
var type = s.ReadASCII(4);
|
||||
int subchunkLength = (int)int2.Swap(s.ReadUInt32());
|
||||
|
||||
switch(type)
|
||||
{
|
||||
// Full frame-modifier
|
||||
case "CBFZ":
|
||||
Format80.DecodeInto( reader.ReadBytes(subchunkLength), cbf );
|
||||
Format80.DecodeInto(s.ReadBytes(subchunkLength), cbf);
|
||||
break;
|
||||
case "CBF0":
|
||||
cbf = reader.ReadBytes(subchunkLength);
|
||||
cbf = s.ReadBytes(subchunkLength);
|
||||
break;
|
||||
|
||||
// frame-modifier chunk
|
||||
@@ -224,12 +222,12 @@ namespace OpenRA.FileFormats
|
||||
if (type == "CBP0")
|
||||
cbf = (byte[])cbp.Clone();
|
||||
else
|
||||
Format80.DecodeInto( cbp, cbf );
|
||||
Format80.DecodeInto(cbp, cbf);
|
||||
|
||||
cbOffset = cbChunk = 0;
|
||||
}
|
||||
|
||||
var bytes = reader.ReadBytes(subchunkLength);
|
||||
var bytes = s.ReadBytes(subchunkLength);
|
||||
bytes.CopyTo(cbp,cbOffset);
|
||||
cbOffset += subchunkLength;
|
||||
cbChunk++;
|
||||
@@ -239,16 +237,16 @@ namespace OpenRA.FileFormats
|
||||
case "CPL0":
|
||||
for (int i = 0; i < numColors; i++)
|
||||
{
|
||||
byte r = (byte)(reader.ReadByte() << 2);
|
||||
byte g = (byte)(reader.ReadByte() << 2);
|
||||
byte b = (byte)(reader.ReadByte() << 2);
|
||||
byte r = (byte)(s.ReadUInt8() << 2);
|
||||
byte g = (byte)(s.ReadUInt8() << 2);
|
||||
byte b = (byte)(s.ReadUInt8() << 2);
|
||||
palette[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
break;
|
||||
|
||||
// Frame data
|
||||
case "VPTZ":
|
||||
Format80.DecodeInto( reader.ReadBytes(subchunkLength), origData );
|
||||
Format80.DecodeInto(s.ReadBytes(subchunkLength), origData);
|
||||
// This is the last subchunk
|
||||
return;
|
||||
default:
|
||||
|
||||
158
OpenRA.FileFormats/Graphics/VxlReader.cs
Normal file
158
OpenRA.FileFormats/Graphics/VxlReader.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public enum NormalType { TiberianSun = 2, RedAlert2 = 4 }
|
||||
public class VxlElement
|
||||
{
|
||||
public byte Color;
|
||||
public byte Normal;
|
||||
}
|
||||
|
||||
public class VxlLimb
|
||||
{
|
||||
public string Name;
|
||||
public float Scale;
|
||||
public float[] Bounds;
|
||||
public byte[] Size;
|
||||
public NormalType Type;
|
||||
|
||||
public uint VoxelCount;
|
||||
public Dictionary<byte, VxlElement>[,] VoxelMap;
|
||||
}
|
||||
|
||||
public class VxlReader
|
||||
{
|
||||
public readonly uint LimbCount;
|
||||
public VxlLimb[] Limbs;
|
||||
|
||||
uint BodySize;
|
||||
|
||||
void ReadVoxelData(Stream s, VxlLimb l)
|
||||
{
|
||||
var baseSize = l.Size[0]*l.Size[1];
|
||||
var colStart = new int[baseSize];
|
||||
for (var i = 0; i < baseSize; i++)
|
||||
colStart[i] = s.ReadInt32();
|
||||
s.Seek(4*baseSize, SeekOrigin.Current);
|
||||
var dataStart = s.Position;
|
||||
|
||||
// Count the voxels in this limb
|
||||
l.VoxelCount = 0;
|
||||
for (var i = 0; i < baseSize; i++)
|
||||
{
|
||||
// Empty column
|
||||
if (colStart[i] == -1)
|
||||
continue;
|
||||
|
||||
s.Seek(dataStart + colStart[i], SeekOrigin.Begin);
|
||||
var z = 0;
|
||||
do
|
||||
{
|
||||
z += s.ReadUInt8();
|
||||
var count = s.ReadUInt8();
|
||||
z += count;
|
||||
l.VoxelCount += count;
|
||||
s.Seek(2*count + 1, SeekOrigin.Current);
|
||||
} while (z < l.Size[2]);
|
||||
}
|
||||
|
||||
// Read the data
|
||||
l.VoxelMap = new Dictionary<byte, VxlElement>[l.Size[0],l.Size[1]];
|
||||
for (var i = 0; i < baseSize; i++)
|
||||
{
|
||||
// Empty column
|
||||
if (colStart[i] == -1)
|
||||
continue;
|
||||
|
||||
s.Seek(dataStart + colStart[i], SeekOrigin.Begin);
|
||||
|
||||
byte x = (byte)(i % l.Size[0]);
|
||||
byte y = (byte)(i / l.Size[0]);
|
||||
byte z = 0;
|
||||
l.VoxelMap[x,y] = new Dictionary<byte, VxlElement>();
|
||||
do
|
||||
{
|
||||
z += s.ReadUInt8();
|
||||
var count = s.ReadUInt8();
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
var v = new VxlElement();
|
||||
v.Color = s.ReadUInt8();
|
||||
v.Normal = s.ReadUInt8();
|
||||
|
||||
l.VoxelMap[x,y].Add(z, v);
|
||||
z++;
|
||||
}
|
||||
// Skip duplicate count
|
||||
s.ReadUInt8();
|
||||
} while (z < l.Size[2]);
|
||||
}
|
||||
}
|
||||
|
||||
public VxlReader(Stream s)
|
||||
{
|
||||
|
||||
if (!s.ReadASCII(16).StartsWith("Voxel Animation"))
|
||||
throw new InvalidDataException("Invalid vxl header");
|
||||
|
||||
s.ReadUInt32();
|
||||
LimbCount = s.ReadUInt32();
|
||||
s.ReadUInt32();
|
||||
BodySize = s.ReadUInt32();
|
||||
s.Seek(770, SeekOrigin.Current);
|
||||
|
||||
// Read Limb headers
|
||||
Limbs = new VxlLimb[LimbCount];
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
Limbs[i] = new VxlLimb();
|
||||
Limbs[i].Name = s.ReadASCII(16);
|
||||
s.Seek(12, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
// Skip to the Limb footers
|
||||
s.Seek(802 + 28*LimbCount + BodySize, SeekOrigin.Begin);
|
||||
|
||||
var LimbDataOffset = new uint[LimbCount];
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
LimbDataOffset[i] = s.ReadUInt32();
|
||||
s.Seek(8, SeekOrigin.Current);
|
||||
Limbs[i].Scale = s.ReadFloat();
|
||||
s.Seek(48, SeekOrigin.Current);
|
||||
|
||||
Limbs[i].Bounds = new float[6];
|
||||
for (var j = 0; j < 6; j++)
|
||||
Limbs[i].Bounds[j] = s.ReadFloat();
|
||||
Limbs[i].Size = s.ReadBytes(3);
|
||||
Limbs[i].Type = (NormalType)s.ReadByte();
|
||||
}
|
||||
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
s.Seek(802 + 28*LimbCount + LimbDataOffset[i], SeekOrigin.Begin);
|
||||
ReadVoxelData(s, Limbs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static VxlReader Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return new VxlReader(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
@@ -18,9 +19,12 @@ namespace OpenRA.FileFormats
|
||||
public class Manifest
|
||||
{
|
||||
public readonly string[]
|
||||
Mods, Folders, Packages, Rules, ServerTraits,
|
||||
Sequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||
Weapons, Voices, Notifications, Music, Movies, TileSets, ChromeMetrics;
|
||||
Mods, Folders, Rules, ServerTraits,
|
||||
Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||
Weapons, Voices, Notifications, Music, Movies, TileSets,
|
||||
ChromeMetrics, PackageContents;
|
||||
|
||||
public readonly Dictionary<string, string> Packages;
|
||||
public readonly MiniYaml LoadScreen;
|
||||
public readonly Dictionary<string, Pair<string,int>> Fonts;
|
||||
public readonly int TileSize = 24;
|
||||
@@ -29,15 +33,16 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
Mods = mods;
|
||||
var yaml = new MiniYaml(null, mods
|
||||
.Select(m => MiniYaml.FromFile("mods/" + m + "/mod.yaml"))
|
||||
.Select(m => MiniYaml.FromFile("mods{0}{1}{0}mod.yaml".F(Path.DirectorySeparatorChar, m)))
|
||||
.Aggregate(MiniYaml.MergeLiberal)).NodesDict;
|
||||
|
||||
// TODO: Use fieldloader
|
||||
Folders = YamlList(yaml, "Folders");
|
||||
Packages = YamlList(yaml, "Packages");
|
||||
Packages = yaml["Packages"].NodesDict.ToDictionary(x => x.Key, x => x.Value.Value);
|
||||
Rules = YamlList(yaml, "Rules");
|
||||
ServerTraits = YamlList(yaml, "ServerTraits");
|
||||
Sequences = YamlList(yaml, "Sequences");
|
||||
VoxelSequences = YamlList(yaml, "VoxelSequences");
|
||||
Cursors = YamlList(yaml, "Cursors");
|
||||
Chrome = YamlList(yaml, "Chrome");
|
||||
Assemblies = YamlList(yaml, "Assemblies");
|
||||
@@ -49,6 +54,7 @@ namespace OpenRA.FileFormats
|
||||
Movies = YamlList(yaml, "Movies");
|
||||
TileSets = YamlList(yaml, "TileSets");
|
||||
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
||||
PackageContents = YamlList(yaml, "PackageContents");
|
||||
|
||||
LoadScreen = yaml["LoadScreen"];
|
||||
Fonts = yaml["Fonts"].NodesDict.ToDictionary(x => x.Key,
|
||||
|
||||
@@ -39,17 +39,17 @@ namespace OpenRA.FileFormats
|
||||
public bool PickAny;
|
||||
public string Category;
|
||||
|
||||
[FieldLoader.LoadUsing( "LoadTiles" )]
|
||||
[FieldLoader.LoadUsing("LoadTiles")]
|
||||
public Dictionary<byte, string> Tiles = new Dictionary<byte, string>();
|
||||
|
||||
public TileTemplate() {}
|
||||
public TileTemplate(MiniYaml my) { FieldLoader.Load( this, my ); }
|
||||
public TileTemplate(MiniYaml my) { FieldLoader.Load(this, my); }
|
||||
|
||||
static object LoadTiles( MiniYaml y )
|
||||
static object LoadTiles(MiniYaml y)
|
||||
{
|
||||
return y.NodesDict["Tiles"].NodesDict.ToDictionary(
|
||||
t => byte.Parse(t.Key),
|
||||
t => t.Value.Value );
|
||||
t => t.Value.Value);
|
||||
}
|
||||
|
||||
static readonly string[] Fields = { "Id", "Image", "Size", "PickAny" };
|
||||
@@ -60,12 +60,14 @@ namespace OpenRA.FileFormats
|
||||
foreach (var field in Fields)
|
||||
{
|
||||
FieldInfo f = this.GetType().GetField(field);
|
||||
if (f.GetValue(this) == null) continue;
|
||||
root.Add( new MiniYamlNode( field, FieldSaver.FormatValue( this, f ) ) );
|
||||
if (f.GetValue(this) == null)
|
||||
continue;
|
||||
|
||||
root.Add(new MiniYamlNode(field, FieldSaver.FormatValue(this, f)));
|
||||
}
|
||||
|
||||
root.Add( new MiniYamlNode( "Tiles", null,
|
||||
Tiles.Select( x => new MiniYamlNode( x.Key.ToString(), x.Value ) ).ToList() ) );
|
||||
root.Add(new MiniYamlNode("Tiles", null,
|
||||
Tiles.Select(x => new MiniYamlNode(x.Key.ToString(), x.Value)).ToList()));
|
||||
|
||||
return new MiniYaml(null, root);
|
||||
}
|
||||
@@ -90,9 +92,9 @@ namespace OpenRA.FileFormats
|
||||
|
||||
public TileSet() {}
|
||||
|
||||
public TileSet( string filepath )
|
||||
public TileSet(string filepath)
|
||||
{
|
||||
var yaml = MiniYaml.DictFromFile( filepath );
|
||||
var yaml = MiniYaml.DictFromFile(filepath);
|
||||
|
||||
// General info
|
||||
FieldLoader.Load(this, yaml["General"]);
|
||||
@@ -110,7 +112,7 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
foreach (var t in Templates)
|
||||
if (t.Value.Data == null)
|
||||
using( var s = FileSystem.OpenWithExts(t.Value.Image, Extensions) )
|
||||
using (var s = FileSystem.OpenWithExts(t.Value.Image, Extensions))
|
||||
t.Value.Data = new Terrain(s, TileSize);
|
||||
}
|
||||
|
||||
@@ -122,33 +124,39 @@ namespace OpenRA.FileFormats
|
||||
foreach (var field in fields)
|
||||
{
|
||||
FieldInfo f = this.GetType().GetField(field);
|
||||
if (f.GetValue(this) == null) continue;
|
||||
gen.Add( new MiniYamlNode( field, FieldSaver.FormatValue( this, f ) ) );
|
||||
if (f.GetValue(this) == null)
|
||||
continue;
|
||||
|
||||
gen.Add(new MiniYamlNode(field, FieldSaver.FormatValue(this, f)));
|
||||
}
|
||||
|
||||
root.Add( new MiniYamlNode( "General", null, gen ) );
|
||||
root.Add(new MiniYamlNode("General", null, gen));
|
||||
|
||||
root.Add( new MiniYamlNode( "Terrain", null,
|
||||
Terrain.Select( t => new MiniYamlNode(
|
||||
"TerrainType@{0}".F( t.Value.Type ),
|
||||
t.Value.Save() ) ).ToList() ) );
|
||||
root.Add(new MiniYamlNode( "Terrain", null,
|
||||
Terrain.Select(t => new MiniYamlNode(
|
||||
"TerrainType@{0}".F(t.Value.Type),
|
||||
t.Value.Save())).ToList()));
|
||||
|
||||
root.Add( new MiniYamlNode( "Templates", null,
|
||||
Templates.Select( t => new MiniYamlNode(
|
||||
"Template@{0}".F( t.Value.Id ),
|
||||
t.Value.Save() ) ).ToList() ) );
|
||||
root.Add(new MiniYamlNode("Templates", null,
|
||||
Templates.Select(t => new MiniYamlNode(
|
||||
"Template@{0}".F(t.Value.Id),
|
||||
t.Value.Save())).ToList()));
|
||||
root.WriteToFile(filepath);
|
||||
}
|
||||
|
||||
public byte[] GetBytes(TileReference<ushort,byte> r)
|
||||
{
|
||||
TileTemplate tile;
|
||||
if( Templates.TryGetValue( r.type, out tile ) )
|
||||
return tile.Data.TileBitmapBytes[ r.index ];
|
||||
if (Templates.TryGetValue(r.type, out tile))
|
||||
{
|
||||
var data = tile.Data.TileBitmapBytes[r.index];
|
||||
if (data != null)
|
||||
return data;
|
||||
}
|
||||
|
||||
byte[] missingTile = new byte[ TileSize * TileSize ];
|
||||
for( int i = 0 ; i < missingTile.Length ; i++ )
|
||||
missingTile[ i ] = 0x36;
|
||||
byte[] missingTile = new byte[TileSize*TileSize];
|
||||
for (var i = 0; i < missingTile.Length; i++)
|
||||
missingTile[i] = 0x00;
|
||||
|
||||
return missingTile;
|
||||
}
|
||||
@@ -159,6 +167,7 @@ namespace OpenRA.FileFormats
|
||||
string ret;
|
||||
if (!tt.TryGetValue(r.index, out ret))
|
||||
return "Clear"; // Default walkable
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
<Compile Include="FileFormats\Blast.cs" />
|
||||
<Compile Include="FileFormats\Blowfish.cs" />
|
||||
<Compile Include="FileFormats\BlowfishKeyProvider.cs" />
|
||||
<Compile Include="FileFormats\CRC32.cs" />
|
||||
<Compile Include="FileFormats\Format2.cs" />
|
||||
<Compile Include="FileFormats\Format40.cs" />
|
||||
<Compile Include="FileFormats\Format80.cs" />
|
||||
@@ -137,6 +138,14 @@
|
||||
<Compile Include="WRot.cs" />
|
||||
<Compile Include="WRange.cs" />
|
||||
<Compile Include="HSLColor.cs" />
|
||||
<Compile Include="Graphics\ShpTSReader.cs" />
|
||||
<Compile Include="FileFormats\XccLocalDatabase.cs" />
|
||||
<Compile Include="FileFormats\XccGlobalDatabase.cs" />
|
||||
<Compile Include="Graphics\VxlReader.cs" />
|
||||
<Compile Include="Graphics\HvaReader.cs" />
|
||||
<Compile Include="StreamExts.cs" />
|
||||
<Compile Include="FileFormats\WavLoader.cs" />
|
||||
<Compile Include="Filesystem\D2kSoundResources.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
@@ -8,19 +8,21 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public enum PackageHashType { Classic, CRC32 }
|
||||
|
||||
public class PackageEntry
|
||||
{
|
||||
public readonly uint Hash;
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
|
||||
public PackageEntry(uint hash, uint offset, uint length)
|
||||
{
|
||||
Hash = hash;
|
||||
@@ -28,11 +30,11 @@ namespace OpenRA.FileFormats
|
||||
Length = length;
|
||||
}
|
||||
|
||||
public PackageEntry(BinaryReader r)
|
||||
public PackageEntry(Stream s)
|
||||
{
|
||||
Hash = r.ReadUInt32();
|
||||
Offset = r.ReadUInt32();
|
||||
Length = r.ReadUInt32();
|
||||
Hash = s.ReadUInt32();
|
||||
Offset = s.ReadUInt32();
|
||||
Length = s.ReadUInt32();
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
@@ -51,33 +53,55 @@ namespace OpenRA.FileFormats
|
||||
return "0x{0:x8} - offset 0x{1:x8} - length 0x{2:x8}".F(Hash, Offset, Length);
|
||||
}
|
||||
|
||||
public static uint HashFilename(string name)
|
||||
public static uint HashFilename(string name, PackageHashType type)
|
||||
{
|
||||
if (name.Length > 12)
|
||||
name = name.Substring(0, 12);
|
||||
switch(type)
|
||||
{
|
||||
case PackageHashType.Classic:
|
||||
{
|
||||
name = name.ToUpperInvariant();
|
||||
if (name.Length % 4 != 0)
|
||||
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
|
||||
|
||||
name = name.ToUpperInvariant();
|
||||
if (name.Length % 4 != 0)
|
||||
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
|
||||
MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(name));
|
||||
BinaryReader reader = new BinaryReader(ms);
|
||||
|
||||
MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(name));
|
||||
BinaryReader reader = new BinaryReader(ms);
|
||||
int len = name.Length >> 2;
|
||||
uint result = 0;
|
||||
|
||||
int len = name.Length >> 2;
|
||||
uint result = 0;
|
||||
while (len-- != 0)
|
||||
result = ((result << 1) | (result >> 31)) + reader.ReadUInt32();
|
||||
|
||||
while (len-- != 0)
|
||||
result = ((result << 1) | (result >> 31)) + reader.ReadUInt32();
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
case PackageHashType.CRC32:
|
||||
{
|
||||
name = name.ToUpperInvariant();
|
||||
var l = name.Length;
|
||||
int a = l >> 2;
|
||||
if ((l & 3) != 0)
|
||||
{
|
||||
name += (char)(l - (a << 2));
|
||||
int i = 3 - (l & 3);
|
||||
while (i-- != 0)
|
||||
name += name[a << 2];
|
||||
}
|
||||
return CRC32.Calculate(Encoding.ASCII.GetBytes(name));
|
||||
}
|
||||
|
||||
default: throw new NotImplementedException("Unknown hash type `{0}`".F(type));
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<uint, string> Names = new Dictionary<uint,string>();
|
||||
|
||||
public static void AddStandardName(string s)
|
||||
{
|
||||
uint hash = HashFilename(s);
|
||||
uint hash = HashFilename(s, PackageHashType.Classic); // RA1 and TD
|
||||
Names.Add(hash, s);
|
||||
uint crcHash = HashFilename(s, PackageHashType.CRC32); // TS
|
||||
Names.Add(crcHash, s);
|
||||
}
|
||||
|
||||
public const int Size = 12;
|
||||
|
||||
138
OpenRA.FileFormats/StreamExts.cs
Executable file
138
OpenRA.FileFormats/StreamExts.cs
Executable file
@@ -0,0 +1,138 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public static class StreamExts
|
||||
{
|
||||
public static byte[] ReadBytes(this Stream s, int count)
|
||||
{
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count", "Non-negative number required.");
|
||||
|
||||
var buf = new byte[count];
|
||||
if (s.Read(buf, 0, count) < count)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static int Peek(this Stream s)
|
||||
{
|
||||
var buf = new byte[1];
|
||||
if (s.Read(buf, 0, 1) == 0)
|
||||
return -1;
|
||||
|
||||
s.Seek(s.Position - 1, SeekOrigin.Begin);
|
||||
return buf[0];
|
||||
}
|
||||
|
||||
public static byte ReadUInt8(this Stream s)
|
||||
{
|
||||
return s.ReadBytes(1)[0];
|
||||
}
|
||||
|
||||
public static ushort ReadUInt16(this Stream s)
|
||||
{
|
||||
return BitConverter.ToUInt16(s.ReadBytes(2), 0);
|
||||
}
|
||||
|
||||
public static short ReadInt16(this Stream s)
|
||||
{
|
||||
return BitConverter.ToInt16(s.ReadBytes(2), 0);
|
||||
}
|
||||
|
||||
public static uint ReadUInt32(this Stream s)
|
||||
{
|
||||
return BitConverter.ToUInt32(s.ReadBytes(4), 0);
|
||||
}
|
||||
|
||||
public static int ReadInt32(this Stream s)
|
||||
{
|
||||
return BitConverter.ToInt32(s.ReadBytes(4), 0);
|
||||
}
|
||||
|
||||
public static float ReadFloat(this Stream s)
|
||||
{
|
||||
return BitConverter.ToSingle(s.ReadBytes(4), 0);
|
||||
}
|
||||
|
||||
public static double ReadDouble(this Stream s)
|
||||
{
|
||||
return BitConverter.ToDouble(s.ReadBytes(8), 0);
|
||||
}
|
||||
|
||||
public static string ReadASCII(this Stream s, int length)
|
||||
{
|
||||
return new string(Encoding.ASCII.GetChars(s.ReadBytes(length)));
|
||||
}
|
||||
|
||||
public static string ReadASCIIZ(this Stream s)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
var buf = new byte[1];
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (s.Read(buf, 0, 1) < 1)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
if (buf[0] == 0)
|
||||
break;
|
||||
|
||||
bytes.Add(buf[0]);
|
||||
}
|
||||
|
||||
return new string(Encoding.ASCII.GetChars(bytes.ToArray()));
|
||||
}
|
||||
|
||||
public static string ReadAllText(this Stream s)
|
||||
{
|
||||
using (s)
|
||||
using (var sr = new StreamReader(s))
|
||||
return sr.ReadToEnd();
|
||||
}
|
||||
|
||||
public static byte[] ReadAllBytes(this Stream s)
|
||||
{
|
||||
using (s)
|
||||
{
|
||||
var data = new byte[s.Length - s.Position];
|
||||
s.Read(data, 0, data.Length);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this Stream s, byte[] data)
|
||||
{
|
||||
s.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ReadAllLines(this Stream s)
|
||||
{
|
||||
using (var sr = new StreamReader(s))
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
var line = sr.ReadLine();
|
||||
if (line == null)
|
||||
yield break;
|
||||
else
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,12 +66,11 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public Shroud.ActorVisibility Sight;
|
||||
|
||||
[Sync] public Player Owner;
|
||||
|
||||
Activity currentActivity;
|
||||
public Group Group;
|
||||
public int Generation;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict )
|
||||
{
|
||||
@@ -106,15 +105,6 @@ namespace OpenRA
|
||||
return TraitsImplementing<IAutoSelectionSize>().Select(x => x.SelectionSize(this)).FirstOrDefault();
|
||||
});
|
||||
|
||||
if (this.HasTrait<RevealsShroud>())
|
||||
{
|
||||
Sight = new Shroud.ActorVisibility
|
||||
{
|
||||
range = this.Trait<RevealsShroud>().RevealRange,
|
||||
vis = Shroud.GetVisOrigins(this).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
ApplyIRender = (x, wr) => x.Render(this, wr);
|
||||
ApplyRenderModifier = (m, p, wr) => p.ModifyRender(this, wr, m);
|
||||
|
||||
@@ -127,12 +117,7 @@ namespace OpenRA
|
||||
Bounds.Invalidate();
|
||||
ExtendedBounds.Invalidate();
|
||||
|
||||
currentActivity = Traits.Util.RunActivity( this, currentActivity );
|
||||
}
|
||||
|
||||
public void UpdateSight()
|
||||
{
|
||||
Sight.vis = Shroud.GetVisOrigins(this).ToArray();
|
||||
currentActivity = Traits.Util.RunActivity(this, currentActivity);
|
||||
}
|
||||
|
||||
public bool IsIdle
|
||||
@@ -143,9 +128,9 @@ namespace OpenRA
|
||||
OpenRA.FileFormats.Lazy<int2> Size;
|
||||
|
||||
// note: these delegates are cached to avoid massive allocation.
|
||||
Func<IRender, WorldRenderer, IEnumerable<Renderable>> ApplyIRender;
|
||||
Func<IEnumerable<Renderable>, IRenderModifier, WorldRenderer, IEnumerable<Renderable>> ApplyRenderModifier;
|
||||
public IEnumerable<Renderable> Render(WorldRenderer wr)
|
||||
Func<IRender, WorldRenderer, IEnumerable<IRenderable>> ApplyIRender;
|
||||
Func<IEnumerable<IRenderable>, IRenderModifier, WorldRenderer, IEnumerable<IRenderable>> ApplyRenderModifier;
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
var mods = TraitsImplementing<IRenderModifier>();
|
||||
var sprites = TraitsImplementing<IRender>().SelectMany(x => ApplyIRender(x, wr));
|
||||
@@ -272,6 +257,7 @@ namespace OpenRA
|
||||
// momentarily remove from world so the ownership queries don't get confused
|
||||
w.Remove(this);
|
||||
Owner = newOwner;
|
||||
Generation++;
|
||||
w.Add(this);
|
||||
|
||||
foreach (var t in this.TraitsImplementing<INotifyOwnerChanged>())
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace OpenRA
|
||||
public int2 ToInt2() { return new int2(X, Y); }
|
||||
public PPos ToPPos() { return new PPos(Game.CellSize * X, Game.CellSize * Y); }
|
||||
|
||||
public WPos CenterPosition { get { return new WPos(1024*X + 512, 1024*Y + 512, 0); } }
|
||||
|
||||
public CPos Clamp(Rectangle r)
|
||||
{
|
||||
return new CPos(Math.Min(r.Right, Math.Max(X, r.Left)),
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace OpenRA
|
||||
|
||||
public float2 ToFloat2() { return new float2(X, Y); }
|
||||
public int2 ToInt2() { return new int2(X, Y); }
|
||||
public WVec ToWVec() { return new WVec(X*1024, Y*1024, 0); }
|
||||
|
||||
public CVec Clamp(Rectangle r)
|
||||
{
|
||||
|
||||
@@ -32,6 +32,6 @@ namespace OpenRA.Effects
|
||||
world.AddFrameEndTask(w => { w.Remove(this); a(); });
|
||||
}
|
||||
|
||||
public IEnumerable<Renderable> Render(WorldRenderer wr) { yield break; }
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr) { yield break; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace OpenRA.Effects
|
||||
world.AddFrameEndTask(w => w.Remove(this));
|
||||
}
|
||||
|
||||
public IEnumerable<Renderable> Render(WorldRenderer wr)
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
if (!target.IsInWorld)
|
||||
yield break;
|
||||
|
||||
@@ -17,6 +17,6 @@ namespace OpenRA.Effects
|
||||
public interface IEffect
|
||||
{
|
||||
void Tick(World world);
|
||||
IEnumerable<Renderable> Render(WorldRenderer r);
|
||||
IEnumerable<IRenderable> Render(WorldRenderer r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,17 +122,17 @@ namespace OpenRA
|
||||
public static void RunAfterTick(Action a) { delayedActions.Add(a); }
|
||||
public static void RunAfterDelay(int delay, Action a) { delayedActions.Add(a, delay); }
|
||||
|
||||
static void Tick( OrderManager orderManager, Viewport viewPort )
|
||||
static void Tick(OrderManager orderManager, Viewport viewPort)
|
||||
{
|
||||
if (orderManager.Connection.ConnectionState != lastConnectionState)
|
||||
{
|
||||
lastConnectionState = orderManager.Connection.ConnectionState;
|
||||
ConnectionStateChanged( orderManager );
|
||||
ConnectionStateChanged(orderManager);
|
||||
}
|
||||
|
||||
Tick( orderManager );
|
||||
if( worldRenderer != null && orderManager.world != worldRenderer.world )
|
||||
Tick( worldRenderer.world.orderManager );
|
||||
Tick(orderManager);
|
||||
if (worldRenderer != null && orderManager.world != worldRenderer.world)
|
||||
Tick(worldRenderer.world.orderManager);
|
||||
|
||||
using (new PerfSample("render"))
|
||||
{
|
||||
@@ -149,12 +149,12 @@ namespace OpenRA
|
||||
delayedActions.PerformActions();
|
||||
}
|
||||
|
||||
static void Tick( OrderManager orderManager )
|
||||
static void Tick(OrderManager orderManager)
|
||||
{
|
||||
int t = Environment.TickCount;
|
||||
int dt = t - orderManager.LastTickTime;
|
||||
if (dt >= Settings.Game.Timestep)
|
||||
using( new PerfSample( "tick_time" ) )
|
||||
using (new PerfSample("tick_time"))
|
||||
{
|
||||
orderManager.LastTickTime += Settings.Game.Timestep;
|
||||
Ui.Tick();
|
||||
@@ -162,7 +162,7 @@ namespace OpenRA
|
||||
if (orderManager.GameStarted)
|
||||
++Viewport.TicksSinceLastMove;
|
||||
Sound.Tick();
|
||||
Sync.CheckSyncUnchanged( world, () => { orderManager.TickImmediate(); } );
|
||||
Sync.CheckSyncUnchanged(world, orderManager.TickImmediate);
|
||||
|
||||
if (world != null)
|
||||
{
|
||||
@@ -230,7 +230,7 @@ namespace OpenRA
|
||||
{
|
||||
get
|
||||
{
|
||||
var client= orderManager.LobbyInfo.ClientWithIndex (
|
||||
var client= orderManager.LobbyInfo.ClientWithIndex(
|
||||
orderManager.Connection.LocalClientId);
|
||||
return ((client!=null) && client.IsAdmin);
|
||||
}
|
||||
@@ -238,7 +238,7 @@ namespace OpenRA
|
||||
|
||||
public static Dictionary<String, Mod> CurrentMods
|
||||
{
|
||||
get { return Mod.AllMods.Where( k => modData.Manifest.Mods.Contains( k.Key )).ToDictionary( k => k.Key, k => k.Value ); }
|
||||
get { return Mod.AllMods.Where(k => modData.Manifest.Mods.Contains(k.Key)).ToDictionary(k => k.Key, k => k.Value); }
|
||||
}
|
||||
|
||||
static Modifiers modifiers;
|
||||
@@ -311,10 +311,9 @@ namespace OpenRA
|
||||
Sound.StopVideo();
|
||||
Sound.Initialize();
|
||||
|
||||
modData = new ModData( mm );
|
||||
modData = new ModData(mm);
|
||||
Renderer.InitializeFonts(modData.Manifest);
|
||||
modData.LoadInitialAssets(true);
|
||||
|
||||
modData.InitializeLoaders();
|
||||
|
||||
PerfHistory.items["render"].hasNormalTick = false;
|
||||
PerfHistory.items["batches"].hasNormalTick = false;
|
||||
@@ -331,11 +330,11 @@ namespace OpenRA
|
||||
Game.Settings.Server.Map = WidgetUtils.ChooseInitialMap(Game.Settings.Server.Map);
|
||||
Game.Settings.Save();
|
||||
Game.CreateServer(new ServerSettings(Game.Settings.Server));
|
||||
while(true)
|
||||
while (true)
|
||||
{
|
||||
System.Threading.Thread.Sleep(100);
|
||||
|
||||
if((server.State == Server.ServerState.GameStarted)
|
||||
if ((server.State == Server.ServerState.GameStarted)
|
||||
&& (server.conns.Count<=1))
|
||||
{
|
||||
Console.WriteLine("No one is playing, shutting down...");
|
||||
@@ -426,7 +425,7 @@ namespace OpenRA
|
||||
|
||||
public static T CreateObject<T>( string name )
|
||||
{
|
||||
return modData.ObjectCreator.CreateObject<T>( name );
|
||||
return modData.ObjectCreator.CreateObject<T>(name);
|
||||
}
|
||||
|
||||
public static void CreateServer(ServerSettings settings)
|
||||
@@ -459,23 +458,6 @@ namespace OpenRA
|
||||
return orderManager != null && orderManager.world == world;
|
||||
}
|
||||
|
||||
public static void JoinExternalGame()
|
||||
{
|
||||
var addressParts = Game.Settings.Game.ConnectTo.Split(
|
||||
new [] { ':' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (addressParts.Length < 1 || addressParts.Length > 2)
|
||||
return;
|
||||
|
||||
var host = addressParts[0];
|
||||
var port = Exts.WithDefault(1234, () => int.Parse(addressParts[1]));
|
||||
|
||||
Game.Settings.Game.ConnectTo = "";
|
||||
Game.Settings.Save();
|
||||
|
||||
Game.JoinServer(host, port);
|
||||
}
|
||||
|
||||
public static bool DownloadMap(string mapHash)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -46,9 +46,9 @@ namespace OpenRA
|
||||
|
||||
static Dictionary<string, T> LoadYamlRules<T>(string[] files, List<MiniYamlNode> dict, Func<MiniYamlNode, Dictionary<string, MiniYaml>, T> f)
|
||||
{
|
||||
var y = files.Select(a => MiniYaml.FromFile(a)).Aggregate(dict,MiniYaml.MergeLiberal);
|
||||
var yy = y.ToDictionary( x => x.Key, x => x.Value );
|
||||
return y.ToDictionary(kv => kv.Key.ToLowerInvariant(), kv => f(kv, yy));
|
||||
var y = files.Select(MiniYaml.FromFile).Aggregate(dict, MiniYaml.MergeLiberal);
|
||||
var yy = y.ToDictionary(x => x.Key, x => x.Value);
|
||||
return y.ToDictionaryWithConflictLog(kv => kv.Key.ToLowerInvariant(), kv => f(kv, yy), "LoadYamlRules", null, null);
|
||||
}
|
||||
|
||||
public static IEnumerable<KeyValuePair<string,MusicInfo>> InstalledMusic { get { return Music.Where( m => m.Value.Exists ); } }
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace OpenRA.GameRules
|
||||
public bool VerboseNatDiscovery = false; // print very detailed logs for debugging
|
||||
public bool AllowCheats = false;
|
||||
public string Map = null;
|
||||
public string[] Ban = null;
|
||||
public string[] Ban = { };
|
||||
public int TimeOut = 0;
|
||||
public bool Dedicated = false;
|
||||
public bool DedicatedLoop = true;
|
||||
@@ -77,6 +77,7 @@ namespace OpenRA.GameRules
|
||||
public bool SanityCheckUnsyncedCode = false;
|
||||
public int Samples = 25;
|
||||
public bool IgnoreVersionMismatch = false;
|
||||
public bool DeveloperMenu = false;
|
||||
}
|
||||
|
||||
public class GraphicSettings
|
||||
@@ -130,7 +131,6 @@ namespace OpenRA.GameRules
|
||||
// Internal game settings
|
||||
public int Timestep = 40;
|
||||
|
||||
public string ConnectTo = "";
|
||||
public bool AllowDownloading = true;
|
||||
public string MapRepository = "http://content.open-ra.org/map/";
|
||||
}
|
||||
@@ -151,6 +151,7 @@ namespace OpenRA.GameRules
|
||||
public string ScatterKey = "x";
|
||||
public string DeployKey = "f";
|
||||
public string StanceCycleKey = "z";
|
||||
public string GuardKey = "d";
|
||||
|
||||
public string CycleTabsKey = "tab";
|
||||
}
|
||||
|
||||
@@ -24,15 +24,13 @@ namespace OpenRA.Graphics
|
||||
|
||||
public string Name { get { return name; } }
|
||||
|
||||
public Animation( string name )
|
||||
: this( name, () => 0 )
|
||||
{
|
||||
}
|
||||
public Animation(string name)
|
||||
: this(name, () => 0) {}
|
||||
|
||||
public Animation( string name, Func<int> facingFunc )
|
||||
public Animation(string name, Func<int> facingFunc)
|
||||
{
|
||||
this.name = name.ToLowerInvariant();
|
||||
this.tickFunc = () => { };
|
||||
this.tickFunc = () => {};
|
||||
this.facingFunc = facingFunc;
|
||||
}
|
||||
|
||||
@@ -46,12 +44,12 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public void Play( string sequenceName )
|
||||
public void Play(string sequenceName)
|
||||
{
|
||||
PlayThen(sequenceName, null);
|
||||
}
|
||||
|
||||
public void PlayRepeating( string sequenceName )
|
||||
public void PlayRepeating(string sequenceName)
|
||||
{
|
||||
backwards = false;
|
||||
tickAlways = false;
|
||||
@@ -75,16 +73,16 @@ namespace OpenRA.Graphics
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PlayThen( string sequenceName, Action after )
|
||||
public void PlayThen(string sequenceName, Action after)
|
||||
{
|
||||
backwards = false;
|
||||
tickAlways = false;
|
||||
CurrentSequence = SequenceProvider.GetSequence( name, sequenceName );
|
||||
CurrentSequence = SequenceProvider.GetSequence(name, sequenceName);
|
||||
frame = 0;
|
||||
tickFunc = () =>
|
||||
{
|
||||
++frame;
|
||||
if( frame >= CurrentSequence.Length )
|
||||
if (frame >= CurrentSequence.Length)
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = () => { };
|
||||
@@ -99,11 +97,11 @@ namespace OpenRA.Graphics
|
||||
backwards = true;
|
||||
}
|
||||
|
||||
public void PlayFetchIndex( string sequenceName, Func<int> func )
|
||||
public void PlayFetchIndex(string sequenceName, Func<int> func)
|
||||
{
|
||||
backwards = false;
|
||||
tickAlways = true;
|
||||
CurrentSequence = SequenceProvider.GetSequence( name, sequenceName );
|
||||
CurrentSequence = SequenceProvider.GetSequence(name, sequenceName);
|
||||
frame = func();
|
||||
tickFunc = () => frame = func();
|
||||
}
|
||||
@@ -113,19 +111,19 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
Tick( 40 ); // tick one frame
|
||||
Tick(40); // tick one frame
|
||||
}
|
||||
|
||||
public bool HasSequence(string seq) { return SequenceProvider.HasSequence( name, seq ); }
|
||||
public bool HasSequence(string seq) { return SequenceProvider.HasSequence(name, seq); }
|
||||
|
||||
public void Tick( int t )
|
||||
public void Tick(int t)
|
||||
{
|
||||
if( tickAlways )
|
||||
if (tickAlways)
|
||||
tickFunc();
|
||||
else
|
||||
{
|
||||
timeUntilNextFrame -= t;
|
||||
while( timeUntilNextFrame <= 0 )
|
||||
while (timeUntilNextFrame <= 0)
|
||||
{
|
||||
tickFunc();
|
||||
timeUntilNextFrame += CurrentSequence != null ? CurrentSequence.Tick : 40; // 25 fps == 40 ms
|
||||
@@ -145,9 +143,9 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public Sequence GetSequence( string sequenceName )
|
||||
public Sequence GetSequence(string sequenceName)
|
||||
{
|
||||
return SequenceProvider.GetSequence( name, sequenceName );
|
||||
return SequenceProvider.GetSequence(name, sequenceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,36 +15,43 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public class AnimationWithOffset
|
||||
{
|
||||
public Animation Animation;
|
||||
public Func<WorldRenderer, float2> OffsetFunc;
|
||||
public Func<bool> DisableFunc;
|
||||
public int ZOffset;
|
||||
public readonly Animation Animation;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<WPos, int> ZOffset;
|
||||
|
||||
public AnimationWithOffset(Animation a)
|
||||
: this(a, null, null)
|
||||
{
|
||||
}
|
||||
public AnimationWithOffset(Animation a, Func<WVec> offset, Func<bool> disable)
|
||||
: this(a, offset, disable, null) { }
|
||||
|
||||
public AnimationWithOffset(Animation a, Func<WorldRenderer, float2> o, Func<bool> d)
|
||||
public AnimationWithOffset(Animation a, Func<WVec> offset, Func<bool> disable, int zOffset)
|
||||
: this(a, offset, disable, _ => zOffset) { }
|
||||
|
||||
public AnimationWithOffset(Animation a, Func<WVec> offset, Func<bool> disable, Func<WPos, int> zOffset)
|
||||
{
|
||||
this.Animation = a;
|
||||
this.OffsetFunc = o;
|
||||
this.DisableFunc = d;
|
||||
this.OffsetFunc = offset;
|
||||
this.DisableFunc = disable;
|
||||
this.ZOffset = zOffset;
|
||||
}
|
||||
|
||||
public Renderable Image(Actor self, WorldRenderer wr, PaletteReference pal)
|
||||
public IRenderable Image(Actor self, WorldRenderer wr, PaletteReference pal)
|
||||
{
|
||||
var p = self.CenterLocation;
|
||||
var loc = p.ToFloat2() - 0.5f * Animation.Image.size
|
||||
+ (OffsetFunc != null ? OffsetFunc(wr) : float2.Zero);
|
||||
var r = new Renderable(Animation.Image, loc, pal, p.Y);
|
||||
return Image(self, wr, pal, 1f);
|
||||
}
|
||||
|
||||
return ZOffset != 0 ? r.WithZOffset(ZOffset) : r;
|
||||
public IRenderable Image(Actor self, WorldRenderer wr, PaletteReference pal, float scale)
|
||||
{
|
||||
var p = self.CenterPosition;
|
||||
if (OffsetFunc != null)
|
||||
p += OffsetFunc();
|
||||
|
||||
var z = (ZOffset != null) ? ZOffset(p) : 0;
|
||||
return new SpriteRenderable(Animation.Image, p, z, pal, scale);
|
||||
}
|
||||
|
||||
public static implicit operator AnimationWithOffset(Animation a)
|
||||
{
|
||||
return new AnimationWithOffset(a);
|
||||
return new AnimationWithOffset(a, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
68
OpenRA.Game/Graphics/BeamRenderable.cs
Normal file
68
OpenRA.Game/Graphics/BeamRenderable.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct BeamRenderable : IRenderable
|
||||
{
|
||||
readonly WPos pos;
|
||||
readonly int zOffset;
|
||||
readonly WVec length;
|
||||
readonly Color color;
|
||||
readonly float width;
|
||||
|
||||
public BeamRenderable(WPos pos, int zOffset, WVec length, float width, Color color)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.zOffset = zOffset;
|
||||
this.length = length;
|
||||
this.color = color;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos; } }
|
||||
public float Scale { get { return 1f; } }
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
|
||||
public IRenderable WithScale(float newScale) { return new BeamRenderable(pos, zOffset, length, width, color); }
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new BeamRenderable(pos, zOffset, length, width, color); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new BeamRenderable(pos, zOffset, length, width, color); }
|
||||
public IRenderable WithPos(WPos pos) { return new BeamRenderable(pos, zOffset, length, width, color); }
|
||||
|
||||
public void BeforeRender(WorldRenderer wr) {}
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
var wlr = Game.Renderer.WorldLineRenderer;
|
||||
var src = wr.ScreenPosition(pos);
|
||||
var dest = wr.ScreenPosition(pos + length);
|
||||
|
||||
var lineWidth = wlr.LineWidth;
|
||||
if (lineWidth != width)
|
||||
{
|
||||
wlr.Flush();
|
||||
wlr.LineWidth = width;
|
||||
}
|
||||
|
||||
wlr.DrawLine(src, dest, color, color);
|
||||
|
||||
if (lineWidth != width)
|
||||
{
|
||||
wlr.Flush();
|
||||
wlr.LineWidth = lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr) {}
|
||||
}
|
||||
}
|
||||
105
OpenRA.Game/Graphics/ContrailRenderable.cs
Normal file
105
OpenRA.Game/Graphics/ContrailRenderable.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct ContrailRenderable : IRenderable
|
||||
{
|
||||
readonly World world;
|
||||
|
||||
// Store trail positions in a circular buffer
|
||||
readonly WPos[] trail;
|
||||
int next;
|
||||
int length;
|
||||
int skip;
|
||||
|
||||
readonly Color color;
|
||||
readonly int zOffset;
|
||||
|
||||
public ContrailRenderable(World world, Color color, int length, int skip, int zOffset)
|
||||
: this(world, new WPos[length], 0, 0, skip, color, zOffset) {}
|
||||
|
||||
ContrailRenderable(World world, WPos[] trail, int next, int length, int skip, Color color, int zOffset)
|
||||
{
|
||||
this.world = world;
|
||||
this.trail = trail;
|
||||
this.next = next;
|
||||
this.length = length;
|
||||
this.skip = skip;
|
||||
this.color = color;
|
||||
this.zOffset = zOffset;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return trail[idx(next-1)]; } }
|
||||
public float Scale { get { return 1f; } }
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
|
||||
public IRenderable WithScale(float newScale) { return new ContrailRenderable(world, (WPos[])trail.Clone(), next, length, skip, color, zOffset); }
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new ContrailRenderable(world, (WPos[])trail.Clone(), next, length, skip, color, zOffset); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new ContrailRenderable(world, (WPos[])trail.Clone(), next, length, skip, color, newOffset); }
|
||||
public IRenderable WithPos(WPos pos) { return new ContrailRenderable(world, (WPos[])trail.Clone(), next, length, skip, color, zOffset); }
|
||||
|
||||
public void BeforeRender(WorldRenderer wr) {}
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
// Need at least 4 points to smooth the contrail over
|
||||
if (length - skip < 4 )
|
||||
return;
|
||||
|
||||
// Start of the first line segment is the tail of the list - don't smooth it.
|
||||
var curPos = trail[idx(next - skip - 1)];
|
||||
var curCell = new CPos(curPos);
|
||||
var curColor = color;
|
||||
for (var i = 0; i < length - skip - 4; i++)
|
||||
{
|
||||
var j = next - skip - i - 2;
|
||||
var nextPos = WPos.Average(trail[idx(j)], trail[idx(j-1)], trail[idx(j-2)], trail[idx(j-3)]);
|
||||
var nextCell = new CPos(nextPos);
|
||||
var nextColor = Exts.ColorLerp(i * 1f / (length - 4), color, Color.Transparent);
|
||||
|
||||
if (!world.FogObscures(curCell) && !world.FogObscures(nextCell))
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(wr.ScreenPosition(curPos), wr.ScreenPosition(nextPos), curColor, nextColor);
|
||||
|
||||
curPos = nextPos;
|
||||
curCell = nextCell;
|
||||
curColor = nextColor;
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr) {}
|
||||
|
||||
// Array index modulo length
|
||||
int idx(int i)
|
||||
{
|
||||
var j = i % trail.Length;
|
||||
return j < 0 ? j + trail.Length : j;
|
||||
}
|
||||
|
||||
public void Update(WPos pos)
|
||||
{
|
||||
trail[next] = pos;
|
||||
next = idx(next+1);
|
||||
|
||||
if (length < trail.Length)
|
||||
length++;
|
||||
}
|
||||
|
||||
public static Color ChooseColor(Actor self)
|
||||
{
|
||||
var ownerColor = Color.FromArgb(255, self.Owner.Color.RGB);
|
||||
return Exts.ColorLerp(0.5f, ownerColor, Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,23 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public static class CursorProvider
|
||||
{
|
||||
static HardwarePalette Palette;
|
||||
static HardwarePalette palette;
|
||||
static Dictionary<string, CursorSequence> cursors;
|
||||
static Cache<string, PaletteReference> palettes;
|
||||
|
||||
static PaletteReference CreatePaletteReference(string name)
|
||||
{
|
||||
var pal = palette.GetPalette(name);
|
||||
if (pal == null)
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
|
||||
return new PaletteReference(name, palette.GetPaletteIndex(name), pal);
|
||||
}
|
||||
|
||||
public static void Initialize(string[] sequenceFiles)
|
||||
{
|
||||
cursors = new Dictionary<string, CursorSequence>();
|
||||
palettes = new Cache<string, PaletteReference>(CreatePaletteReference);
|
||||
var sequences = new MiniYaml(null, sequenceFiles.Select(s => MiniYaml.FromFile(s)).Aggregate(MiniYaml.MergeLiberal));
|
||||
int[] ShadowIndex = { };
|
||||
|
||||
@@ -35,14 +46,14 @@ namespace OpenRA.Graphics
|
||||
int.TryParse(sequences.NodesDict["ShadowIndex"].Value, out ShadowIndex[ShadowIndex.Length - 1]);
|
||||
}
|
||||
|
||||
Palette = new HardwarePalette();
|
||||
palette = new HardwarePalette();
|
||||
foreach (var p in sequences.NodesDict["Palettes"].Nodes)
|
||||
Palette.AddPalette(p.Key, new Palette(FileSystem.Open(p.Value.Value), ShadowIndex), false);
|
||||
palette.AddPalette(p.Key, new Palette(FileSystem.Open(p.Value.Value), ShadowIndex), false);
|
||||
|
||||
foreach (var s in sequences.NodesDict["Cursors"].Nodes)
|
||||
LoadSequencesForCursor(s.Key, s.Value);
|
||||
|
||||
Palette.Initialize();
|
||||
palette.Initialize();
|
||||
}
|
||||
|
||||
static void LoadSequencesForCursor(string cursorSrc, MiniYaml cursor)
|
||||
@@ -63,10 +74,10 @@ namespace OpenRA.Graphics
|
||||
var cursorSequence = GetCursorSequence(cursorName);
|
||||
var cursorSprite = cursorSequence.GetSprite(cursorFrame);
|
||||
|
||||
renderer.SetPalette(Palette);
|
||||
renderer.SetPalette(palette);
|
||||
renderer.SpriteRenderer.DrawSprite(cursorSprite,
|
||||
lastMousePos - cursorSequence.Hotspot,
|
||||
Palette.GetPaletteIndex(cursorSequence.Palette),
|
||||
palettes[cursorSequence.Palette],
|
||||
cursorSprite.size);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ namespace OpenRA.Graphics
|
||||
Renderer renderer;
|
||||
IShader shader;
|
||||
|
||||
Vertex[] vertices = new Vertex[ Renderer.TempBufferSize ];
|
||||
Vertex[] vertices = new Vertex[Renderer.TempBufferSize];
|
||||
int nv = 0;
|
||||
|
||||
public LineRenderer( Renderer renderer, IShader shader )
|
||||
public LineRenderer(Renderer renderer, IShader shader)
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
@@ -32,49 +32,50 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
if( nv > 0 )
|
||||
if (nv > 0)
|
||||
{
|
||||
shader.Render( () =>
|
||||
renderer.Device.EnableAlphaBlending();
|
||||
shader.Render(() =>
|
||||
{
|
||||
var vb = renderer.GetTempVertexBuffer();
|
||||
vb.SetData( vertices, nv );
|
||||
vb.SetData(vertices, nv);
|
||||
renderer.SetLineWidth(LineWidth * Game.viewport.Zoom);
|
||||
renderer.DrawBatch( vb, 0, nv, PrimitiveType.LineList );
|
||||
} );
|
||||
|
||||
renderer.DrawBatch(vb, 0, nv, PrimitiveType.LineList);
|
||||
});
|
||||
renderer.Device.DisableAlphaBlending();
|
||||
nv = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawRect( float2 tl, float2 br, Color c )
|
||||
public void DrawRect(float2 tl, float2 br, Color c)
|
||||
{
|
||||
var tr = new float2( br.X, tl.Y );
|
||||
var bl = new float2( tl.X, br.Y );
|
||||
DrawLine( tl, tr, c, c );
|
||||
DrawLine( tl, bl, c, c );
|
||||
DrawLine( tr, br, c, c );
|
||||
DrawLine( bl, br, c, c );
|
||||
var tr = new float2(br.X, tl.Y);
|
||||
var bl = new float2(tl.X, br.Y);
|
||||
DrawLine(tl, tr, c, c);
|
||||
DrawLine(tl, bl, c, c);
|
||||
DrawLine(tr, br, c, c);
|
||||
DrawLine(bl, br, c, c);
|
||||
}
|
||||
|
||||
public void DrawLine( float2 start, float2 end, Color startColor, Color endColor )
|
||||
public void DrawLine(float2 start, float2 end, Color startColor, Color endColor)
|
||||
{
|
||||
Renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if( nv + 2 > Renderer.TempBufferSize )
|
||||
if (nv + 2 > Renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
vertices[ nv++ ] = new Vertex( start + offset,
|
||||
new float2( startColor.R / 255.0f, startColor.G / 255.0f ),
|
||||
new float2( startColor.B / 255.0f, startColor.A / 255.0f ) );
|
||||
vertices[nv++] = new Vertex(start + offset,
|
||||
new float2(startColor.R / 255.0f, startColor.G / 255.0f),
|
||||
new float2(startColor.B / 255.0f, startColor.A / 255.0f));
|
||||
|
||||
vertices[ nv++ ] = new Vertex( end + offset,
|
||||
new float2( endColor.R / 255.0f, endColor.G / 255.0f ),
|
||||
new float2( endColor.B / 255.0f, endColor.A / 255.0f ) );
|
||||
vertices[nv++] = new Vertex(end + offset,
|
||||
new float2(endColor.R / 255.0f, endColor.G / 255.0f),
|
||||
new float2(endColor.B / 255.0f, endColor.A / 255.0f));
|
||||
}
|
||||
|
||||
public void FillRect( RectangleF r, Color color )
|
||||
public void FillRect(RectangleF r, Color color)
|
||||
{
|
||||
for (float y = r.Top; y < r.Bottom; y++)
|
||||
for (var y = r.Top; y < r.Bottom; y++)
|
||||
DrawLine(new float2(r.Left, y), new float2(r.Right, y), color, color);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,14 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
if (nv > 0)
|
||||
{
|
||||
renderer.Device.EnableAlphaBlending();
|
||||
shader.Render(() =>
|
||||
{
|
||||
var vb = renderer.GetTempVertexBuffer();
|
||||
vb.SetData(vertices, nv);
|
||||
renderer.DrawBatch(vb, 0, nv, PrimitiveType.QuadList);
|
||||
});
|
||||
renderer.Device.DisableAlphaBlending();
|
||||
|
||||
nv = 0;
|
||||
}
|
||||
|
||||
91
OpenRA.Game/Graphics/Renderable.cs
Normal file
91
OpenRA.Game/Graphics/Renderable.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class RenderableComparer : IComparer<IRenderable>
|
||||
{
|
||||
WorldRenderer wr;
|
||||
public RenderableComparer(WorldRenderer wr)
|
||||
{
|
||||
this.wr = wr;
|
||||
}
|
||||
|
||||
public int Compare(IRenderable x, IRenderable y)
|
||||
{
|
||||
var xOrder = wr.ScreenZPosition(x.Pos, x.ZOffset);
|
||||
var yOrder = wr.ScreenZPosition(y.Pos, y.ZOffset);
|
||||
return xOrder.CompareTo(yOrder);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IRenderable
|
||||
{
|
||||
WPos Pos { get; }
|
||||
float Scale { get; }
|
||||
PaletteReference Palette { get; }
|
||||
int ZOffset { get; }
|
||||
|
||||
IRenderable WithScale(float newScale);
|
||||
IRenderable WithPalette(PaletteReference newPalette);
|
||||
IRenderable WithZOffset(int newOffset);
|
||||
IRenderable WithPos(WPos pos);
|
||||
void BeforeRender(WorldRenderer wr);
|
||||
void Render(WorldRenderer wr);
|
||||
void RenderDebugGeometry(WorldRenderer wr);
|
||||
}
|
||||
|
||||
public struct SpriteRenderable : IRenderable
|
||||
{
|
||||
readonly Sprite sprite;
|
||||
readonly WPos pos;
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
readonly float scale;
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, int zOffset, PaletteReference palette, float scale)
|
||||
{
|
||||
this.sprite = sprite;
|
||||
this.pos = pos;
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
// Provided for legacy support only - Don't use for new things!
|
||||
public SpriteRenderable(Sprite sprite, float2 pos, PaletteReference palette, int z)
|
||||
: this(sprite, new PPos((int)pos.X, (int)pos.Y).ToWPos(0), z, palette, 1f) { }
|
||||
|
||||
public WPos Pos { get { return pos; } }
|
||||
public float Scale { get { return scale; } }
|
||||
public PaletteReference Palette { get { return palette; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
|
||||
public IRenderable WithScale(float newScale) { return new SpriteRenderable(sprite, pos, zOffset, palette, newScale); }
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, zOffset, newPalette, scale); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, newOffset, palette, scale); }
|
||||
public IRenderable WithPos(WPos pos) { return new SpriteRenderable(sprite, pos, zOffset, palette, scale); }
|
||||
|
||||
public void BeforeRender(WorldRenderer wr) {}
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
sprite.DrawAt(wr.ScreenPxPosition(pos) - (0.5f*scale*sprite.size).ToInt2(), palette, scale);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
{
|
||||
var offset = wr.ScreenPxPosition(pos) - 0.5f*scale*sprite.size + sprite.offset;
|
||||
Game.Renderer.WorldLineRenderer.DrawRect(offset, offset + sprite.size, Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,10 @@ namespace OpenRA.Graphics
|
||||
internal static int TempBufferCount;
|
||||
|
||||
public SpriteRenderer WorldSpriteRenderer { get; private set; }
|
||||
public SpriteRenderer WorldRgbaSpriteRenderer { get; private set; }
|
||||
public QuadRenderer WorldQuadRenderer { get; private set; }
|
||||
public LineRenderer WorldLineRenderer { get; private set; }
|
||||
public VoxelRenderer WorldVoxelRenderer { get; private set; }
|
||||
public LineRenderer LineRenderer { get; private set; }
|
||||
public SpriteRenderer RgbaSpriteRenderer { get; private set; }
|
||||
public SpriteRenderer SpriteRenderer { get; private set; }
|
||||
@@ -45,7 +47,9 @@ namespace OpenRA.Graphics
|
||||
SheetSize = Game.Settings.Graphics.SheetSize;
|
||||
|
||||
WorldSpriteRenderer = new SpriteRenderer(this, device.CreateShader("shp"));
|
||||
WorldRgbaSpriteRenderer = new SpriteRenderer(this, device.CreateShader("rgba"));
|
||||
WorldLineRenderer = new LineRenderer(this, device.CreateShader("line"));
|
||||
WorldVoxelRenderer = new VoxelRenderer(this, device.CreateShader("vxl"));
|
||||
LineRenderer = new LineRenderer(this, device.CreateShader("line"));
|
||||
WorldQuadRenderer = new QuadRenderer(this, device.CreateShader("line"));
|
||||
RgbaSpriteRenderer = new SpriteRenderer(this, device.CreateShader("rgba"));
|
||||
@@ -66,11 +70,13 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
device.Clear();
|
||||
WorldSpriteRenderer.SetViewportParams(Resolution, zoom, scroll);
|
||||
WorldRgbaSpriteRenderer.SetViewportParams(Resolution, zoom, scroll);
|
||||
SpriteRenderer.SetViewportParams(Resolution, 1f, float2.Zero);
|
||||
RgbaSpriteRenderer.SetViewportParams(Resolution, 1f, float2.Zero);
|
||||
WorldLineRenderer.SetViewportParams(Resolution, zoom, scroll);
|
||||
WorldQuadRenderer.SetViewportParams(Resolution, zoom, scroll);
|
||||
LineRenderer.SetViewportParams(Resolution, 1f, float2.Zero);
|
||||
WorldVoxelRenderer.SetViewportParams(Resolution, zoom, scroll);
|
||||
}
|
||||
|
||||
ITexture currentPaletteTexture;
|
||||
@@ -85,6 +91,8 @@ namespace OpenRA.Graphics
|
||||
RgbaSpriteRenderer.SetPalette(currentPaletteTexture);
|
||||
SpriteRenderer.SetPalette(currentPaletteTexture);
|
||||
WorldSpriteRenderer.SetPalette(currentPaletteTexture);
|
||||
WorldRgbaSpriteRenderer.SetPalette(currentPaletteTexture);
|
||||
WorldVoxelRenderer.SetPalette(currentPaletteTexture);
|
||||
}
|
||||
|
||||
public void EndFrame(IInputHandler inputHandler)
|
||||
@@ -186,5 +194,17 @@ namespace OpenRA.Graphics
|
||||
Flush();
|
||||
Device.DisableScissor();
|
||||
}
|
||||
|
||||
public void EnableDepthBuffer()
|
||||
{
|
||||
Flush();
|
||||
Device.EnableDepthBuffer();
|
||||
}
|
||||
|
||||
public void DisableDepthBuffer()
|
||||
{
|
||||
Flush();
|
||||
Device.DisableDepthBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -19,6 +20,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
readonly Sprite[] sprites;
|
||||
readonly int start, length, stride, facings, tick;
|
||||
readonly bool reverseFacings, transpose;
|
||||
|
||||
public readonly string Name;
|
||||
public int Start { get { return start; } }
|
||||
@@ -33,10 +35,18 @@ namespace OpenRA.Graphics
|
||||
var srcOverride = info.Value;
|
||||
Name = name;
|
||||
var d = info.NodesDict;
|
||||
var offset = float2.Zero;
|
||||
|
||||
sprites = Game.modData.SpriteLoader.LoadAllSprites(srcOverride ?? unit);
|
||||
start = int.Parse(d["Start"].Value);
|
||||
|
||||
if (d.ContainsKey("Offset"))
|
||||
offset = FieldLoader.GetValue<float2>("Offset", d["Offset"].Value);
|
||||
|
||||
// Apply offset to each sprite in the sequence
|
||||
// Different sequences may apply different offsets to the same frame
|
||||
sprites = Game.modData.SpriteLoader.LoadAllSprites(srcOverride ?? unit).Select(
|
||||
s => new Sprite(s.sheet, s.bounds, s.offset + offset, s.channel)).ToArray();
|
||||
|
||||
if (!d.ContainsKey("Length"))
|
||||
length = 1;
|
||||
else if (d["Length"].Value == "*")
|
||||
@@ -49,16 +59,23 @@ namespace OpenRA.Graphics
|
||||
else
|
||||
stride = length;
|
||||
|
||||
if(d.ContainsKey("Facings"))
|
||||
facings = int.Parse(d["Facings"].Value);
|
||||
if (d.ContainsKey("Facings"))
|
||||
{
|
||||
var f = int.Parse(d["Facings"].Value);
|
||||
facings = Math.Abs(f);
|
||||
reverseFacings = f < 0;
|
||||
}
|
||||
else
|
||||
facings = 1;
|
||||
|
||||
if(d.ContainsKey("Tick"))
|
||||
if (d.ContainsKey("Tick"))
|
||||
tick = int.Parse(d["Tick"].Value);
|
||||
else
|
||||
tick = 40;
|
||||
|
||||
if (d.ContainsKey("Transpose"))
|
||||
transpose = bool.Parse(d["Transpose"].Value);
|
||||
|
||||
if (length > stride)
|
||||
throw new InvalidOperationException(
|
||||
"{0}: Sequence {1}.{2}: Length must be <= stride"
|
||||
@@ -71,15 +88,22 @@ namespace OpenRA.Graphics
|
||||
info.Nodes[0].Location));
|
||||
}
|
||||
|
||||
public Sprite GetSprite( int frame )
|
||||
public Sprite GetSprite(int frame)
|
||||
{
|
||||
return GetSprite( frame, 0 );
|
||||
return GetSprite(frame, 0);
|
||||
}
|
||||
|
||||
public Sprite GetSprite(int frame, int facing)
|
||||
{
|
||||
var f = Traits.Util.QuantizeFacing( facing, facings );
|
||||
return sprites[ (f * stride) + ( frame % length ) + start ];
|
||||
var f = Traits.Util.QuantizeFacing(facing, facings);
|
||||
|
||||
if (reverseFacings)
|
||||
f = (facings - f) % facings;
|
||||
|
||||
int i = transpose ? (frame % length) * facings + f :
|
||||
(f * stride) + (frame % length);
|
||||
|
||||
return sprites[start + i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileFormats.Graphics;
|
||||
|
||||
@@ -16,21 +18,52 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public class Sheet
|
||||
{
|
||||
Bitmap bitmap;
|
||||
ITexture texture;
|
||||
bool dirty;
|
||||
byte[] data;
|
||||
|
||||
public readonly Size Size;
|
||||
public byte[] Data { get { return data ?? texture.GetData(); } }
|
||||
|
||||
public Sheet(Size size)
|
||||
{
|
||||
Size = size;
|
||||
data = new byte[4*Size.Width*Size.Height];
|
||||
}
|
||||
|
||||
public Sheet(ITexture texture)
|
||||
{
|
||||
this.texture = texture;
|
||||
Size = texture.Size;
|
||||
}
|
||||
|
||||
public Sheet(string filename)
|
||||
{
|
||||
bitmap = (Bitmap)Image.FromStream(FileSystem.Open(filename));
|
||||
var bitmap = (Bitmap)Image.FromStream(FileSystem.Open(filename));
|
||||
Size = bitmap.Size;
|
||||
|
||||
data = new byte[4*Size.Width*Size.Height];
|
||||
var b = bitmap.LockBits(bitmap.Bounds(),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
|
||||
unsafe
|
||||
{
|
||||
int* c = (int*)b.Scan0;
|
||||
|
||||
for (var x = 0; x < Size.Width; x++)
|
||||
for (var y = 0; y < Size.Height; y++)
|
||||
{
|
||||
var i = 4*Size.Width*y + 4*x;
|
||||
|
||||
// Convert argb to bgra
|
||||
var argb = *(c + (y * b.Stride >> 2) + x);
|
||||
data[i++] = (byte)(argb >> 0);
|
||||
data[i++] = (byte)(argb >> 8);
|
||||
data[i++] = (byte)(argb >> 16);
|
||||
data[i++] = (byte)(argb >> 24);
|
||||
}
|
||||
}
|
||||
bitmap.UnlockBits(b);
|
||||
}
|
||||
|
||||
public ITexture Texture
|
||||
@@ -45,23 +78,69 @@ namespace OpenRA.Graphics
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
if (data != null)
|
||||
{
|
||||
texture.SetData(data, Size.Width, Size.Height);
|
||||
dirty = false;
|
||||
}
|
||||
else if (bitmap != null)
|
||||
{
|
||||
texture.SetData(bitmap);
|
||||
dirty = false;
|
||||
}
|
||||
texture.SetData(data, Size.Width, Size.Height);
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Data { get { if (data == null) data = new byte[4 * Size.Width * Size.Height]; return data; } }
|
||||
public void MakeDirty() { dirty = true; }
|
||||
public Bitmap AsBitmap()
|
||||
{
|
||||
var d = Data;
|
||||
var b = new Bitmap(Size.Width, Size.Height);
|
||||
var output = b.LockBits(new Rectangle(0, 0, Size.Width, Size.Height),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
|
||||
unsafe
|
||||
{
|
||||
int* c = (int*)output.Scan0;
|
||||
|
||||
for (var x = 0; x < Size.Width; x++)
|
||||
for (var y = 0; y < Size.Height; y++)
|
||||
{
|
||||
var i = 4*Size.Width*y + 4*x;
|
||||
|
||||
// Convert bgra to argb
|
||||
var argb = (d[i+3] << 24) | (d[i+2] << 16) | (d[i+1] << 8) | d[i];
|
||||
*(c + (y * output.Stride >> 2) + x) = argb;
|
||||
}
|
||||
}
|
||||
b.UnlockBits(output);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
public Bitmap AsBitmap(TextureChannel channel, Palette pal)
|
||||
{
|
||||
var d = Data;
|
||||
var b = new Bitmap(Size.Width, Size.Height);
|
||||
var output = b.LockBits(new Rectangle(0, 0, Size.Width, Size.Height),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
|
||||
unsafe
|
||||
{
|
||||
int* c = (int*)output.Scan0;
|
||||
|
||||
for (var x = 0; x < Size.Width; x++)
|
||||
for (var y = 0; y < Size.Height; y++)
|
||||
{
|
||||
var index = d[4*Size.Width*y + 4*x + (int)channel];
|
||||
*(c + (y * output.Stride >> 2) + x) = pal.GetColor(index).ToArgb();
|
||||
}
|
||||
}
|
||||
b.UnlockBits(output);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
public void CommitData()
|
||||
{
|
||||
if (data == null)
|
||||
throw new InvalidOperationException("Texture-wrappers are read-only");
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,68 +8,78 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class SheetOverflowException : Exception
|
||||
{
|
||||
public SheetOverflowException(string message)
|
||||
: base(message) {}
|
||||
}
|
||||
|
||||
public enum SheetType
|
||||
{
|
||||
Indexed = 1,
|
||||
DualIndexed = 2,
|
||||
BGRA = 4,
|
||||
}
|
||||
|
||||
public class SheetBuilder
|
||||
{
|
||||
internal SheetBuilder(TextureChannel ch)
|
||||
Sheet current;
|
||||
TextureChannel channel;
|
||||
SheetType type;
|
||||
int rowHeight = 0;
|
||||
Point p;
|
||||
Func<Sheet> allocateSheet;
|
||||
|
||||
public static Sheet AllocateSheet()
|
||||
{
|
||||
current = null;
|
||||
rowHeight = 0;
|
||||
channel = null;
|
||||
initialChannel = ch;
|
||||
return new Sheet(new Size(Renderer.SheetSize, Renderer.SheetSize));;
|
||||
}
|
||||
|
||||
internal SheetBuilder(SheetType t)
|
||||
: this(t, AllocateSheet) {}
|
||||
|
||||
internal SheetBuilder(SheetType t, Func<Sheet> allocateSheet)
|
||||
{
|
||||
channel = TextureChannel.Red;
|
||||
type = t;
|
||||
current = allocateSheet();
|
||||
this.allocateSheet = allocateSheet;
|
||||
}
|
||||
|
||||
public Sprite Add(byte[] src, Size size)
|
||||
{
|
||||
Sprite rect = Allocate(size);
|
||||
var rect = Allocate(size);
|
||||
Util.FastCopyIntoChannel(rect, src);
|
||||
current.CommitData();
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sprite Add(Size size, byte paletteIndex)
|
||||
{
|
||||
byte[] data = new byte[size.Width * size.Height];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
var data = new byte[size.Width * size.Height];
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
data[i] = paletteIndex;
|
||||
|
||||
return Add(data, size);
|
||||
}
|
||||
|
||||
Sheet NewSheet() { return new Sheet(new Size( Renderer.SheetSize, Renderer.SheetSize ) ); }
|
||||
|
||||
Sheet current = null;
|
||||
int rowHeight = 0;
|
||||
Point p;
|
||||
TextureChannel? channel = null;
|
||||
TextureChannel initialChannel;
|
||||
|
||||
TextureChannel? NextChannel(TextureChannel? t)
|
||||
TextureChannel? NextChannel(TextureChannel t)
|
||||
{
|
||||
if (t == null)
|
||||
return initialChannel;
|
||||
var nextChannel = (int)t + (int)type;
|
||||
if (nextChannel > (int)TextureChannel.Alpha)
|
||||
return null;
|
||||
|
||||
switch (t.Value)
|
||||
{
|
||||
case TextureChannel.Red: return TextureChannel.Green;
|
||||
case TextureChannel.Green: return TextureChannel.Blue;
|
||||
case TextureChannel.Blue: return TextureChannel.Alpha;
|
||||
case TextureChannel.Alpha: return null;
|
||||
|
||||
default: return null;
|
||||
}
|
||||
return (TextureChannel)nextChannel;
|
||||
}
|
||||
|
||||
public Sprite Allocate(Size imageSize)
|
||||
public Sprite Allocate(Size imageSize) { return Allocate(imageSize, float2.Zero); }
|
||||
public Sprite Allocate(Size imageSize, float2 spriteOffset)
|
||||
{
|
||||
if (current == null)
|
||||
{
|
||||
current = NewSheet();
|
||||
channel = NextChannel(null);
|
||||
}
|
||||
|
||||
if (imageSize.Width + p.X > current.Size.Width)
|
||||
{
|
||||
p = new Point(0, p.Y + rowHeight);
|
||||
@@ -81,22 +91,25 @@ namespace OpenRA.Graphics
|
||||
|
||||
if (p.Y + imageSize.Height > current.Size.Height)
|
||||
{
|
||||
|
||||
if (null == (channel = NextChannel(channel)))
|
||||
var next = NextChannel(channel);
|
||||
if (next == null)
|
||||
{
|
||||
current = NewSheet();
|
||||
channel = NextChannel(channel);
|
||||
current = allocateSheet();
|
||||
channel = TextureChannel.Red;
|
||||
}
|
||||
else
|
||||
channel = next.Value;
|
||||
|
||||
rowHeight = imageSize.Height;
|
||||
p = new Point(0,0);
|
||||
}
|
||||
|
||||
Sprite rect = new Sprite(current, new Rectangle(p, imageSize), channel.Value);
|
||||
current.MakeDirty();
|
||||
var rect = new Sprite(current, new Rectangle(p, imageSize), spriteOffset, channel);
|
||||
p.X += imageSize.Width;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sheet Current { get { return current; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,14 +185,14 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
s[starti, j].DrawAt(
|
||||
Game.CellSize * new float2(starti, j),
|
||||
pal.Index,
|
||||
pal,
|
||||
new float2(Game.CellSize * (i - starti), Game.CellSize));
|
||||
starti = i + 1;
|
||||
}
|
||||
|
||||
s[i, j].DrawAt(
|
||||
Game.CellSize * new float2(i, j),
|
||||
pal.Index);
|
||||
pal);
|
||||
starti = i + 1;
|
||||
last = s[i, j];
|
||||
}
|
||||
@@ -200,7 +200,7 @@ namespace OpenRA.Graphics
|
||||
if (starti < clip.Right)
|
||||
s[starti, j].DrawAt(
|
||||
Game.CellSize * new float2(starti, j),
|
||||
pal.Index,
|
||||
pal,
|
||||
new float2(Game.CellSize * (clip.Right - starti), Game.CellSize));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,57 +17,52 @@ namespace OpenRA.Graphics
|
||||
public readonly Rectangle bounds;
|
||||
public readonly Sheet sheet;
|
||||
public readonly TextureChannel channel;
|
||||
public readonly RectangleF uv;
|
||||
public readonly float2 size;
|
||||
|
||||
readonly float2[] uvhax;
|
||||
public readonly float2 offset;
|
||||
readonly float2[] textureCoords;
|
||||
|
||||
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel)
|
||||
: this(sheet, bounds, float2.Zero, channel) {}
|
||||
|
||||
public Sprite(Sheet sheet, Rectangle bounds, float2 offset, TextureChannel channel)
|
||||
{
|
||||
this.bounds = bounds;
|
||||
this.sheet = sheet;
|
||||
this.bounds = bounds;
|
||||
this.offset = offset;
|
||||
this.channel = channel;
|
||||
|
||||
uv = new RectangleF(
|
||||
(float)(bounds.Left) / sheet.Size.Width,
|
||||
(float)(bounds.Top) / sheet.Size.Height,
|
||||
(float)(bounds.Width) / sheet.Size.Width,
|
||||
(float)(bounds.Height) / sheet.Size.Height);
|
||||
|
||||
uvhax = new float2[]
|
||||
{
|
||||
new float2( uv.Left, uv.Top ),
|
||||
new float2( uv.Right, uv.Top ),
|
||||
new float2( uv.Left, uv.Bottom ),
|
||||
new float2( uv.Right, uv.Bottom ),
|
||||
};
|
||||
|
||||
this.size = new float2(bounds.Size);
|
||||
|
||||
var left = (float)(bounds.Left) / sheet.Size.Width;
|
||||
var top = (float)(bounds.Top) / sheet.Size.Height;
|
||||
var right = (float)(bounds.Right) / sheet.Size.Width;
|
||||
var bottom = (float)(bounds.Bottom) / sheet.Size.Height;
|
||||
textureCoords = new float2[]
|
||||
{
|
||||
new float2(left, top),
|
||||
new float2(right, top),
|
||||
new float2(left, bottom),
|
||||
new float2(right, bottom),
|
||||
};
|
||||
}
|
||||
|
||||
public float2 FastMapTextureCoords( int k )
|
||||
public float2 FastMapTextureCoords(int k)
|
||||
{
|
||||
return uvhax[ k ];
|
||||
return textureCoords[k];
|
||||
}
|
||||
|
||||
public void DrawAt( WorldRenderer wr, float2 location, string palette )
|
||||
public void DrawAt(float2 location, PaletteReference pal)
|
||||
{
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite( this, location, wr, palette, this.size );
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, pal, size);
|
||||
}
|
||||
|
||||
public void DrawAt( float2 location, int paletteIndex )
|
||||
public void DrawAt(float2 location, PaletteReference pal, float scale)
|
||||
{
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite( this, location, paletteIndex, this.size );
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, pal, size*scale);
|
||||
}
|
||||
|
||||
public void DrawAt(float2 location, int paletteIndex, float scale)
|
||||
public void DrawAt(float2 location, PaletteReference pal, float2 size)
|
||||
{
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, paletteIndex, this.size * scale);
|
||||
}
|
||||
|
||||
public void DrawAt( float2 location, int paletteIndex, float2 size )
|
||||
{
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite( this, location, paletteIndex, size );
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite(this, location, pal, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ namespace OpenRA.Graphics
|
||||
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph,
|
||||
Pair<char,Color>.EqualityComparer);
|
||||
|
||||
// setup a 1-channel SheetBuilder for our private use
|
||||
if (builder == null) builder = new SheetBuilder(TextureChannel.Alpha);
|
||||
// setup a SheetBuilder for our private use
|
||||
// TODO: SheetBuilder state is leaked between mod switches
|
||||
if (builder == null)
|
||||
builder = new SheetBuilder(SheetType.BGRA);
|
||||
|
||||
PrecacheColor(Color.White);
|
||||
PrecacheColor(Color.Red);
|
||||
@@ -46,7 +48,7 @@ namespace OpenRA.Graphics
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void DrawText (string text, float2 location, Color c)
|
||||
public void DrawText(string text, float2 location, Color c)
|
||||
{
|
||||
location.Y += size; // baseline vs top
|
||||
|
||||
@@ -84,7 +86,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public int2 Measure(string text)
|
||||
{
|
||||
return new int2((int)text.Split( '\n' ).Max( s => s.Sum(a => glyphs[Pair.New(a, Color.White)].Advance)), text.Split('\n').Count()*size);
|
||||
return new int2((int)text.Split('\n').Max(s => s.Sum(a => glyphs[Pair.New(a, Color.White)].Advance)), text.Split('\n').Count()*size);
|
||||
}
|
||||
|
||||
Cache<Pair<char,Color>, GlyphInfo> glyphs;
|
||||
@@ -96,9 +98,8 @@ namespace OpenRA.Graphics
|
||||
face.LoadGlyph(index, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
var s = builder.Allocate(
|
||||
new Size((int)face.Glyph.Metrics.Width >> 6,
|
||||
(int)face.Glyph.Metrics.Height >> 6));
|
||||
var size = new Size((int)face.Glyph.Metrics.Width >> 6, (int)face.Glyph.Metrics.Height >> 6);
|
||||
var s = builder.Allocate(size);
|
||||
|
||||
var g = new GlyphInfo
|
||||
{
|
||||
@@ -128,6 +129,8 @@ namespace OpenRA.Graphics
|
||||
p += face.Glyph.Bitmap.Pitch;
|
||||
}
|
||||
}
|
||||
s.sheet.CommitData();
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
@@ -15,11 +16,11 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public class SpriteLoader
|
||||
{
|
||||
public SpriteLoader( string[] exts, SheetBuilder sheetBuilder )
|
||||
public SpriteLoader(string[] exts, SheetBuilder sheetBuilder)
|
||||
{
|
||||
SheetBuilder = sheetBuilder;
|
||||
this.exts = exts;
|
||||
sprites = new Cache<string, Sprite[]>( LoadSprites );
|
||||
sprites = new Cache<string, Sprite[]>(LoadSprites);
|
||||
}
|
||||
|
||||
readonly SheetBuilder SheetBuilder;
|
||||
@@ -28,8 +29,19 @@ namespace OpenRA.Graphics
|
||||
|
||||
Sprite[] LoadSprites(string filename)
|
||||
{
|
||||
var shp = new ShpReader(FileSystem.OpenWithExts(filename, exts));
|
||||
return shp.Frames.Select(a => SheetBuilder.Add(a.Image, shp.Size)).ToArray();
|
||||
BinaryReader reader = new BinaryReader(FileSystem.OpenWithExts(filename, exts));
|
||||
|
||||
var ImageCount = reader.ReadUInt16();
|
||||
if (ImageCount == 0)
|
||||
{
|
||||
var shp = new ShpTSReader(FileSystem.OpenWithExts(filename, exts));
|
||||
return shp.Select(a => SheetBuilder.Add(a.Image, shp.Size)).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
var shp = new ShpReader(FileSystem.OpenWithExts(filename, exts));
|
||||
return shp.Frames.Select(a => SheetBuilder.Add(a.Image, shp.Size)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public Sprite[] LoadAllSprites(string filename) { return sprites[filename]; }
|
||||
|
||||
@@ -33,40 +33,42 @@ namespace OpenRA.Graphics
|
||||
if (nv > 0)
|
||||
{
|
||||
shader.SetTexture("DiffuseTexture", currentSheet.Texture);
|
||||
renderer.Device.EnableAlphaBlending();
|
||||
shader.Render(() =>
|
||||
{
|
||||
var vb = renderer.GetTempVertexBuffer();
|
||||
vb.SetData(vertices, nv);
|
||||
renderer.DrawBatch(vb, 0, nv, PrimitiveType.QuadList);
|
||||
});
|
||||
renderer.Device.DisableAlphaBlending();
|
||||
|
||||
nv = 0;
|
||||
currentSheet = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float2 location, WorldRenderer wr, string palette)
|
||||
public void DrawSprite(Sprite s, float2 location, PaletteReference pal)
|
||||
{
|
||||
DrawSprite(s, location, wr.Palette(palette).Index, s.size);
|
||||
DrawSprite(s, location, pal.Index, s.size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float2 location, WorldRenderer wr, string palette, float2 size)
|
||||
public void DrawSprite(Sprite s, float2 location, PaletteReference pal, float2 size)
|
||||
{
|
||||
DrawSprite(s, location, wr.Palette(palette).Index, size);
|
||||
DrawSprite(s, location, pal.Index, size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float2 location, int paletteIndex, float2 size)
|
||||
void DrawSprite(Sprite s, float2 location, int paletteIndex, float2 size)
|
||||
{
|
||||
Renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (s.sheet != currentSheet)
|
||||
Flush();
|
||||
|
||||
if( nv + 4 > Renderer.TempBufferSize )
|
||||
if (nv + 4 > Renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
currentSheet = s.sheet;
|
||||
Util.FastCreateQuad(vertices, location.ToInt2(), s, paletteIndex, nv, size);
|
||||
Util.FastCreateQuad(vertices, location + s.offset, s, paletteIndex, nv, size);
|
||||
nv += 4;
|
||||
}
|
||||
|
||||
@@ -81,10 +83,27 @@ namespace OpenRA.Graphics
|
||||
DrawSprite(s, location, 0, size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float2 a, float2 b, float2 c, float2 d)
|
||||
{
|
||||
Renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (s.sheet != currentSheet)
|
||||
Flush();
|
||||
|
||||
if (nv + 4 > Renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
currentSheet = s.sheet;
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, 0, nv);
|
||||
nv += 4;
|
||||
}
|
||||
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet)
|
||||
{
|
||||
shader.SetTexture("DiffuseTexture", sheet.Texture);
|
||||
renderer.Device.EnableAlphaBlending();
|
||||
shader.Render(() => renderer.DrawBatch(buffer, start, length, type));
|
||||
renderer.Device.DisableAlphaBlending();
|
||||
}
|
||||
|
||||
public void SetPalette(ITexture palette)
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
class TerrainRenderer
|
||||
{
|
||||
SheetBuilder sheetBuilder;
|
||||
IVertexBuffer<Vertex> vertexBuffer;
|
||||
Sheet terrainSheet;
|
||||
|
||||
World world;
|
||||
Map map;
|
||||
@@ -29,37 +29,42 @@ namespace OpenRA.Graphics
|
||||
this.world = world;
|
||||
this.map = world.Map;
|
||||
|
||||
var tileSize = new Size( Game.CellSize, Game.CellSize );
|
||||
var allocated = false;
|
||||
Func<Sheet> allocate = () =>
|
||||
{
|
||||
if (allocated)
|
||||
throw new SheetOverflowException("Terrain sheet overflow");
|
||||
allocated = true;
|
||||
|
||||
// TODO: Use a fixed sheet size specified in the tileset yaml
|
||||
return SheetBuilder.AllocateSheet();
|
||||
};
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
|
||||
|
||||
var tileSize = new Size(Game.CellSize, Game.CellSize);
|
||||
var tileMapping = new Cache<TileReference<ushort,byte>, Sprite>(
|
||||
x => Game.modData.SheetBuilder.Add(world.TileSet.GetBytes(x), tileSize));
|
||||
|
||||
var vertices = new Vertex[4 * map.Bounds.Height * map.Bounds.Width];
|
||||
|
||||
terrainSheet = tileMapping[map.MapTiles.Value[map.Bounds.Left, map.Bounds.Top]].sheet;
|
||||
|
||||
int nv = 0;
|
||||
x => sheetBuilder.Add(world.TileSet.GetBytes(x), tileSize));
|
||||
|
||||
var terrainPalette = wr.Palette("terrain").Index;
|
||||
var vertices = new Vertex[4 * map.Bounds.Height * map.Bounds.Width];
|
||||
int nv = 0;
|
||||
|
||||
for( int j = map.Bounds.Top; j < map.Bounds.Bottom; j++ )
|
||||
for( int i = map.Bounds.Left; i < map.Bounds.Right; i++ )
|
||||
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
|
||||
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
|
||||
{
|
||||
var tile = tileMapping[map.MapTiles.Value[i, j]];
|
||||
// TODO: move GetPaletteIndex out of the inner loop.
|
||||
Util.FastCreateQuad(vertices, Game.CellSize * new float2(i, j), tile, terrainPalette, nv, tile.size);
|
||||
nv += 4;
|
||||
|
||||
if (tileMapping[map.MapTiles.Value[i, j]].sheet != terrainSheet)
|
||||
throw new InvalidOperationException("Terrain sprites span multiple sheets. Try increasing Game.Settings.Graphics.SheetSize.");
|
||||
}
|
||||
|
||||
vertexBuffer = Game.Renderer.Device.CreateVertexBuffer( vertices.Length );
|
||||
vertexBuffer.SetData( vertices, nv );
|
||||
vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(vertices.Length);
|
||||
vertexBuffer.SetData(vertices, nv);
|
||||
}
|
||||
|
||||
public void Draw( WorldRenderer wr, Viewport viewport )
|
||||
public void Draw(WorldRenderer wr, Viewport viewport)
|
||||
{
|
||||
int verticesPerRow = map.Bounds.Width * 4;
|
||||
int verticesPerRow = 4*map.Bounds.Width;
|
||||
|
||||
int visibleRows = (int)(viewport.Height * 1f / Game.CellSize / viewport.Zoom + 2);
|
||||
|
||||
@@ -79,17 +84,22 @@ namespace OpenRA.Graphics
|
||||
firstRow = r.Bottom - map.Bounds.Top;
|
||||
}
|
||||
|
||||
if (firstRow < 0) firstRow = 0;
|
||||
if (lastRow > map.Bounds.Height) lastRow = map.Bounds.Height;
|
||||
// Sanity checking
|
||||
if (firstRow < 0)
|
||||
firstRow = 0;
|
||||
|
||||
if( lastRow < firstRow ) lastRow = firstRow;
|
||||
if (lastRow > map.Bounds.Height)
|
||||
lastRow = map.Bounds.Height;
|
||||
|
||||
if (lastRow < firstRow)
|
||||
lastRow = firstRow;
|
||||
|
||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||
vertexBuffer, verticesPerRow * firstRow, verticesPerRow * (lastRow - firstRow),
|
||||
PrimitiveType.QuadList, terrainSheet);
|
||||
PrimitiveType.QuadList, sheetBuilder.Current);
|
||||
|
||||
foreach (var r in world.WorldActor.TraitsImplementing<IRenderOverlay>())
|
||||
r.Render( wr );
|
||||
r.Render(wr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
OpenRA.Game/Graphics/TextRenderable.cs
Normal file
59
OpenRA.Game/Graphics/TextRenderable.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct TextRenderable : IRenderable
|
||||
{
|
||||
readonly SpriteFont font;
|
||||
readonly WPos pos;
|
||||
readonly int zOffset;
|
||||
readonly Color color;
|
||||
readonly string text;
|
||||
|
||||
public TextRenderable(SpriteFont font, WPos pos, int zOffset, Color color, string text)
|
||||
{
|
||||
this.font = font;
|
||||
this.pos = pos;
|
||||
this.zOffset = zOffset;
|
||||
this.color = color;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos; } }
|
||||
public float Scale { get { return 1f; } }
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
|
||||
public IRenderable WithScale(float newScale) { return new TextRenderable(font, pos, zOffset, color, text); }
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new TextRenderable(font, pos, zOffset, color, text); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new TextRenderable(font, pos, zOffset, color, text); }
|
||||
public IRenderable WithPos(WPos pos) { return new TextRenderable(font, pos, zOffset, color, text); }
|
||||
|
||||
public void BeforeRender(WorldRenderer wr) {}
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
var screenPos = Game.viewport.Zoom*(wr.ScreenPosition(pos) - Game.viewport.Location) - 0.5f*font.Measure(text).ToFloat2();
|
||||
var screenPxPos = new float2((float)Math.Round(screenPos.X), (float)Math.Round(screenPos.Y));
|
||||
font.DrawTextWithContrast(text, screenPxPos, color, Color.Black, 1);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
{
|
||||
var size = font.Measure(text).ToFloat2();
|
||||
var offset = wr.ScreenPxPosition(pos) - 0.5f*size;
|
||||
Game.Renderer.WorldLineRenderer.DrawRect(offset, offset + size, Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.FileFormats.Graphics;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -17,27 +18,32 @@ namespace OpenRA.Graphics
|
||||
static float[] channelSelect = { 0.75f, 0.25f, -0.25f, -0.75f };
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices, float2 o, Sprite r, int palette, int nv, float2 size)
|
||||
{
|
||||
var b = new float2(o.X + size.X, o.Y);
|
||||
var c = new float2(o.X + size.X, o.Y + size.Y);
|
||||
var d = new float2(o.X, o.Y + size.Y);
|
||||
FastCreateQuad(vertices, o, b, c, d, r, palette, nv);
|
||||
}
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices, float2 a, float2 b, float2 c, float2 d, Sprite r, int palette, int nv)
|
||||
{
|
||||
var attrib = new float2(palette / (float)HardwarePalette.MaxPalettes, channelSelect[(int)r.channel]);
|
||||
|
||||
vertices[nv] = new Vertex(o,
|
||||
r.FastMapTextureCoords(0), attrib);
|
||||
vertices[nv + 1] = new Vertex(new float2(o.X + size.X, o.Y),
|
||||
r.FastMapTextureCoords(1), attrib);
|
||||
vertices[nv + 2] = new Vertex(new float2(o.X + size.X, o.Y + size.Y),
|
||||
r.FastMapTextureCoords(3), attrib);
|
||||
vertices[nv + 3] = new Vertex(new float2(o.X, o.Y + size.Y),
|
||||
r.FastMapTextureCoords(2), attrib);
|
||||
vertices[nv] = new Vertex(a, r.FastMapTextureCoords(0), attrib);
|
||||
vertices[nv + 1] = new Vertex(b, r.FastMapTextureCoords(1), attrib);
|
||||
vertices[nv + 2] = new Vertex(c, r.FastMapTextureCoords(3), attrib);
|
||||
vertices[nv + 3] = new Vertex(d, r.FastMapTextureCoords(2), attrib);
|
||||
}
|
||||
|
||||
static readonly int[] channelMasks = { 2, 1, 0, 3 }; // yes, our channel order is nuts.
|
||||
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src)
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src) { FastCopyIntoChannel(dest, 0, src); }
|
||||
public static void FastCopyIntoChannel(Sprite dest, int channelOffset, byte[] src)
|
||||
{
|
||||
var data = dest.sheet.Data;
|
||||
var srcStride = dest.bounds.Width;
|
||||
var destStride = dest.sheet.Size.Width * 4;
|
||||
var destOffset = destStride * dest.bounds.Top + dest.bounds.Left * 4 + channelMasks[(int)dest.channel];
|
||||
var destOffset = destStride * dest.bounds.Top + dest.bounds.Left * 4 + channelMasks[(int)dest.channel + channelOffset];
|
||||
var destSkip = destStride - 4 * srcStride;
|
||||
var height = dest.bounds.Height;
|
||||
|
||||
@@ -52,5 +58,217 @@ namespace OpenRA.Graphics
|
||||
destOffset += destSkip;
|
||||
}
|
||||
}
|
||||
|
||||
public static float[] IdentityMatrix()
|
||||
{
|
||||
return Exts.MakeArray(16, j => (j % 5 == 0) ? 1.0f : 0);
|
||||
}
|
||||
|
||||
public static float[] ScaleMatrix(float sx, float sy, float sz)
|
||||
{
|
||||
var mtx = IdentityMatrix();
|
||||
mtx[0] = sx;
|
||||
mtx[5] = sy;
|
||||
mtx[10] = sz;
|
||||
return mtx;
|
||||
}
|
||||
|
||||
public static float[] TranslationMatrix(float x, float y, float z)
|
||||
{
|
||||
var mtx = IdentityMatrix();
|
||||
mtx[12] = x;
|
||||
mtx[13] = y;
|
||||
mtx[14] = z;
|
||||
return mtx;
|
||||
}
|
||||
|
||||
public static float[] MatrixMultiply(float[] lhs, float[] rhs)
|
||||
{
|
||||
var mtx = new float[16];
|
||||
for (var i = 0; i < 4; i++)
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
mtx[4*i + j] = 0;
|
||||
for (var k = 0; k < 4; k++)
|
||||
mtx[4*i + j] += lhs[4*k + j]*rhs[4*i + k];
|
||||
}
|
||||
|
||||
return mtx;
|
||||
}
|
||||
|
||||
public static float[] MatrixVectorMultiply(float[] mtx, float[] vec)
|
||||
{
|
||||
var ret = new float[4];
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
ret[j] = 0;
|
||||
for (var k = 0; k < 4; k++)
|
||||
ret[j] += mtx[4*k + j]*vec[k];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static float[] MatrixInverse(float[] m)
|
||||
{
|
||||
var mtx = new float[16];
|
||||
|
||||
mtx[0] = m[5]*m[10]*m[15] -
|
||||
m[5]*m[11]*m[14] -
|
||||
m[9]*m[6]*m[15] +
|
||||
m[9]*m[7]*m[14] +
|
||||
m[13]*m[6]*m[11] -
|
||||
m[13]*m[7]*m[10];
|
||||
|
||||
mtx[4] = -m[4]*m[10]*m[15] +
|
||||
m[4]*m[11]*m[14] +
|
||||
m[8]*m[6]*m[15] -
|
||||
m[8]*m[7]*m[14] -
|
||||
m[12]*m[6]*m[11] +
|
||||
m[12]*m[7]*m[10];
|
||||
|
||||
mtx[8] = m[4]*m[9]*m[15] -
|
||||
m[4]*m[11]*m[13] -
|
||||
m[8]*m[5]*m[15] +
|
||||
m[8]*m[7]*m[13] +
|
||||
m[12]*m[5]*m[11] -
|
||||
m[12]*m[7]*m[9];
|
||||
|
||||
mtx[12] = -m[4]*m[9]*m[14] +
|
||||
m[4]*m[10]*m[13] +
|
||||
m[8]*m[5]*m[14] -
|
||||
m[8]*m[6]*m[13] -
|
||||
m[12]*m[5]*m[10] +
|
||||
m[12]*m[6]*m[9];
|
||||
|
||||
mtx[1] = -m[1]*m[10]*m[15] +
|
||||
m[1]*m[11]*m[14] +
|
||||
m[9]*m[2]*m[15] -
|
||||
m[9]*m[3]*m[14] -
|
||||
m[13]*m[2]*m[11] +
|
||||
m[13]*m[3]*m[10];
|
||||
|
||||
mtx[5] = m[0]*m[10]*m[15] -
|
||||
m[0]*m[11]*m[14] -
|
||||
m[8]*m[2]*m[15] +
|
||||
m[8]*m[3]*m[14] +
|
||||
m[12]*m[2]*m[11] -
|
||||
m[12]*m[3]*m[10];
|
||||
|
||||
mtx[9] = -m[0]*m[9]*m[15] +
|
||||
m[0]*m[11]*m[13] +
|
||||
m[8]*m[1]*m[15] -
|
||||
m[8]*m[3]*m[13] -
|
||||
m[12]*m[1]*m[11] +
|
||||
m[12]*m[3]*m[9];
|
||||
|
||||
mtx[13] = m[0]*m[9]*m[14] -
|
||||
m[0]*m[10]*m[13] -
|
||||
m[8]*m[1]*m[14] +
|
||||
m[8]*m[2]*m[13] +
|
||||
m[12]*m[1]*m[10] -
|
||||
m[12]*m[2]*m[9];
|
||||
|
||||
mtx[2] = m[1]*m[6]*m[15] -
|
||||
m[1]*m[7]*m[14] -
|
||||
m[5]*m[2]*m[15] +
|
||||
m[5]*m[3]*m[14] +
|
||||
m[13]*m[2]*m[7] -
|
||||
m[13]*m[3]*m[6];
|
||||
|
||||
mtx[6] = -m[0]*m[6]*m[15] +
|
||||
m[0]*m[7]*m[14] +
|
||||
m[4]*m[2]*m[15] -
|
||||
m[4]*m[3]*m[14] -
|
||||
m[12]*m[2]*m[7] +
|
||||
m[12]*m[3]*m[6];
|
||||
|
||||
mtx[10] = m[0]*m[5]*m[15] -
|
||||
m[0]*m[7]*m[13] -
|
||||
m[4]*m[1]*m[15] +
|
||||
m[4]*m[3]*m[13] +
|
||||
m[12]*m[1]*m[7] -
|
||||
m[12]*m[3]*m[5];
|
||||
|
||||
mtx[14] = -m[0]*m[5]*m[14] +
|
||||
m[0]*m[6]*m[13] +
|
||||
m[4]*m[1]*m[14] -
|
||||
m[4]*m[2]*m[13] -
|
||||
m[12]*m[1]*m[6] +
|
||||
m[12]*m[2]*m[5];
|
||||
|
||||
mtx[3] = -m[1]*m[6]*m[11] +
|
||||
m[1]*m[7]*m[10] +
|
||||
m[5]*m[2]*m[11] -
|
||||
m[5]*m[3]*m[10] -
|
||||
m[9]*m[2]*m[7] +
|
||||
m[9]*m[3]*m[6];
|
||||
|
||||
mtx[7] = m[0]*m[6]*m[11] -
|
||||
m[0]*m[7]*m[10] -
|
||||
m[4]*m[2]*m[11] +
|
||||
m[4]*m[3]*m[10] +
|
||||
m[8]*m[2]*m[7] -
|
||||
m[8]*m[3]*m[6];
|
||||
|
||||
mtx[11] = -m[0]*m[5]*m[11] +
|
||||
m[0]*m[7]*m[9] +
|
||||
m[4]*m[1]*m[11] -
|
||||
m[4]*m[3]*m[9] -
|
||||
m[8]*m[1]*m[7] +
|
||||
m[8]*m[3]*m[5];
|
||||
|
||||
mtx[15] = m[0]*m[5]*m[10] -
|
||||
m[0]*m[6]*m[9] -
|
||||
m[4]*m[1]*m[10] +
|
||||
m[4]*m[2]*m[9] +
|
||||
m[8]*m[1]*m[6] -
|
||||
m[8]*m[2]*m[5];
|
||||
|
||||
var det = m[0]*mtx[0] + m[1]*mtx[4] + m[2]*mtx[8] + m[3]*mtx[12];
|
||||
if (det == 0)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
mtx[i] *= 1/det;
|
||||
|
||||
return mtx;
|
||||
}
|
||||
|
||||
public static float[] MakeFloatMatrix(int[] imtx)
|
||||
{
|
||||
var fmtx = new float[16];
|
||||
for (var i = 0; i < 16; i++)
|
||||
fmtx[i] = imtx[i]*1f / imtx[15];
|
||||
return fmtx;
|
||||
}
|
||||
|
||||
public static float[] MatrixAABBMultiply(float[] mtx, float[] bounds)
|
||||
{
|
||||
// Corner offsets
|
||||
var ix = new uint[] {0,0,0,0,3,3,3,3};
|
||||
var iy = new uint[] {1,1,4,4,1,1,4,4};
|
||||
var iz = new uint[] {2,5,2,5,2,5,2,5};
|
||||
|
||||
// Vectors to opposing corner
|
||||
var ret = new float[] {float.MaxValue, float.MaxValue, float.MaxValue,
|
||||
float.MinValue, float.MinValue, float.MinValue};
|
||||
|
||||
// Transform vectors and find new bounding box
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
var vec = new float[] {bounds[ix[i]], bounds[iy[i]], bounds[iz[i]], 1};
|
||||
var tvec = Util.MatrixVectorMultiply(mtx, vec);
|
||||
|
||||
ret[0] = Math.Min(ret[0], tvec[0]/tvec[3]);
|
||||
ret[1] = Math.Min(ret[1], tvec[1]/tvec[3]);
|
||||
ret[2] = Math.Min(ret[2], tvec[2]/tvec[3]);
|
||||
ret[3] = Math.Max(ret[3], tvec[0]/tvec[3]);
|
||||
ret[4] = Math.Max(ret[4], tvec[1]/tvec[3]);
|
||||
ret[5] = Math.Max(ret[5], tvec[2]/tvec[3]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
128
OpenRA.Game/Graphics/Voxel.cs
Normal file
128
OpenRA.Game/Graphics/Voxel.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileFormats.Graphics;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
struct Limb
|
||||
{
|
||||
public float Scale;
|
||||
public float[] Bounds;
|
||||
public byte[] Size;
|
||||
public VoxelRenderData RenderData;
|
||||
}
|
||||
|
||||
public class Voxel
|
||||
{
|
||||
Limb[] limbData;
|
||||
float[] transforms;
|
||||
|
||||
public readonly uint Frames;
|
||||
public readonly uint Limbs;
|
||||
|
||||
public Voxel(VoxelLoader loader, VxlReader vxl, HvaReader hva)
|
||||
{
|
||||
if (vxl.LimbCount != hva.LimbCount)
|
||||
throw new InvalidOperationException("Voxel and hva limb counts don't match");
|
||||
|
||||
transforms = hva.Transforms;
|
||||
Frames = hva.FrameCount;
|
||||
Limbs = hva.LimbCount;
|
||||
|
||||
limbData = new Limb[vxl.LimbCount];
|
||||
for (var i = 0; i < vxl.LimbCount; i++)
|
||||
{
|
||||
var vl = vxl.Limbs[i];
|
||||
var l = new Limb();
|
||||
l.Scale = vl.Scale;
|
||||
l.Bounds = (float[])vl.Bounds.Clone();
|
||||
l.Size = (byte[])vl.Size.Clone();
|
||||
l.RenderData = loader.GenerateRenderData(vxl.Limbs[i]);
|
||||
limbData[i] = l;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
var l = limbData[limb];
|
||||
var t = new float[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];
|
||||
t[13] *= l.Scale*(l.Bounds[4] - l.Bounds[1]) / l.Size[1];
|
||||
t[14] *= l.Scale*(l.Bounds[5] - l.Bounds[2]) / l.Size[2];
|
||||
|
||||
// Center, flip and scale
|
||||
t = Util.MatrixMultiply(t, Util.TranslationMatrix(l.Bounds[0], l.Bounds[1], l.Bounds[2]));
|
||||
t = Util.MatrixMultiply(Util.ScaleMatrix(l.Scale, -l.Scale, l.Scale), t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public VoxelRenderData RenderData(uint limb)
|
||||
{
|
||||
return limbData[limb].RenderData;
|
||||
}
|
||||
|
||||
public float[] Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return limbData.Select(a => a.Size.Select(b => a.Scale*b).ToArray())
|
||||
.Aggregate((a,b) => new float[]
|
||||
{
|
||||
Math.Max(a[0], b[0]),
|
||||
Math.Max(a[1], b[1]),
|
||||
Math.Max(a[2], b[2])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public float[] Bounds(uint frame)
|
||||
{
|
||||
var ret = new float[] {float.MaxValue,float.MaxValue,float.MaxValue,
|
||||
float.MinValue,float.MinValue,float.MinValue};
|
||||
|
||||
for (uint j = 0; j < Limbs; j++)
|
||||
{
|
||||
var l = limbData[j];
|
||||
var b = new float[]
|
||||
{
|
||||
0, 0, 0,
|
||||
(l.Bounds[3] - l.Bounds[0]),
|
||||
(l.Bounds[4] - l.Bounds[1]),
|
||||
(l.Bounds[5] - l.Bounds[2])
|
||||
};
|
||||
|
||||
// Calculate limb bounding box
|
||||
var bb = Util.MatrixAABBMultiply(TransformationMatrix(j, frame), b);
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
ret[i] = Math.Min(ret[i], bb[i]);
|
||||
ret[i+3] = Math.Max(ret[i+3], bb[i+3]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
OpenRA.Game/Graphics/VoxelAnimation.cs
Normal file
35
OpenRA.Game/Graphics/VoxelAnimation.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2011 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct VoxelAnimation
|
||||
{
|
||||
public readonly Voxel Voxel;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<IEnumerable<WRot>> RotationFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<uint> FrameFunc;
|
||||
|
||||
public VoxelAnimation(Voxel voxel, Func<WVec> offset, Func<IEnumerable<WRot>> rotation, Func<bool> disable, Func<uint> frame)
|
||||
{
|
||||
Voxel = voxel;
|
||||
OffsetFunc = offset;
|
||||
RotationFunc = rotation;
|
||||
DisableFunc = disable;
|
||||
FrameFunc = frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
222
OpenRA.Game/Graphics/VoxelLoader.cs
Normal file
222
OpenRA.Game/Graphics/VoxelLoader.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileFormats.Graphics;
|
||||
|
||||
namespace OpenRA.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 class VoxelLoader
|
||||
{
|
||||
SheetBuilder sheetBuilder;
|
||||
|
||||
Cache<Pair<string,string>, Voxel> voxels;
|
||||
IVertexBuffer<Vertex> vertexBuffer;
|
||||
List<Vertex[]> vertices;
|
||||
int totalVertexCount;
|
||||
int cachedVertexCount;
|
||||
|
||||
SheetBuilder CreateSheetBuilder()
|
||||
{
|
||||
var allocated = false;
|
||||
Func<Sheet> allocate = () =>
|
||||
{
|
||||
if (allocated)
|
||||
throw new SheetOverflowException("");
|
||||
allocated = true;
|
||||
return SheetBuilder.AllocateSheet();
|
||||
};
|
||||
|
||||
return new SheetBuilder(SheetType.DualIndexed, allocate);
|
||||
}
|
||||
|
||||
public VoxelLoader()
|
||||
{
|
||||
voxels = new Cache<Pair<string,string>, Voxel>(LoadFile);
|
||||
vertices = new List<Vertex[]>();
|
||||
totalVertexCount = 0;
|
||||
cachedVertexCount = 0;
|
||||
|
||||
sheetBuilder = CreateSheetBuilder();
|
||||
}
|
||||
|
||||
static float[] channelSelect = { 0.75f, 0.25f, -0.25f, -0.75f };
|
||||
Vertex[] GenerateSlicePlane(int su, int sv, Func<int,int,VxlElement> first, Func<int,int,VxlElement> second, Func<int, int, float[]> coord)
|
||||
{
|
||||
var colors = new byte[su*sv];
|
||||
var normals = new byte[su*sv];
|
||||
|
||||
var c = 0;
|
||||
for (var v = 0; v < sv; v++)
|
||||
for (var u = 0; u < su; u++)
|
||||
{
|
||||
var voxel = first(u,v) ?? second(u,v);
|
||||
colors[c] = voxel == null ? (byte)0 : voxel.Color;
|
||||
normals[c] = voxel == null ? (byte)0 : voxel.Normal;
|
||||
c++;
|
||||
}
|
||||
|
||||
Sprite s = sheetBuilder.Allocate(new Size(su, sv));
|
||||
Util.FastCopyIntoChannel(s, 0, colors);
|
||||
Util.FastCopyIntoChannel(s, 1, normals);
|
||||
s.sheet.CommitData();
|
||||
|
||||
var channels = new float2(channelSelect[(int)s.channel], channelSelect[(int)s.channel + 1]);
|
||||
return new Vertex[4]
|
||||
{
|
||||
new Vertex(coord(0, 0), s.FastMapTextureCoords(0), channels),
|
||||
new Vertex(coord(su, 0), s.FastMapTextureCoords(1), channels),
|
||||
new Vertex(coord(su, sv), s.FastMapTextureCoords(3), channels),
|
||||
new Vertex(coord(0, sv), s.FastMapTextureCoords(2), channels)
|
||||
};
|
||||
}
|
||||
|
||||
IEnumerable<Vertex[]> GenerateSlicePlanes(VxlLimb l)
|
||||
{
|
||||
Func<int,int,int,VxlElement> get = (x,y,z) =>
|
||||
{
|
||||
if (x < 0 || y < 0 || z < 0)
|
||||
return null;
|
||||
|
||||
if (x >= l.Size[0] || y >= l.Size[1] || z >= l.Size[2])
|
||||
return null;
|
||||
|
||||
var v = l.VoxelMap[(byte)x,(byte)y];
|
||||
if (v == null || !v.ContainsKey((byte)z))
|
||||
return null;
|
||||
|
||||
return l.VoxelMap[(byte)x,(byte)y][(byte)z];
|
||||
};
|
||||
|
||||
// Cull slices without any visible faces
|
||||
var xPlanes = new bool[l.Size[0]+1];
|
||||
var yPlanes = new bool[l.Size[1]+1];
|
||||
var zPlanes = new bool[l.Size[2]+1];
|
||||
for (var x = 0; x < l.Size[0]; x++)
|
||||
{
|
||||
for (var y = 0; y < l.Size[1]; y++)
|
||||
{
|
||||
for (var z = 0; z < l.Size[2]; z++)
|
||||
{
|
||||
if (get(x,y,z) == null)
|
||||
continue;
|
||||
|
||||
// Only generate a plane if it is actually visible
|
||||
if (!xPlanes[x] && get(x-1,y,z) == null)
|
||||
xPlanes[x] = true;
|
||||
if (!xPlanes[x+1] && get(x+1,y,z) == null)
|
||||
xPlanes[x+1] = true;
|
||||
|
||||
if (!yPlanes[y] && get(x,y-1,z) == null)
|
||||
yPlanes[y] = true;
|
||||
if (!yPlanes[y+1] && get(x,y+1,z) == null)
|
||||
yPlanes[y+1] = true;
|
||||
|
||||
if (!zPlanes[z] && get(x,y,z-1) == null)
|
||||
zPlanes[z] = true;
|
||||
if (!zPlanes[z+1] && get(x,y,z+1) == null)
|
||||
zPlanes[z+1] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var x = 0; x <= l.Size[0]; x++)
|
||||
if (xPlanes[x])
|
||||
yield return GenerateSlicePlane(l.Size[1], l.Size[2],
|
||||
(u,v) => get(x, u, v),
|
||||
(u,v) => get(x - 1, u, v),
|
||||
(u,v) => new float[] {x, u, v});
|
||||
|
||||
for (var y = 0; y <= l.Size[1]; y++)
|
||||
if (yPlanes[y])
|
||||
yield return GenerateSlicePlane(l.Size[0], l.Size[2],
|
||||
(u,v) => get(u, y, v),
|
||||
(u,v) => get(u, y - 1, v),
|
||||
(u,v) => new float[] {u, y, v});
|
||||
|
||||
for (var z = 0; z <= l.Size[2]; z++)
|
||||
if (zPlanes[z])
|
||||
yield return GenerateSlicePlane(l.Size[0], l.Size[1],
|
||||
(u,v) => get(u, v, z),
|
||||
(u,v) => get(u, v, z - 1),
|
||||
(u,v) => new float[] {u, v, z});
|
||||
}
|
||||
|
||||
public VoxelRenderData GenerateRenderData(VxlLimb l)
|
||||
{
|
||||
Vertex[] v;
|
||||
try
|
||||
{
|
||||
v = GenerateSlicePlanes(l).SelectMany(x => x).ToArray();
|
||||
}
|
||||
catch (SheetOverflowException)
|
||||
{
|
||||
// Sheet overflow - allocate a new sheet and try once more
|
||||
Log.Write("debug", "Voxel sheet overflow! Generating new sheet");
|
||||
sheetBuilder = CreateSheetBuilder();
|
||||
v = GenerateSlicePlanes(l).SelectMany(x => x).ToArray();
|
||||
}
|
||||
|
||||
vertices.Add(v);
|
||||
|
||||
var start = totalVertexCount;
|
||||
var count = v.Length;
|
||||
totalVertexCount += count;
|
||||
return new VoxelRenderData(start, count, sheetBuilder.Current);
|
||||
}
|
||||
|
||||
public void RefreshBuffer()
|
||||
{
|
||||
vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(totalVertexCount);
|
||||
vertexBuffer.SetData(vertices.SelectMany(v => v).ToArray(), totalVertexCount);
|
||||
cachedVertexCount = totalVertexCount;
|
||||
}
|
||||
|
||||
public IVertexBuffer<Vertex> VertexBuffer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedVertexCount != totalVertexCount)
|
||||
RefreshBuffer();
|
||||
return vertexBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
Voxel LoadFile(Pair<string,string> files)
|
||||
{
|
||||
var vxl = new VxlReader(FileSystem.OpenWithExts(files.First, ".vxl"));
|
||||
var hva = new HvaReader(FileSystem.OpenWithExts(files.Second, ".hva"));
|
||||
return new Voxel(this, vxl, hva);
|
||||
}
|
||||
|
||||
public Voxel Load(string vxl, string hva)
|
||||
{
|
||||
return voxels[Pair.New(vxl, hva)];
|
||||
}
|
||||
}
|
||||
}
|
||||
88
OpenRA.Game/Graphics/VoxelProvider.cs
Normal file
88
OpenRA.Game/Graphics/VoxelProvider.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public static class VoxelProvider
|
||||
{
|
||||
static Dictionary<string, Dictionary<string, Voxel>> units;
|
||||
|
||||
public static void Initialize(string[] voxelFiles, List<MiniYamlNode> voxelNodes)
|
||||
{
|
||||
units = new Dictionary<string, Dictionary<string, Voxel>>();
|
||||
|
||||
var sequences = voxelFiles
|
||||
.Select(s => MiniYaml.FromFile(s))
|
||||
.Aggregate(voxelNodes, MiniYaml.MergeLiberal);
|
||||
|
||||
foreach (var s in sequences)
|
||||
LoadVoxelsForUnit(s.Key, s.Value);
|
||||
|
||||
Game.modData.VoxelLoader.RefreshBuffer();
|
||||
}
|
||||
|
||||
static Voxel LoadVoxel(string unit, string name, 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 Game.modData.VoxelLoader.Load(vxl, hva);
|
||||
}
|
||||
|
||||
static void LoadVoxelsForUnit(string unit, MiniYaml sequences)
|
||||
{
|
||||
Game.modData.LoadScreen.Display();
|
||||
try
|
||||
{
|
||||
var seq = sequences.NodesDict.ToDictionary(x => x.Key, x => LoadVoxel(unit,x.Key,x.Value));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
176
OpenRA.Game/Graphics/VoxelRenderable.cs
Normal file
176
OpenRA.Game/Graphics/VoxelRenderable.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct VoxelRenderable : IRenderable
|
||||
{
|
||||
readonly IEnumerable<VoxelAnimation> voxels;
|
||||
readonly WPos pos;
|
||||
readonly int zOffset;
|
||||
readonly WRot camera;
|
||||
readonly WRot lightSource;
|
||||
readonly float[] lightAmbientColor;
|
||||
readonly float[] lightDiffuseColor;
|
||||
readonly PaletteReference palette;
|
||||
readonly PaletteReference normalsPalette;
|
||||
readonly PaletteReference shadowPalette;
|
||||
readonly float scale;
|
||||
|
||||
// Generated at render-time
|
||||
VoxelRenderProxy renderProxy;
|
||||
|
||||
public VoxelRenderable(IEnumerable<VoxelAnimation> voxels, WPos pos, int zOffset, WRot camera, float scale,
|
||||
WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
PaletteReference color, PaletteReference normals, PaletteReference shadow)
|
||||
{
|
||||
this.voxels = voxels;
|
||||
this.pos = pos;
|
||||
this.zOffset = zOffset;
|
||||
this.scale = scale;
|
||||
this.camera = camera;
|
||||
this.lightSource = lightSource;
|
||||
this.lightAmbientColor = lightAmbientColor;
|
||||
this.lightDiffuseColor = lightDiffuseColor;
|
||||
this.palette = color;
|
||||
this.normalsPalette = normals;
|
||||
this.shadowPalette = shadow;
|
||||
this.renderProxy = null;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos; } }
|
||||
public float Scale { get { return scale; } }
|
||||
public PaletteReference Palette { get { return palette; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
|
||||
public IRenderable WithScale(float newScale)
|
||||
{
|
||||
return new VoxelRenderable(voxels, pos, zOffset, camera, newScale,
|
||||
lightSource, lightAmbientColor, lightDiffuseColor,
|
||||
palette, normalsPalette, shadowPalette);
|
||||
}
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette)
|
||||
{
|
||||
return new VoxelRenderable(voxels, pos, zOffset, camera, scale,
|
||||
lightSource, lightAmbientColor, lightDiffuseColor,
|
||||
newPalette, normalsPalette, shadowPalette);
|
||||
}
|
||||
|
||||
public IRenderable WithZOffset(int newOffset)
|
||||
{
|
||||
return new VoxelRenderable(voxels, pos, newOffset, camera, scale,
|
||||
lightSource, lightAmbientColor, lightDiffuseColor,
|
||||
palette, normalsPalette, shadowPalette);
|
||||
}
|
||||
|
||||
public IRenderable WithPos(WPos newPos)
|
||||
{
|
||||
return new VoxelRenderable(voxels, newPos, zOffset, camera, scale,
|
||||
lightSource, lightAmbientColor, lightDiffuseColor,
|
||||
palette, normalsPalette, shadowPalette);
|
||||
}
|
||||
|
||||
// This will need generalizing once we support TS/RA2 terrain
|
||||
static readonly float[] groundNormal = new float[] {0,0,1,1};
|
||||
public void BeforeRender(WorldRenderer wr)
|
||||
{
|
||||
renderProxy = Game.Renderer.WorldVoxelRenderer.RenderAsync(
|
||||
wr, voxels, camera, scale, groundNormal, lightSource,
|
||||
lightAmbientColor, lightDiffuseColor,
|
||||
palette, normalsPalette, shadowPalette);
|
||||
}
|
||||
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
var pxOrigin = wr.ScreenPosition(pos);
|
||||
var groundZ = 0.5f*(pxOrigin.Y - wr.ScreenZPosition(pos, 0));
|
||||
var shadowOrigin = pxOrigin - groundZ*(new float2(renderProxy.ShadowDirection, 1));
|
||||
|
||||
var psb = renderProxy.ProjectedShadowBounds;
|
||||
var sa = shadowOrigin + psb[0];
|
||||
var sb = shadowOrigin + psb[2];
|
||||
var sc = shadowOrigin + psb[1];
|
||||
var sd = shadowOrigin + psb[3];
|
||||
Game.Renderer.WorldRgbaSpriteRenderer.DrawSprite(renderProxy.ShadowSprite, sa, sb, sc, sd);
|
||||
Game.Renderer.WorldRgbaSpriteRenderer.DrawSprite(renderProxy.Sprite, pxOrigin - 0.5f*renderProxy.Sprite.size);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
{
|
||||
var pxOrigin = wr.ScreenPosition(pos);
|
||||
var groundZ = 0.5f*(pxOrigin.Y - wr.ScreenZPosition(pos, 0));
|
||||
var shadowOrigin = pxOrigin - groundZ*(new float2(renderProxy.ShadowDirection, 1));
|
||||
|
||||
// Draw sprite rect
|
||||
var offset = pxOrigin + renderProxy.Sprite.offset - 0.5f*renderProxy.Sprite.size;
|
||||
Game.Renderer.WorldLineRenderer.DrawRect(offset, offset + renderProxy.Sprite.size, Color.Red);
|
||||
|
||||
// Draw transformed shadow sprite rect
|
||||
var c = Color.Purple;
|
||||
var psb = renderProxy.ProjectedShadowBounds;
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(shadowOrigin + psb[1], shadowOrigin + psb[3], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(shadowOrigin + psb[3], shadowOrigin + psb[0], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(shadowOrigin + psb[0], shadowOrigin + psb[2], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(shadowOrigin + psb[2], shadowOrigin + psb[1], c, c);
|
||||
|
||||
// Draw voxel bounding box
|
||||
var draw = voxels.Where(v => v.DisableFunc == null || !v.DisableFunc());
|
||||
var scaleTransform = Util.ScaleMatrix(scale, scale, scale);
|
||||
var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
|
||||
|
||||
foreach (var v in draw)
|
||||
{
|
||||
var bounds = v.Voxel.Bounds(v.FrameFunc());
|
||||
var worldTransform = v.RotationFunc().Reverse().Aggregate(scaleTransform,
|
||||
(x,y) => Util.MatrixMultiply(x, Util.MakeFloatMatrix(y.AsMatrix())));
|
||||
|
||||
var pxOffset = wr.ScreenVector(v.OffsetFunc());
|
||||
var pxPos = pxOrigin + new float2(pxOffset[0], pxOffset[1]);
|
||||
var screenTransform = Util.MatrixMultiply(cameraTransform, worldTransform);
|
||||
DrawBoundsBox(pxPos, screenTransform, bounds, Color.Yellow);
|
||||
}
|
||||
}
|
||||
|
||||
static readonly uint[] ix = new uint[] {0,0,0,0,3,3,3,3};
|
||||
static readonly uint[] iy = new uint[] {1,1,4,4,1,1,4,4};
|
||||
static readonly uint[] iz = new uint[] {2,5,2,5,2,5,2,5};
|
||||
static void DrawBoundsBox(float2 pxPos, float[] transform, float[] bounds, Color c)
|
||||
{
|
||||
var corners = new float2[8];
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
var vec = new float[] {bounds[ix[i]], bounds[iy[i]], bounds[iz[i]], 1};
|
||||
var screen = Util.MatrixVectorMultiply(transform, vec);
|
||||
corners[i] = pxPos + new float2(screen[0], screen[1]);
|
||||
}
|
||||
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[0], corners[1], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[1], corners[3], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[3], corners[2], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[2], corners[0], c, c);
|
||||
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[4], corners[5], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[5], corners[7], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[7], corners[6], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[6], corners[4], c, c);
|
||||
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[0], corners[4], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[1], corners[5], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[2], corners[6], c, c);
|
||||
Game.Renderer.WorldLineRenderer.DrawLine(corners[3], corners[7], c, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
342
OpenRA.Game/Graphics/VoxelRenderer.cs
Normal file
342
OpenRA.Game/Graphics/VoxelRenderer.cs
Normal file
@@ -0,0 +1,342 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileFormats.Graphics;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class VoxelRenderProxy
|
||||
{
|
||||
public readonly Sprite Sprite;
|
||||
public readonly Sprite ShadowSprite;
|
||||
public readonly float ShadowDirection;
|
||||
public readonly float2[] ProjectedShadowBounds;
|
||||
|
||||
public VoxelRenderProxy(Sprite sprite, Sprite shadowSprite, float2[] projectedShadowBounds, float shadowDirection)
|
||||
{
|
||||
Sprite = sprite;
|
||||
ShadowSprite = shadowSprite;
|
||||
ProjectedShadowBounds = projectedShadowBounds;
|
||||
ShadowDirection = shadowDirection;
|
||||
}
|
||||
}
|
||||
|
||||
public class VoxelRenderer
|
||||
{
|
||||
Renderer renderer;
|
||||
IShader shader;
|
||||
|
||||
SheetBuilder sheetBuilder;
|
||||
Dictionary<Sheet, IFrameBuffer> mappedBuffers;
|
||||
Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers;
|
||||
List<Pair<Sheet, Action>> doRender;
|
||||
|
||||
// Static constants
|
||||
static readonly float[] shadowDiffuse = new float[] {0,0,0};
|
||||
static readonly float[] shadowAmbient = new float[] {1,1,1};
|
||||
static readonly float2 spritePadding = new float2(2, 2);
|
||||
static readonly float[] zeroVector = new float[] {0,0,0,1};
|
||||
static readonly float[] zVector = new float[] {0,0,1,1};
|
||||
static readonly float[] flipMtx = Util.ScaleMatrix(1, -1, 1);
|
||||
static readonly float[] shadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2);
|
||||
|
||||
public VoxelRenderer(Renderer renderer, IShader shader)
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
|
||||
mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
|
||||
unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
|
||||
doRender = new List<Pair<Sheet, Action>>();
|
||||
}
|
||||
|
||||
public void SetPalette(ITexture palette)
|
||||
{
|
||||
shader.SetTexture("Palette", palette);
|
||||
}
|
||||
|
||||
public void SetViewportParams(Size screen, float zoom, float2 scroll)
|
||||
{
|
||||
var a = 2f / Renderer.SheetSize;
|
||||
var view = new float[]
|
||||
{
|
||||
a, 0, 0, 0,
|
||||
0, -a, 0, 0,
|
||||
0, 0, -2*a, 0,
|
||||
-1, 1, 0, 1
|
||||
};
|
||||
|
||||
shader.SetMatrix("View", view);
|
||||
}
|
||||
|
||||
public VoxelRenderProxy RenderAsync(WorldRenderer wr, IEnumerable<VoxelAnimation> voxels, WRot camera, float scale,
|
||||
float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
|
||||
{
|
||||
// Correct for inverted y-axis
|
||||
var scaleTransform = Util.ScaleMatrix(scale, scale, scale);
|
||||
|
||||
// Correct for bogus light source definition
|
||||
var lightYaw = Util.MakeFloatMatrix(new WRot(WAngle.Zero, WAngle.Zero, -lightSource.Yaw).AsMatrix());
|
||||
var lightPitch = Util.MakeFloatMatrix(new WRot(WAngle.Zero, -lightSource.Pitch, WAngle.Zero).AsMatrix());
|
||||
var shadowTransform = Util.MatrixMultiply(lightPitch, lightYaw);
|
||||
|
||||
var invShadowTransform = Util.MatrixInverse(shadowTransform);
|
||||
var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
|
||||
var invCameraTransform = Util.MatrixInverse(cameraTransform);
|
||||
|
||||
// Sprite rectangle
|
||||
var tl = new float2(float.MaxValue, float.MaxValue);
|
||||
var br = new float2(float.MinValue, float.MinValue);
|
||||
|
||||
// Shadow sprite rectangle
|
||||
var stl = new float2(float.MaxValue, float.MaxValue);
|
||||
var sbr = new float2(float.MinValue, float.MinValue);
|
||||
|
||||
foreach (var v in voxels)
|
||||
{
|
||||
// Convert screen offset back to world coords
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(v.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var worldTransform = v.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 worldBounds = Util.MatrixAABBMultiply(worldTransform, bounds);
|
||||
var screenBounds = Util.MatrixAABBMultiply(cameraTransform, worldBounds);
|
||||
var shadowBounds = Util.MatrixAABBMultiply(shadowTransform, worldBounds);
|
||||
|
||||
// Aggregate bounds rects
|
||||
tl = float2.Min(tl, new float2(screenBounds[0], screenBounds[1]));
|
||||
br = float2.Max(br, new float2(screenBounds[3], screenBounds[4]));
|
||||
stl = float2.Min(stl, new float2(shadowBounds[0], shadowBounds[1]));
|
||||
sbr = float2.Max(sbr, new float2(shadowBounds[3], shadowBounds[4]));
|
||||
}
|
||||
|
||||
// Inflate rects to ensure rendering is within bounds
|
||||
tl -= spritePadding;
|
||||
br += spritePadding;
|
||||
stl -= spritePadding;
|
||||
sbr += spritePadding;
|
||||
|
||||
// Corners of the shadow quad, in shadow-space
|
||||
var corners = new float[][]
|
||||
{
|
||||
new float[] {stl.X, stl.Y, 0, 1},
|
||||
new float[] {sbr.X, sbr.Y, 0, 1},
|
||||
new float[] {sbr.X, stl.Y, 0, 1},
|
||||
new float[] {stl.X, sbr.Y, 0, 1}
|
||||
};
|
||||
|
||||
var shadowScreenTransform = Util.MatrixMultiply(cameraTransform, invShadowTransform);
|
||||
var shadowGroundNormal = Util.MatrixVectorMultiply(shadowTransform, groundNormal);
|
||||
var screenCorners = new float2[4];
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
// Project to ground plane
|
||||
corners[j][2] = -(corners[j][1]*shadowGroundNormal[1]/shadowGroundNormal[2] +
|
||||
corners[j][0]*shadowGroundNormal[0]/shadowGroundNormal[2]);
|
||||
|
||||
// Rotate to camera-space
|
||||
corners[j] = Util.MatrixVectorMultiply(shadowScreenTransform, corners[j]);
|
||||
screenCorners[j] = new float2(corners[j][0], corners[j][1]);
|
||||
}
|
||||
|
||||
// Shadows are rendered at twice the resolution to reduce artefacts
|
||||
Size spriteSize, shadowSpriteSize;
|
||||
int2 spriteOffset, shadowSpriteOffset;
|
||||
CalculateSpriteGeometry(tl, br, 1, out spriteSize, out spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out shadowSpriteSize, out shadowSpriteOffset);
|
||||
|
||||
var sprite = sheetBuilder.Allocate(spriteSize, spriteOffset);
|
||||
var shadowSprite = sheetBuilder.Allocate(shadowSpriteSize, shadowSpriteOffset);
|
||||
var sb = sprite.bounds;
|
||||
var ssb = shadowSprite.bounds;
|
||||
var spriteCenter = new float2(sb.Left + sb.Width / 2, sb.Top + sb.Height / 2);
|
||||
var shadowCenter = new float2(ssb.Left + ssb.Width / 2, ssb.Top + ssb.Height / 2);
|
||||
|
||||
var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, Renderer.SheetSize - (spriteCenter.Y - spriteOffset.Y), 0);
|
||||
var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, Renderer.SheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
|
||||
var correctionTransform = Util.MatrixMultiply(translateMtx, flipMtx);
|
||||
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, shadowScaleFlipMtx);
|
||||
|
||||
doRender.Add(Pair.New<Sheet, Action>(sprite.sheet, () =>
|
||||
{
|
||||
foreach (var v in voxels)
|
||||
{
|
||||
// Convert screen offset to world offset
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(v.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var rotations = v.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x,y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
var worldTransform = Util.MatrixMultiply(scaleTransform, rotations);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
var transform = Util.MatrixMultiply(cameraTransform, worldTransform);
|
||||
transform = Util.MatrixMultiply(correctionTransform, transform);
|
||||
|
||||
var shadow = Util.MatrixMultiply(shadowTransform, worldTransform);
|
||||
shadow = Util.MatrixMultiply(shadowCorrectionTransform, shadow);
|
||||
|
||||
var lightTransform = Util.MatrixMultiply(Util.MatrixInverse(rotations), invShadowTransform);
|
||||
|
||||
var frame = v.FrameFunc();
|
||||
for (uint i = 0; i < v.Voxel.Limbs; i++)
|
||||
{
|
||||
var rd = v.Voxel.RenderData(i);
|
||||
var t = v.Voxel.TransformationMatrix(i, frame);
|
||||
|
||||
// Transform light vector from shadow -> world -> limb coords
|
||||
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(Util.MatrixInverse(t), lightTransform));
|
||||
|
||||
Render(rd, Util.MatrixMultiply(transform, t), lightDirection,
|
||||
lightAmbientColor, lightDiffuseColor, color.Index, normals.Index);
|
||||
|
||||
// Disable shadow normals by forcing zero diffuse and identity ambient light
|
||||
Render(rd, Util.MatrixMultiply(shadow, t), lightDirection,
|
||||
shadowAmbient, shadowDiffuse, shadowPalette.Index, normals.Index);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, zVector);
|
||||
screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
|
||||
return new VoxelRenderProxy(sprite, shadowSprite, screenCorners, -screenLightVector[2]/screenLightVector[1]);
|
||||
}
|
||||
|
||||
static void CalculateSpriteGeometry(float2 tl, float2 br, float scale, out Size size, out int2 offset)
|
||||
{
|
||||
var width = (int)(scale*(br.X - tl.X));
|
||||
var height = (int)(scale*(br.Y - tl.Y));
|
||||
offset = (0.5f*scale*(br + tl)).ToInt2();
|
||||
|
||||
// Width and height must be even to avoid rendering glitches
|
||||
if ((width & 1) == 1)
|
||||
width += 1;
|
||||
if ((height & 1) == 1)
|
||||
height += 1;
|
||||
|
||||
size = new Size(width, height);
|
||||
}
|
||||
|
||||
static float[] ExtractRotationVector(float[] mtx)
|
||||
{
|
||||
var tVec = Util.MatrixVectorMultiply(mtx, zVector);
|
||||
var tOrigin = Util.MatrixVectorMultiply(mtx, zeroVector);
|
||||
tVec[0] -= tOrigin[0]*tVec[3]/tOrigin[3];
|
||||
tVec[1] -= tOrigin[1]*tVec[3]/tOrigin[3];
|
||||
tVec[2] -= tOrigin[2]*tVec[3]/tOrigin[3];
|
||||
|
||||
// Renormalize
|
||||
var w = (float)Math.Sqrt(tVec[0]*tVec[0] + tVec[1]*tVec[1] + tVec[2]*tVec[2]);
|
||||
tVec[0] /= w;
|
||||
tVec[1] /= w;
|
||||
tVec[2] /= w;
|
||||
tVec[3] = 1f;
|
||||
|
||||
return tVec;
|
||||
}
|
||||
|
||||
void Render(VoxelRenderData renderData,
|
||||
float[] t, float[] lightDirection,
|
||||
float[] ambientLight, float[] diffuseLight,
|
||||
int colorPalette, int normalsPalette)
|
||||
{
|
||||
shader.SetTexture("DiffuseTexture", renderData.Sheet.Texture);
|
||||
shader.SetVec("PaletteRows", (colorPalette + 0.5f) / HardwarePalette.MaxPalettes,
|
||||
(normalsPalette + 0.5f) / HardwarePalette.MaxPalettes);
|
||||
shader.SetMatrix("TransformMatrix", t);
|
||||
shader.SetVec("LightDirection", lightDirection, 4);
|
||||
shader.SetVec("AmbientLight", ambientLight, 3);
|
||||
shader.SetVec("DiffuseLight", diffuseLight, 3);
|
||||
|
||||
shader.Render(() => renderer.DrawBatch(Game.modData.VoxelLoader.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.QuadList));
|
||||
}
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
foreach (var kv in mappedBuffers)
|
||||
unmappedBuffers.Push(kv);
|
||||
mappedBuffers.Clear();
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, AllocateSheet);
|
||||
doRender.Clear();
|
||||
}
|
||||
|
||||
IFrameBuffer EnableFrameBuffer(Sheet s)
|
||||
{
|
||||
var fbo = mappedBuffers[s];
|
||||
Game.Renderer.Flush();
|
||||
fbo.Bind();
|
||||
|
||||
Game.Renderer.Device.EnableDepthBuffer();
|
||||
return fbo;
|
||||
}
|
||||
|
||||
void DisableFrameBuffer(IFrameBuffer fbo)
|
||||
{
|
||||
Game.Renderer.Flush();
|
||||
Game.Renderer.Device.DisableDepthBuffer();
|
||||
fbo.Unbind();
|
||||
}
|
||||
|
||||
public void EndFrame()
|
||||
{
|
||||
if (doRender.Count == 0)
|
||||
return;
|
||||
|
||||
Sheet currentSheet = null;
|
||||
IFrameBuffer fbo = null;
|
||||
foreach (var v in doRender)
|
||||
{
|
||||
// Change sheet
|
||||
if (v.First != currentSheet)
|
||||
{
|
||||
if (fbo != null)
|
||||
DisableFrameBuffer(fbo);
|
||||
|
||||
currentSheet = v.First;
|
||||
fbo = EnableFrameBuffer(currentSheet);
|
||||
}
|
||||
|
||||
v.Second();
|
||||
}
|
||||
|
||||
DisableFrameBuffer(fbo);
|
||||
}
|
||||
|
||||
public Sheet AllocateSheet()
|
||||
{
|
||||
// Reuse cached fbo
|
||||
if (unmappedBuffers.Count > 0)
|
||||
{
|
||||
var kv = unmappedBuffers.Pop();
|
||||
mappedBuffers.Add(kv.Key, kv.Value);
|
||||
return kv.Key;
|
||||
}
|
||||
|
||||
var size = new Size(Renderer.SheetSize, Renderer.SheetSize);
|
||||
var framebuffer = renderer.Device.CreateFrameBuffer(size);
|
||||
var sheet = new Sheet(framebuffer.Texture);
|
||||
mappedBuffers.Add(sheet, framebuffer);
|
||||
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ namespace OpenRA.Graphics
|
||||
internal readonly ShroudRenderer shroudRenderer;
|
||||
internal readonly HardwarePalette palette;
|
||||
internal Cache<string, PaletteReference> palettes;
|
||||
Lazy<DeveloperMode> devTrait;
|
||||
|
||||
internal WorldRenderer(World world)
|
||||
{
|
||||
@@ -51,6 +52,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
terrainRenderer = new TerrainRenderer(world, this);
|
||||
shroudRenderer = new ShroudRenderer(world);
|
||||
|
||||
devTrait = Lazy.New(() => world.LocalPlayer != null ? world.LocalPlayer.PlayerActor.Trait<DeveloperMode>() : null);
|
||||
}
|
||||
|
||||
PaletteReference CreatePaletteReference(string name)
|
||||
@@ -65,30 +68,32 @@ namespace OpenRA.Graphics
|
||||
public PaletteReference Palette(string name) { return palettes[name]; }
|
||||
public void AddPalette(string name, Palette pal, bool allowModifiers) { palette.AddPalette(name, pal, allowModifiers); }
|
||||
|
||||
class SpriteComparer : IComparer<Renderable>
|
||||
{
|
||||
public int Compare(Renderable x, Renderable y)
|
||||
{
|
||||
return (x.Z + x.ZOffset).CompareTo(y.Z + y.ZOffset);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<Renderable> SpritesToRender()
|
||||
List<IRenderable> GenerateRenderables()
|
||||
{
|
||||
var bounds = Game.viewport.WorldBounds(world);
|
||||
var comparer = new SpriteComparer();
|
||||
var comparer = new RenderableComparer(this);
|
||||
|
||||
var actors = world.FindUnits(
|
||||
bounds.TopLeftAsCPos().ToPPos(),
|
||||
bounds.BottomRightAsCPos().ToPPos()
|
||||
);
|
||||
bounds.BottomRightAsCPos().ToPPos());
|
||||
|
||||
var renderables = actors.SelectMany(a => a.Render(this))
|
||||
var worldRenderables = actors.SelectMany(a => a.Render(this))
|
||||
.OrderBy(r => r, comparer);
|
||||
|
||||
var effects = world.Effects.SelectMany(e => e.Render(this));
|
||||
// Effects are drawn on top of all actors
|
||||
// TODO: Allow effects to be interleaved with actors
|
||||
var effectRenderables = world.Effects
|
||||
.SelectMany(e => e.Render(this));
|
||||
|
||||
return renderables.Concat(effects);
|
||||
// Iterating via foreach() copies the structs, so enumerate by index
|
||||
var renderables = worldRenderables.Concat(effectRenderables).ToList();
|
||||
|
||||
Game.Renderer.WorldVoxelRenderer.BeginFrame();
|
||||
for (var i = 0; i < renderables.Count; i++)
|
||||
renderables[i].BeforeRender(this);
|
||||
Game.Renderer.WorldVoxelRenderer.EndFrame();
|
||||
|
||||
return renderables;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
@@ -98,13 +103,14 @@ namespace OpenRA.Graphics
|
||||
if (world.IsShellmap && !Game.Settings.Game.ShowShellmap)
|
||||
return;
|
||||
|
||||
var renderables = GenerateRenderables();
|
||||
var bounds = Game.viewport.ViewBounds(world);
|
||||
Game.Renderer.EnableScissor(bounds.Left, bounds.Top, bounds.Width, bounds.Height);
|
||||
|
||||
terrainRenderer.Draw(this, Game.viewport);
|
||||
foreach (var a in world.traitDict.ActorsWithTraitMultiple<IRenderAsTerrain>(world))
|
||||
foreach (var r in a.Trait.RenderAsTerrain(this, a.Actor))
|
||||
r.Sprite.DrawAt(r.Pos, r.Palette.Index, r.Scale);
|
||||
r.Render(this);
|
||||
|
||||
foreach (var a in world.Selection.Actors)
|
||||
if (!a.Destroyed)
|
||||
@@ -116,8 +122,8 @@ namespace OpenRA.Graphics
|
||||
if (world.OrderGenerator != null)
|
||||
world.OrderGenerator.RenderBeforeWorld(this, world);
|
||||
|
||||
foreach (var image in SpritesToRender())
|
||||
image.Sprite.DrawAt(image.Pos, image.Palette.Index, image.Scale);
|
||||
for (var i = 0; i < renderables.Count; i++)
|
||||
renderables[i].Render(this);
|
||||
|
||||
// added for contrails
|
||||
foreach (var a in world.ActorsWithTrait<IPostRender>())
|
||||
@@ -129,6 +135,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
var renderShroud = world.RenderPlayer != null ? world.RenderPlayer.Shroud : null;
|
||||
shroudRenderer.Draw(this, renderShroud);
|
||||
|
||||
if (devTrait.Value != null && devTrait.Value.ShowDebugGeometry)
|
||||
for (var i = 0; i < renderables.Count; i++)
|
||||
renderables[i].RenderDebugGeometry(this);
|
||||
|
||||
Game.Renderer.DisableScissor();
|
||||
|
||||
foreach (var g in world.Selection.Actors.Where(a => !a.Destroyed)
|
||||
@@ -222,19 +233,18 @@ namespace OpenRA.Graphics
|
||||
|
||||
public int2 ScreenPxPosition(WPos pos)
|
||||
{
|
||||
return new int2(Game.CellSize*pos.X/1024, Game.CellSize*(pos.Y - pos.Z)/1024);
|
||||
}
|
||||
public float ScreenZOffset(WPos pos) { return pos.Z*Game.CellSize/1024f; }
|
||||
|
||||
public int2 ScreenPxOffset(WVec vec)
|
||||
{
|
||||
return new int2(Game.CellSize*vec.X/1024, Game.CellSize*(vec.Y - vec.Z)/1024);
|
||||
// Round to nearest pixel
|
||||
var px = ScreenPosition(pos);
|
||||
return new int2((int)Math.Round(px.X), (int)Math.Round(px.Y));
|
||||
}
|
||||
|
||||
public float[] ScreenOffset(WVec vec)
|
||||
// For scaling vectors to pixel sizes in the voxel renderer
|
||||
public float[] ScreenVector(WVec vec)
|
||||
{
|
||||
var c = Game.CellSize/1024f;
|
||||
return new float[] {c*vec.X, c*vec.Y, c*vec.Z};
|
||||
return new float[] {c*vec.X, c*vec.Y, c*vec.Z, 1};
|
||||
}
|
||||
|
||||
public float ScreenZPosition(WPos pos, int zOffset) { return (pos.Y + pos.Z + zOffset)*Game.CellSize/1024f; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace OpenRA
|
||||
|
||||
[FieldLoader.Ignore] public List<MiniYamlNode> Rules = new List<MiniYamlNode>();
|
||||
[FieldLoader.Ignore] public List<MiniYamlNode> Sequences = new List<MiniYamlNode>();
|
||||
[FieldLoader.Ignore] public List<MiniYamlNode> VoxelSequences = new List<MiniYamlNode>();
|
||||
[FieldLoader.Ignore] public List<MiniYamlNode> Weapons = new List<MiniYamlNode>();
|
||||
[FieldLoader.Ignore] public List<MiniYamlNode> Voices = new List<MiniYamlNode>();
|
||||
[FieldLoader.Ignore] public List<MiniYamlNode> Notifications = new List<MiniYamlNode>();
|
||||
@@ -104,7 +105,7 @@ namespace OpenRA
|
||||
public Map(string path)
|
||||
{
|
||||
Path = path;
|
||||
Container = FileSystem.OpenPackage(path, int.MaxValue);
|
||||
Container = FileSystem.OpenPackage(path, null, int.MaxValue);
|
||||
|
||||
AssertExists("map.yaml");
|
||||
AssertExists("map.bin");
|
||||
@@ -150,6 +151,7 @@ namespace OpenRA
|
||||
|
||||
Rules = NodesOrEmpty(yaml, "Rules");
|
||||
Sequences = NodesOrEmpty(yaml, "Sequences");
|
||||
VoxelSequences = NodesOrEmpty(yaml, "VoxelSequences");
|
||||
Weapons = NodesOrEmpty(yaml, "Weapons");
|
||||
Voices = NodesOrEmpty(yaml, "Voices");
|
||||
Notifications = NodesOrEmpty(yaml, "Notifications");
|
||||
@@ -206,6 +208,7 @@ namespace OpenRA
|
||||
root.Add(new MiniYamlNode("Smudges", MiniYaml.FromList<SmudgeReference>( Smudges.Value )));
|
||||
root.Add(new MiniYamlNode("Rules", null, Rules));
|
||||
root.Add(new MiniYamlNode("Sequences", null, Sequences));
|
||||
root.Add(new MiniYamlNode("VoxelSequences", null, VoxelSequences));
|
||||
root.Add(new MiniYamlNode("Weapons", null, Weapons));
|
||||
root.Add(new MiniYamlNode("Voices", null, Voices));
|
||||
root.Add(new MiniYamlNode("Notifications", null, Notifications));
|
||||
@@ -229,44 +232,27 @@ namespace OpenRA
|
||||
Container.Write(entries);
|
||||
}
|
||||
|
||||
static byte ReadByte(Stream s)
|
||||
{
|
||||
int ret = s.ReadByte();
|
||||
if (ret == -1)
|
||||
throw new NotImplementedException();
|
||||
return (byte)ret;
|
||||
}
|
||||
|
||||
static ushort ReadWord(Stream s)
|
||||
{
|
||||
ushort ret = ReadByte(s);
|
||||
ret |= (ushort)(ReadByte(s) << 8);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public TileReference<ushort, byte>[,] LoadMapTiles()
|
||||
{
|
||||
var tiles = new TileReference<ushort, byte>[MapSize.X, MapSize.Y];
|
||||
using (var dataStream = Container.GetContent("map.bin"))
|
||||
{
|
||||
if (ReadByte(dataStream) != 1)
|
||||
if (dataStream.ReadUInt8() != 1)
|
||||
throw new InvalidDataException("Unknown binary map format");
|
||||
|
||||
// Load header info
|
||||
var width = ReadWord(dataStream);
|
||||
var height = ReadWord(dataStream);
|
||||
var width = dataStream.ReadUInt16();
|
||||
var height = dataStream.ReadUInt16();
|
||||
|
||||
if (width != MapSize.X || height != MapSize.Y)
|
||||
throw new InvalidDataException("Invalid tile data");
|
||||
|
||||
|
||||
// Load tile data
|
||||
for (int i = 0; i < MapSize.X; i++)
|
||||
for (int j = 0; j < MapSize.Y; j++)
|
||||
{
|
||||
ushort tile = ReadWord(dataStream);
|
||||
byte index = ReadByte(dataStream);
|
||||
var tile = dataStream.ReadUInt16();
|
||||
var index = dataStream.ReadUInt8();
|
||||
if (index == byte.MaxValue)
|
||||
index = (byte)(i % 4 + (j % 4) * 4);
|
||||
|
||||
@@ -282,26 +268,25 @@ namespace OpenRA
|
||||
|
||||
using (var dataStream = Container.GetContent("map.bin"))
|
||||
{
|
||||
if (ReadByte(dataStream) != 1)
|
||||
if (dataStream.ReadUInt8() != 1)
|
||||
throw new InvalidDataException("Unknown binary map format");
|
||||
|
||||
// Load header info
|
||||
var width = ReadWord(dataStream);
|
||||
var height = ReadWord(dataStream);
|
||||
var width = dataStream.ReadUInt16();
|
||||
var height = dataStream.ReadUInt16();
|
||||
|
||||
if (width != MapSize.X || height != MapSize.Y)
|
||||
throw new InvalidDataException("Invalid tile data");
|
||||
|
||||
// Skip past tile data
|
||||
for (var i = 0; i < 3*MapSize.X*MapSize.Y; i++)
|
||||
ReadByte(dataStream);
|
||||
dataStream.Seek(3*MapSize.X*MapSize.Y, SeekOrigin.Current);
|
||||
|
||||
// Load resource data
|
||||
for (var i = 0; i < MapSize.X; i++)
|
||||
for (var j = 0; j < MapSize.Y; j++)
|
||||
{
|
||||
byte type = ReadByte(dataStream);
|
||||
byte index = ReadByte(dataStream);
|
||||
var type = dataStream.ReadUInt8();
|
||||
var index = dataStream.ReadUInt8();
|
||||
resources[i, j] = new TileReference<byte, byte>(type, index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,33 +28,35 @@ namespace OpenRA
|
||||
public ILoadScreen LoadScreen = null;
|
||||
public SheetBuilder SheetBuilder;
|
||||
public SpriteLoader SpriteLoader;
|
||||
public VoxelLoader VoxelLoader;
|
||||
|
||||
public ModData( params string[] mods )
|
||||
public ModData(params string[] mods)
|
||||
{
|
||||
Manifest = new Manifest( mods );
|
||||
ObjectCreator = new ObjectCreator( Manifest );
|
||||
Manifest = new Manifest(mods);
|
||||
ObjectCreator = new ObjectCreator(Manifest);
|
||||
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);
|
||||
LoadScreen.Init(Manifest.LoadScreen.NodesDict.ToDictionary(x => x.Key, x => x.Value.Value));
|
||||
LoadScreen.Display();
|
||||
WidgetLoader = new WidgetLoader( this );
|
||||
}
|
||||
WidgetLoader = new WidgetLoader(this);
|
||||
|
||||
public void LoadInitialAssets(bool enumMaps)
|
||||
{
|
||||
// all this manipulation of static crap here is nasty and breaks
|
||||
// horribly when you use ModData in unexpected ways.
|
||||
AvailableMaps = FindMaps(Manifest.Mods);
|
||||
|
||||
// HACK: Mount only local folders so we have a half-working environment for the asset installer
|
||||
FileSystem.UnmountAll();
|
||||
foreach (var dir in Manifest.Folders)
|
||||
FileSystem.Mount(dir);
|
||||
|
||||
if (enumMaps)
|
||||
AvailableMaps = FindMaps(Manifest.Mods);
|
||||
}
|
||||
|
||||
public void InitializeLoaders()
|
||||
{
|
||||
// all this manipulation of static crap here is nasty and breaks
|
||||
// horribly when you use ModData in unexpected ways.
|
||||
ChromeMetrics.Initialize(Manifest.ChromeMetrics);
|
||||
ChromeProvider.Initialize(Manifest.Chrome);
|
||||
SheetBuilder = new SheetBuilder(TextureChannel.Red);
|
||||
SheetBuilder = new SheetBuilder(SheetType.Indexed);
|
||||
SpriteLoader = new SpriteLoader(new string[] { ".shp" }, SheetBuilder);
|
||||
VoxelLoader = new VoxelLoader();
|
||||
CursorProvider.Initialize(Manifest.Cursors);
|
||||
}
|
||||
|
||||
@@ -66,18 +68,17 @@ namespace OpenRA
|
||||
var map = new Map(AvailableMaps[uid].Path);
|
||||
|
||||
// Reinit all our assets
|
||||
LoadInitialAssets(false);
|
||||
foreach (var pkg in Manifest.Packages)
|
||||
FileSystem.Mount(pkg);
|
||||
InitializeLoaders();
|
||||
FileSystem.LoadFromManifest(Manifest);
|
||||
|
||||
// Mount map package so custom assets can be used. TODO: check priority.
|
||||
FileSystem.Mount(FileSystem.OpenPackage(map.Path, int.MaxValue));
|
||||
FileSystem.Mount(FileSystem.OpenPackage(map.Path, null, int.MaxValue));
|
||||
|
||||
Rules.LoadRules(Manifest, map);
|
||||
SpriteLoader = new SpriteLoader(Rules.TileSets[map.Tileset].Extensions, SheetBuilder);
|
||||
// TODO: Don't load the sequences for assets that are not used in this tileset. Maybe use the existing EditorTilesetFilters.
|
||||
SequenceProvider.Initialize(Manifest.Sequences, map.Sequences);
|
||||
|
||||
VoxelProvider.Initialize(Manifest.VoxelSequences, map.VoxelSequences);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ namespace OpenRA.Network
|
||||
{
|
||||
int LocalClientId { get; }
|
||||
ConnectionState ConnectionState { get; }
|
||||
void Send( int frame, List<byte[]> orders );
|
||||
void SendImmediate( List<byte[]> orders );
|
||||
void SendSync( int frame, byte[] syncData );
|
||||
void Receive( Action<int, byte[]> packetFn );
|
||||
void Send(int frame, List<byte[]> orders);
|
||||
void SendImmediate(List<byte[]> orders);
|
||||
void SendSync(int frame, byte[] syncData);
|
||||
void Receive(Action<int, byte[]> packetFn);
|
||||
}
|
||||
|
||||
class EchoConnection : IConnection
|
||||
@@ -55,51 +55,51 @@ namespace OpenRA.Network
|
||||
get { return ConnectionState.PreConnecting; }
|
||||
}
|
||||
|
||||
public virtual void Send( int frame, List<byte[]> orders )
|
||||
public virtual void Send(int frame, List<byte[]> orders)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Write( BitConverter.GetBytes( frame ) );
|
||||
foreach( var o in orders )
|
||||
ms.Write( o );
|
||||
Send( ms.ToArray() );
|
||||
ms.Write(BitConverter.GetBytes(frame));
|
||||
foreach (var o in orders)
|
||||
ms.Write(o);
|
||||
Send(ms.ToArray());
|
||||
}
|
||||
|
||||
public virtual void SendImmediate( List<byte[]> orders )
|
||||
public virtual void SendImmediate(List<byte[]> orders)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Write( BitConverter.GetBytes( (int)0 ) );
|
||||
foreach( var o in orders )
|
||||
ms.Write( o );
|
||||
Send( ms.ToArray() );
|
||||
ms.Write(BitConverter.GetBytes((int)0));
|
||||
foreach (var o in orders)
|
||||
ms.Write(o);
|
||||
Send(ms.ToArray());
|
||||
}
|
||||
|
||||
public virtual void SendSync( int frame, byte[] syncData )
|
||||
public virtual void SendSync(int frame, byte[] syncData)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Write( BitConverter.GetBytes( frame ) );
|
||||
ms.Write( syncData );
|
||||
Send( ms.ToArray() );
|
||||
ms.Write(BitConverter.GetBytes(frame));
|
||||
ms.Write(syncData);
|
||||
Send(ms.ToArray());
|
||||
}
|
||||
|
||||
protected virtual void Send( byte[] packet )
|
||||
protected virtual void Send(byte[] packet)
|
||||
{
|
||||
if( packet.Length == 0 )
|
||||
if (packet.Length == 0)
|
||||
throw new NotImplementedException();
|
||||
lock( this )
|
||||
receivedPackets.Add( new ReceivedPacket { FromClient = LocalClientId, Data = packet } );
|
||||
lock (this)
|
||||
receivedPackets.Add(new ReceivedPacket { FromClient = LocalClientId, Data = packet } );
|
||||
}
|
||||
|
||||
public virtual void Receive( Action<int, byte[]> packetFn )
|
||||
public virtual void Receive(Action<int, byte[]> packetFn)
|
||||
{
|
||||
List<ReceivedPacket> packets;
|
||||
lock( this )
|
||||
lock (this)
|
||||
{
|
||||
packets = receivedPackets;
|
||||
receivedPackets = new List<ReceivedPacket>();
|
||||
}
|
||||
|
||||
foreach( var p in packets )
|
||||
packetFn( p.FromClient, p.Data );
|
||||
foreach (var p in packets)
|
||||
packetFn(p.FromClient, p.Data);
|
||||
}
|
||||
|
||||
public virtual void Dispose() { }
|
||||
@@ -112,15 +112,15 @@ namespace OpenRA.Network
|
||||
ConnectionState connectionState = ConnectionState.Connecting;
|
||||
Thread t;
|
||||
|
||||
public NetworkConnection( string host, int port )
|
||||
public NetworkConnection(string host, int port)
|
||||
{
|
||||
t = new Thread( _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
socket = new TcpClient( host, port );
|
||||
socket = new TcpClient(host, port);
|
||||
socket.NoDelay = true;
|
||||
var reader = new BinaryReader( socket.GetStream() );
|
||||
var reader = new BinaryReader(socket.GetStream());
|
||||
var serverProtocol = reader.ReadInt32();
|
||||
|
||||
if (ProtocolVersion.Version != serverProtocol)
|
||||
@@ -131,22 +131,22 @@ namespace OpenRA.Network
|
||||
clientId = reader.ReadInt32();
|
||||
connectionState = ConnectionState.Connected;
|
||||
|
||||
for( ; ; )
|
||||
for (;;)
|
||||
{
|
||||
var len = reader.ReadInt32();
|
||||
var client = reader.ReadInt32();
|
||||
var buf = reader.ReadBytes( len );
|
||||
if( len == 0 )
|
||||
if (len == 0)
|
||||
throw new NotImplementedException();
|
||||
lock( this )
|
||||
receivedPackets.Add( new ReceivedPacket { FromClient = client, Data = buf } );
|
||||
lock (this)
|
||||
receivedPackets.Add(new ReceivedPacket { FromClient = client, Data = buf } );
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
connectionState = ConnectionState.NotConnected;
|
||||
if( socket != null )
|
||||
if (socket != null)
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
@@ -159,28 +159,28 @@ namespace OpenRA.Network
|
||||
|
||||
List<byte[]> queuedSyncPackets = new List<byte[]>();
|
||||
|
||||
public override void SendSync( int frame, byte[] syncData )
|
||||
public override void SendSync(int frame, byte[] syncData)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Write( BitConverter.GetBytes( frame ) );
|
||||
ms.Write( syncData );
|
||||
queuedSyncPackets.Add( ms.ToArray() );
|
||||
ms.Write(BitConverter.GetBytes(frame));
|
||||
ms.Write(syncData);
|
||||
queuedSyncPackets.Add(ms.ToArray());
|
||||
}
|
||||
|
||||
protected override void Send( byte[] packet )
|
||||
protected override void Send(byte[] packet)
|
||||
{
|
||||
base.Send( packet );
|
||||
base.Send(packet);
|
||||
|
||||
try
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Write(BitConverter.GetBytes((int)packet.Length));
|
||||
ms.Write(packet);
|
||||
foreach( var q in queuedSyncPackets )
|
||||
foreach (var q in queuedSyncPackets)
|
||||
{
|
||||
ms.Write( BitConverter.GetBytes( (int)q.Length ) );
|
||||
ms.Write( q );
|
||||
base.Send( q );
|
||||
ms.Write(BitConverter.GetBytes((int)q.Length));
|
||||
ms.Write(q);
|
||||
base.Send(q);
|
||||
}
|
||||
queuedSyncPackets.Clear();
|
||||
ms.WriteTo(socket.GetStream());
|
||||
@@ -192,16 +192,16 @@ namespace OpenRA.Network
|
||||
|
||||
bool disposed = false;
|
||||
|
||||
public override void Dispose ()
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
GC.SuppressFinalize( this );
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
t.Abort();
|
||||
if (socket != null)
|
||||
socket.Client.Close();
|
||||
using( new PerfSample( "Thread.Join" ))
|
||||
using (new PerfSample("Thread.Join"))
|
||||
{
|
||||
if (!t.Join(1000))
|
||||
return;
|
||||
|
||||
@@ -168,16 +168,16 @@ namespace OpenRA
|
||||
return a.ActorID;
|
||||
}
|
||||
|
||||
static bool TryGetActorFromUInt(World world, uint aID, out Actor ret )
|
||||
static bool TryGetActorFromUInt(World world, uint aID, out Actor ret)
|
||||
{
|
||||
if( aID == 0xFFFFFFFF )
|
||||
if (aID == 0xFFFFFFFF)
|
||||
{
|
||||
ret = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( var a in world.Actors.Where( x => x.ActorID == aID ) )
|
||||
foreach (var a in world.Actors.Where(x => x.ActorID == aID))
|
||||
{
|
||||
ret = a;
|
||||
return true;
|
||||
|
||||
@@ -21,13 +21,14 @@ namespace OpenRA.Network
|
||||
readonly SyncReport syncReport;
|
||||
readonly FrameData frameData = new FrameData();
|
||||
|
||||
public Session LobbyInfo = new Session( Game.Settings.Game.Mods );
|
||||
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex( Connection.LocalClientId ); } }
|
||||
public Session LobbyInfo = new Session(Game.Settings.Game.Mods);
|
||||
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
|
||||
public World world;
|
||||
|
||||
public readonly string Host;
|
||||
public readonly int Port;
|
||||
public string ServerError;
|
||||
|
||||
public string ServerError = "Server is not responding.";
|
||||
|
||||
public int NetFrameNumber { get; private set; }
|
||||
public int LocalFrameNumber;
|
||||
@@ -47,27 +48,27 @@ namespace OpenRA.Network
|
||||
if (GameStarted) return;
|
||||
|
||||
NetFrameNumber = 1;
|
||||
for( int i = NetFrameNumber ; i <= FramesAhead ; i++ )
|
||||
Connection.Send( i, new List<byte[]>() );
|
||||
for (var i = NetFrameNumber ; i <= FramesAhead ; i++)
|
||||
Connection.Send(i, new List<byte[]>());
|
||||
}
|
||||
|
||||
public OrderManager( string host, int port, IConnection conn )
|
||||
public OrderManager(string host, int port, IConnection conn)
|
||||
{
|
||||
this.Host = host;
|
||||
this.Port = port;
|
||||
Connection = conn;
|
||||
syncReport = new SyncReport( this );
|
||||
syncReport = new SyncReport(this);
|
||||
}
|
||||
|
||||
public void IssueOrders( Order[] orders )
|
||||
public void IssueOrders(Order[] orders)
|
||||
{
|
||||
foreach( var order in orders )
|
||||
IssueOrder( order );
|
||||
foreach (var order in orders)
|
||||
IssueOrder(order);
|
||||
}
|
||||
|
||||
public void IssueOrder( Order order )
|
||||
public void IssueOrder(Order order)
|
||||
{
|
||||
localOrders.Add( order );
|
||||
localOrders.Add(order);
|
||||
}
|
||||
|
||||
public void TickImmediate()
|
||||
@@ -82,25 +83,25 @@ namespace OpenRA.Network
|
||||
Connection.Receive(
|
||||
( clientId, packet ) =>
|
||||
{
|
||||
var frame = BitConverter.ToInt32( packet, 0 );
|
||||
if( packet.Length == 5 && packet[ 4 ] == 0xBF )
|
||||
frameData.ClientQuit( clientId, frame );
|
||||
else if( packet.Length >= 5 && packet[ 4 ] == 0x65 )
|
||||
CheckSync( packet );
|
||||
else if( frame == 0 )
|
||||
immediatePackets.Add( Pair.New( clientId, packet ) );
|
||||
var frame = BitConverter.ToInt32(packet, 0);
|
||||
if (packet.Length == 5 && packet[4] == 0xBF)
|
||||
frameData.ClientQuit(clientId, frame);
|
||||
else if (packet.Length >= 5 && packet[4] == 0x65)
|
||||
CheckSync(packet);
|
||||
else if (frame == 0)
|
||||
immediatePackets.Add(Pair.New(clientId, packet));
|
||||
else
|
||||
frameData.AddFrameOrders( clientId, frame, packet );
|
||||
frameData.AddFrameOrders(clientId, frame, packet);
|
||||
} );
|
||||
|
||||
foreach( var p in immediatePackets )
|
||||
foreach( var o in p.Second.ToOrderList( world ) )
|
||||
UnitOrders.ProcessOrder( this, world, p.First, o );
|
||||
foreach (var p in immediatePackets)
|
||||
foreach (var o in p.Second.ToOrderList(world))
|
||||
UnitOrders.ProcessOrder(this, world, p.First, o);
|
||||
}
|
||||
|
||||
Dictionary<int, byte[]> syncForFrame = new Dictionary<int, byte[]>();
|
||||
|
||||
void CheckSync( byte[] packet )
|
||||
void CheckSync(byte[] packet)
|
||||
{
|
||||
var frame = BitConverter.ToInt32(packet, 0);
|
||||
byte[] existingSync;
|
||||
@@ -133,7 +134,7 @@ namespace OpenRA.Network
|
||||
|
||||
void OutOfSync(int frame, int index)
|
||||
{
|
||||
var orders = frameData.OrdersForFrame( world, frame );
|
||||
var orders = frameData.OrdersForFrame(world, frame);
|
||||
|
||||
// Invalid index
|
||||
if (index >= orders.Count())
|
||||
@@ -154,7 +155,7 @@ namespace OpenRA.Network
|
||||
|
||||
public bool IsReadyForNextFrame
|
||||
{
|
||||
get { return NetFrameNumber >= 1 && frameData.IsReadyForFrame( NetFrameNumber ); }
|
||||
get { return NetFrameNumber >= 1 && frameData.IsReadyForFrame(NetFrameNumber); }
|
||||
}
|
||||
|
||||
static readonly IEnumerable<Session.Client> NoClients = new Session.Client[] {};
|
||||
@@ -171,23 +172,23 @@ namespace OpenRA.Network
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if( !IsReadyForNextFrame )
|
||||
if (!IsReadyForNextFrame)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
Connection.Send( NetFrameNumber + FramesAhead, localOrders.Select( o => o.Serialize() ).ToList() );
|
||||
Connection.Send(NetFrameNumber + FramesAhead, localOrders.Select(o => o.Serialize()).ToList());
|
||||
localOrders.Clear();
|
||||
|
||||
var sync = new List<int>();
|
||||
sync.Add( world.SyncHash() );
|
||||
sync.Add(world.SyncHash());
|
||||
|
||||
foreach( var order in frameData.OrdersForFrame( world, NetFrameNumber) )
|
||||
foreach (var order in frameData.OrdersForFrame(world, NetFrameNumber))
|
||||
{
|
||||
UnitOrders.ProcessOrder( this, world, order.Client, order.Order );
|
||||
sync.Add( world.SyncHash() );
|
||||
UnitOrders.ProcessOrder(this, world, order.Client, order.Order);
|
||||
sync.Add(world.SyncHash());
|
||||
}
|
||||
|
||||
var ss = sync.SerializeSync();
|
||||
Connection.SendSync( NetFrameNumber, ss );
|
||||
Connection.SendSync(NetFrameNumber, ss);
|
||||
|
||||
syncReport.UpdateSyncReport();
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@ namespace OpenRA.Network
|
||||
{
|
||||
public string ServerName;
|
||||
public string Map;
|
||||
public string[] Ban;
|
||||
public string[] Mods = { "ra" }; // mod names
|
||||
public int OrderLatency = 3; // net tick frames (x 120 = ms)
|
||||
public int RandomSeed = 0;
|
||||
|
||||
@@ -154,8 +154,10 @@ namespace OpenRA.Network
|
||||
}
|
||||
|
||||
case "ServerError":
|
||||
orderManager.ServerError = order.TargetString;
|
||||
break;
|
||||
{
|
||||
orderManager.ServerError = order.TargetString;
|
||||
break;
|
||||
}
|
||||
|
||||
case "SyncInfo":
|
||||
{
|
||||
@@ -200,13 +202,13 @@ namespace OpenRA.Network
|
||||
}
|
||||
default:
|
||||
{
|
||||
if( !order.IsImmediate )
|
||||
if (!order.IsImmediate)
|
||||
{
|
||||
var self = order.Subject;
|
||||
var health = self.TraitOrDefault<Health>();
|
||||
if( health == null || !health.IsDead )
|
||||
foreach( var t in self.TraitsImplementing<IResolveOrder>() )
|
||||
t.ResolveOrder( self, order );
|
||||
if (health == null || !health.IsDead)
|
||||
foreach (var t in self.TraitsImplementing<IResolveOrder>())
|
||||
t.ResolveOrder(self, order);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -224,6 +224,18 @@
|
||||
<Compile Include="Traits\DebugPauseState.cs" />
|
||||
<Compile Include="Network\UPnP.cs" />
|
||||
<Compile Include="Widgets\ClientTooltipRegionWidget.cs" />
|
||||
<Compile Include="Graphics\Renderable.cs" />
|
||||
<Compile Include="Traits\Render\RenderSprites.cs" />
|
||||
<Compile Include="Graphics\Voxel.cs" />
|
||||
<Compile Include="Graphics\VoxelRenderer.cs" />
|
||||
<Compile Include="Graphics\VoxelLoader.cs" />
|
||||
<Compile Include="Graphics\VoxelProvider.cs" />
|
||||
<Compile Include="Traits\BodyOrientation.cs" />
|
||||
<Compile Include="Graphics\VoxelAnimation.cs" />
|
||||
<Compile Include="Graphics\VoxelRenderable.cs" />
|
||||
<Compile Include="Graphics\TextRenderable.cs" />
|
||||
<Compile Include="Graphics\BeamRenderable.cs" />
|
||||
<Compile Include="Graphics\ContrailRenderable.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">
|
||||
|
||||
@@ -87,13 +87,19 @@ namespace OpenRA.Orders
|
||||
{
|
||||
var actorsAt = self.World.ActorMap.GetUnitsAt(xy).ToList();
|
||||
|
||||
var forceAttack = mi.Modifiers.HasModifier(Modifiers.Ctrl);
|
||||
var forceQueue = mi.Modifiers.HasModifier(Modifiers.Shift);
|
||||
var modifiers = TargetModifiers.None;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Ctrl))
|
||||
modifiers |= TargetModifiers.ForceAttack;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Shift))
|
||||
modifiers |= TargetModifiers.ForceQueue;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Alt))
|
||||
modifiers |= TargetModifiers.ForceMove;
|
||||
|
||||
string cursor = null;
|
||||
if (underCursor != null)
|
||||
if (o.Order.CanTargetActor(self, underCursor, forceAttack, forceQueue, ref cursor))
|
||||
if (o.Order.CanTargetActor(self, underCursor, modifiers, ref cursor))
|
||||
return new UnitOrderResult(self, o.Order, o.Trait, cursor, Target.FromActor(underCursor));
|
||||
if (o.Order.CanTargetLocation(self, xy, actorsAt, forceAttack, forceQueue, ref cursor))
|
||||
if (o.Order.CanTargetLocation(self, xy, actorsAt, modifiers, ref cursor))
|
||||
return new UnitOrderResult(self, o.Order, o.Trait, cursor, Target.FromCell(xy));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace OpenRA.Server
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
bool ReadDataInner( Server server )
|
||||
bool ReadDataInner(Server server)
|
||||
{
|
||||
var rx = new byte[1024];
|
||||
var len = 0;
|
||||
@@ -65,6 +65,7 @@ namespace OpenRA.Server
|
||||
if (e.SocketErrorCode == SocketError.WouldBlock) break;
|
||||
|
||||
server.DropClient(this);
|
||||
Log.Write("server", "Dropping client {0} because reading the data failed: {1}", this.PlayerIndex.ToString(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -72,7 +73,7 @@ namespace OpenRA.Server
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ReadData( Server server )
|
||||
public void ReadData(Server server)
|
||||
{
|
||||
if (ReadDataInner(server))
|
||||
while (data.Count >= ExpectLength)
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace OpenRA.Server
|
||||
protected set { pState = value; }
|
||||
}
|
||||
|
||||
public List<string> TempBans = new List<string>();
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
State = ServerState.ShuttingDown;
|
||||
@@ -97,20 +99,19 @@ namespace OpenRA.Server
|
||||
UPnP.ForwardPort();
|
||||
|
||||
foreach (var trait in modData.Manifest.ServerTraits)
|
||||
ServerTraits.Add( modData.ObjectCreator.CreateObject<ServerTrait>(trait) );
|
||||
ServerTraits.Add(modData.ObjectCreator.CreateObject<ServerTrait>(trait));
|
||||
|
||||
lobbyInfo = new Session( mods );
|
||||
lobbyInfo = new Session(mods);
|
||||
lobbyInfo.GlobalSettings.RandomSeed = randomSeed;
|
||||
lobbyInfo.GlobalSettings.Map = settings.Map;
|
||||
lobbyInfo.GlobalSettings.ServerName = settings.Name;
|
||||
lobbyInfo.GlobalSettings.Ban = settings.Ban;
|
||||
lobbyInfo.GlobalSettings.Dedicated = settings.Dedicated;
|
||||
|
||||
foreach (var t in ServerTraits.WithInterface<INotifyServerStart>())
|
||||
t.ServerStarted(this);
|
||||
|
||||
Log.Write("server", "Initial mods: ");
|
||||
foreach( var m in lobbyInfo.GlobalSettings.Mods )
|
||||
foreach (var m in lobbyInfo.GlobalSettings.Mods)
|
||||
Log.Write("server","- {0}", m);
|
||||
|
||||
Log.Write("server", "Initial map: {0}",lobbyInfo.GlobalSettings.Map);
|
||||
@@ -118,31 +119,31 @@ namespace OpenRA.Server
|
||||
new Thread( _ =>
|
||||
{
|
||||
var timeout = ServerTraits.WithInterface<ITick>().Min(t => t.TickTimeout);
|
||||
for( ; ; )
|
||||
for (;;)
|
||||
{
|
||||
var checkRead = new List<Socket>();
|
||||
checkRead.Add( listener.Server );
|
||||
foreach( var c in conns ) checkRead.Add( c.socket );
|
||||
foreach( var c in preConns ) checkRead.Add( c.socket );
|
||||
checkRead.Add(listener.Server);
|
||||
foreach (var c in conns) checkRead.Add(c.socket);
|
||||
foreach (var c in preConns) checkRead.Add(c.socket);
|
||||
|
||||
Socket.Select( checkRead, null, null, timeout );
|
||||
Socket.Select(checkRead, null, null, timeout);
|
||||
if (State == ServerState.ShuttingDown)
|
||||
{
|
||||
EndGame();
|
||||
break;
|
||||
}
|
||||
|
||||
foreach( var s in checkRead )
|
||||
if( s == listener.Server ) AcceptConnection();
|
||||
foreach (var s in checkRead)
|
||||
if (s == listener.Server) AcceptConnection();
|
||||
else if (preConns.Count > 0)
|
||||
{
|
||||
var p = preConns.SingleOrDefault( c => c.socket == s );
|
||||
if (p != null) p.ReadData( this );
|
||||
var p = preConns.SingleOrDefault(c => c.socket == s);
|
||||
if (p != null) p.ReadData(this);
|
||||
}
|
||||
else if (conns.Count > 0)
|
||||
{
|
||||
var conn = conns.SingleOrDefault( c => c.socket == s );
|
||||
if (conn != null) conn.ReadData( this );
|
||||
var conn = conns.SingleOrDefault(c => c.socket == s);
|
||||
if (conn != null) conn.ReadData(this);
|
||||
}
|
||||
|
||||
foreach (var t in ServerTraits.WithInterface<ITick>())
|
||||
@@ -151,8 +152,7 @@ namespace OpenRA.Server
|
||||
if (State == ServerState.ShuttingDown)
|
||||
{
|
||||
EndGame();
|
||||
if (Settings.AllowPortForward)
|
||||
UPnP.RemovePortforward();
|
||||
if (Settings.AllowPortForward) UPnP.RemovePortforward();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -187,10 +187,11 @@ namespace OpenRA.Server
|
||||
if (!listener.Server.IsBound) return;
|
||||
newSocket = listener.AcceptSocket();
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
/* could have an exception here when listener 'goes away' when calling AcceptConnection! */
|
||||
/* alternative would be to use locking but the listener doesnt go away without a reason */
|
||||
/* TODO: Could have an exception here when listener 'goes away' when calling AcceptConnection! */
|
||||
/* Alternative would be to use locking but the listener doesnt go away without a reason. */
|
||||
Log.Write("server", "Accepting the connection failed.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,19 +203,23 @@ namespace OpenRA.Server
|
||||
|
||||
// assign the player number.
|
||||
newConn.PlayerIndex = ChooseFreePlayerIndex();
|
||||
newConn.socket.Send(BitConverter.GetBytes(ProtocolVersion.Version));
|
||||
newConn.socket.Send(BitConverter.GetBytes(newConn.PlayerIndex));
|
||||
SendData(newConn.socket, BitConverter.GetBytes(ProtocolVersion.Version));
|
||||
SendData(newConn.socket, BitConverter.GetBytes(newConn.PlayerIndex));
|
||||
preConns.Add(newConn);
|
||||
|
||||
// Dispatch a handshake order
|
||||
var request = new HandshakeRequest()
|
||||
{
|
||||
Map = lobbyInfo.GlobalSettings.Map,
|
||||
Mods = lobbyInfo.GlobalSettings.Mods.Select(m => "{0}@{1}".F(m,Mod.AllMods[m].Version)).ToArray()
|
||||
Mods = lobbyInfo.GlobalSettings.Mods.Select(m => "{0}@{1}".F(m, Mod.AllMods[m].Version)).ToArray()
|
||||
};
|
||||
DispatchOrdersToClient(newConn, 0, 0, new ServerOrder("HandshakeRequest", request.Serialize()).Serialize());
|
||||
}
|
||||
catch (Exception) { DropClient(newConn); }
|
||||
catch (Exception e)
|
||||
{
|
||||
DropClient(newConn);
|
||||
Log.Write("server", "Dropping client {0} because handshake failed: {1}", newConn.PlayerIndex.ToString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateClient(Connection newConn, string data)
|
||||
@@ -232,7 +237,25 @@ namespace OpenRA.Server
|
||||
}
|
||||
|
||||
var handshake = HandshakeResponse.Deserialize(data);
|
||||
var client = handshake.Client;
|
||||
|
||||
var client = new Session.Client()
|
||||
{
|
||||
Name = handshake.Client.Name,
|
||||
IpAddress = ((IPEndPoint)newConn.socket.RemoteEndPoint).Address.ToString(),
|
||||
Index = newConn.PlayerIndex,
|
||||
Slot = lobbyInfo.FirstEmptySlot(),
|
||||
PreferredColor = handshake.Client.Color,
|
||||
Color = handshake.Client.Color,
|
||||
Country = "random",
|
||||
SpawnPoint = 0,
|
||||
Team = 0,
|
||||
State = Session.ClientState.NotReady,
|
||||
IsAdmin = !lobbyInfo.Clients.Any(c1 => c1.IsAdmin)
|
||||
};
|
||||
|
||||
if (client.Slot != null)
|
||||
SyncClientToPlayerReference(client, Map.Players[client.Slot]);
|
||||
|
||||
var mods = handshake.Mods;
|
||||
|
||||
// Check that the client has compatible mods
|
||||
@@ -263,42 +286,21 @@ namespace OpenRA.Server
|
||||
return;
|
||||
}
|
||||
|
||||
client.IpAddress = ((IPEndPoint)newConn.socket.RemoteEndPoint).Address.ToString();
|
||||
|
||||
// Check if IP is banned
|
||||
if (lobbyInfo.GlobalSettings.Ban != null)
|
||||
var bans = Settings.Ban.Union(TempBans);
|
||||
if (bans.Contains(client.IpAddress))
|
||||
{
|
||||
|
||||
if (lobbyInfo.GlobalSettings.Ban.Contains(client.IpAddress))
|
||||
{
|
||||
Console.WriteLine("Rejected connection from "+client.Name+"("+newConn.socket.RemoteEndPoint+"); Banned.");
|
||||
Log.Write("server", "Rejected connection from {0}; Banned.",
|
||||
newConn.socket.RemoteEndPoint);
|
||||
SendOrderTo(newConn, "ServerError", "You are banned from the server!");
|
||||
DropClient(newConn);
|
||||
return;
|
||||
}
|
||||
Log.Write("server", "Rejected connection from {0}; Banned.", newConn.socket.RemoteEndPoint);
|
||||
SendOrderTo(newConn, "ServerError", "You are {0} from the server.".F(Settings.Ban.Contains(client.IpAddress) ? "banned" : "temporarily banned"));
|
||||
DropClient(newConn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Promote connection to a valid client
|
||||
preConns.Remove(newConn);
|
||||
conns.Add(newConn);
|
||||
|
||||
// Enforce correct PlayerIndex and Slot
|
||||
client.Index = newConn.PlayerIndex;
|
||||
client.Slot = lobbyInfo.FirstEmptySlot();
|
||||
|
||||
if (client.Slot != null)
|
||||
SyncClientToPlayerReference(client, Map.Players[client.Slot]);
|
||||
|
||||
lobbyInfo.Clients.Add(client);
|
||||
|
||||
// Assume that first validated client is server admin
|
||||
if (lobbyInfo.Clients.Where(c1 => c1.Bot == null).Count() == 1)
|
||||
client.IsAdmin=true;
|
||||
|
||||
OpenRA.Network.Session.Client clientAdmin = lobbyInfo.Clients.Where(c1 => c1.IsAdmin).Single();
|
||||
|
||||
Log.Write("server", "Client {0}: Accepted connection from {1}.",
|
||||
newConn.PlayerIndex, newConn.socket.RemoteEndPoint);
|
||||
|
||||
@@ -317,19 +319,25 @@ namespace OpenRA.Server
|
||||
SendOrderTo(newConn, "Message", motd);
|
||||
}
|
||||
|
||||
if (lobbyInfo.GlobalSettings.Dedicated)
|
||||
{
|
||||
var message = client.IsAdmin ? "You are the server admin." : "{0} is the server admin.".F(clientAdmin.Name);
|
||||
SendOrderTo(newConn, "Message", message);
|
||||
}
|
||||
|
||||
if (mods.Any(m => m.Contains("{DEV_VERSION}")))
|
||||
SendMessage("{0} is running an unversioned development build, ".F(client.Name) +
|
||||
"and may desynchronize the game state if they have incompatible rules.");
|
||||
|
||||
SetOrderLag();
|
||||
}
|
||||
catch (Exception) { DropClient(newConn); }
|
||||
}
|
||||
|
||||
void SetOrderLag()
|
||||
{
|
||||
if (lobbyInfo.IsSinglePlayer)
|
||||
lobbyInfo.GlobalSettings.OrderLatency = 1;
|
||||
else
|
||||
lobbyInfo.GlobalSettings.OrderLatency = 3;
|
||||
|
||||
SyncLobbyInfo();
|
||||
}
|
||||
|
||||
public static void SyncClientToPlayerReference(Session.Client c, PlayerReference pr)
|
||||
{
|
||||
if (pr == null)
|
||||
@@ -364,14 +372,16 @@ namespace OpenRA.Server
|
||||
{
|
||||
try
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Write( BitConverter.GetBytes( data.Length + 4 ) );
|
||||
ms.Write( BitConverter.GetBytes( client ) );
|
||||
ms.Write( BitConverter.GetBytes( frame ) );
|
||||
ms.Write( data );
|
||||
c.socket.Send( ms.ToArray() );
|
||||
SendData(c.socket, BitConverter.GetBytes(data.Length + 4));
|
||||
SendData(c.socket, BitConverter.GetBytes(client));
|
||||
SendData(c.socket, BitConverter.GetBytes(frame));
|
||||
SendData(c.socket, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DropClient(c);
|
||||
Log.Write("server", "Dropping client {0} because dispatching orders failed: {1}", client.ToString(), e);
|
||||
}
|
||||
catch (Exception) { DropClient(c); }
|
||||
}
|
||||
|
||||
public void DispatchOrdersToClients(Connection conn, int frame, byte[] data)
|
||||
@@ -485,7 +495,9 @@ namespace OpenRA.Server
|
||||
{
|
||||
conns.Remove(toDrop);
|
||||
|
||||
OpenRA.Network.Session.Client dropClient = lobbyInfo.Clients.Where(c1 => c1.Index == toDrop.PlayerIndex).Single();
|
||||
var dropClient = lobbyInfo.Clients.FirstOrDefault(c1 => c1.Index == toDrop.PlayerIndex);
|
||||
if (dropClient == null)
|
||||
return;
|
||||
|
||||
// Send disconnected order, even if still in the lobby
|
||||
SendMessage("{0} has disconnected.".F(dropClient.Name));
|
||||
@@ -500,7 +512,7 @@ namespace OpenRA.Server
|
||||
// Remove any bots controlled by the admin
|
||||
lobbyInfo.Clients.RemoveAll(c => c.Bot != null && c.BotControllerClientIndex == toDrop.PlayerIndex);
|
||||
|
||||
OpenRA.Network.Session.Client nextAdmin = lobbyInfo.Clients.Where(c1 => c1.Bot == null)
|
||||
var nextAdmin = lobbyInfo.Clients.Where(c1 => c1.Bot == null)
|
||||
.OrderBy(c => c.Index).FirstOrDefault();
|
||||
|
||||
if (nextAdmin != null)
|
||||
@@ -512,7 +524,10 @@ namespace OpenRA.Server
|
||||
|
||||
DispatchOrders(toDrop, toDrop.MostRecentFrame, new byte[] {0xbf});
|
||||
|
||||
if (conns.Count != 0 || lobbyInfo.GlobalSettings.Dedicated)
|
||||
if (!conns.Any())
|
||||
TempBans.Clear();
|
||||
|
||||
if (conns.Any() || lobbyInfo.GlobalSettings.Dedicated)
|
||||
SyncLobbyInfo();
|
||||
|
||||
if (!lobbyInfo.GlobalSettings.Dedicated && dropClient.IsAdmin)
|
||||
@@ -524,6 +539,8 @@ namespace OpenRA.Server
|
||||
toDrop.socket.Disconnect(false);
|
||||
}
|
||||
catch { }
|
||||
|
||||
SetOrderLag();
|
||||
}
|
||||
|
||||
public void SyncLobbyInfo()
|
||||
@@ -543,9 +560,9 @@ namespace OpenRA.Server
|
||||
|
||||
Console.WriteLine("Game started");
|
||||
|
||||
foreach( var c in conns )
|
||||
foreach( var d in conns )
|
||||
DispatchOrdersToClient( c, d.PlayerIndex, 0x7FFFFFFF, new byte[] { 0xBF } );
|
||||
foreach (var c in conns)
|
||||
foreach (var d in conns)
|
||||
DispatchOrdersToClient(c, d.PlayerIndex, 0x7FFFFFFF, new byte[] { 0xBF });
|
||||
|
||||
// Drop any unvalidated clients
|
||||
foreach (var c in preConns.ToArray())
|
||||
@@ -569,5 +586,29 @@ namespace OpenRA.Server
|
||||
gameTimeout.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SendData(Socket s, byte[] data)
|
||||
{
|
||||
var start = 0;
|
||||
var length = data.Length;
|
||||
SocketError error;
|
||||
|
||||
// Non-blocking sends are free to send only part of the data
|
||||
while (start < length)
|
||||
{
|
||||
var sent = s.Send(data, start, length - start, SocketFlags.None, out error);
|
||||
if (error == SocketError.WouldBlock)
|
||||
{
|
||||
Log.Write("server", "Non-blocking send of {0} bytes failed. Falling back to blocking send.", length - start);
|
||||
s.Blocking = true;
|
||||
sent = s.Send(data, start, length - start, SocketFlags.None);
|
||||
s.Blocking = false;
|
||||
}
|
||||
else if (error != SocketError.Success)
|
||||
throw new SocketException((int)error);
|
||||
|
||||
start += sent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,15 @@ namespace OpenRA
|
||||
return null;
|
||||
}
|
||||
|
||||
return LoadSoundRaw(AudLoader.LoadSound(FileSystem.Open(filename)));
|
||||
if (filename.ToLowerInvariant().EndsWith("wav"))
|
||||
return LoadWave(new WavLoader(FileSystem.Open(filename)));
|
||||
else
|
||||
return LoadSoundRaw(AudLoader.LoadSound(FileSystem.Open(filename)));
|
||||
}
|
||||
|
||||
static ISoundSource LoadWave(WavLoader wave)
|
||||
{
|
||||
return soundEngine.AddSoundSourceFromMemory(wave.RawOutput, wave.Channels, wave.BitsPerSample, wave.SampleRate);
|
||||
}
|
||||
|
||||
static ISoundSource LoadSoundRaw(byte[] rawData)
|
||||
@@ -45,7 +53,7 @@ namespace OpenRA
|
||||
|
||||
static ISoundEngine CreateEngine(string engine)
|
||||
{
|
||||
engine = Game.Settings.Server.Dedicated?"Null":engine;
|
||||
engine = Game.Settings.Server.Dedicated ? "Null" : engine;
|
||||
switch (engine)
|
||||
{ /* TODO: if someone cares about pluggable crap here, ship this out */
|
||||
case "AL": return new OpenAlSoundEngine();
|
||||
@@ -366,6 +374,7 @@ namespace OpenRA
|
||||
|
||||
public OpenAlSoundEngine()
|
||||
{
|
||||
Console.WriteLine("Using OpenAL sound engine");
|
||||
//var str = Alc.alcGetString(IntPtr.Zero, Alc.ALC_DEFAULT_DEVICE_SPECIFIER);
|
||||
var dev = Alc.alcOpenDevice(null);
|
||||
if (dev == IntPtr.Zero)
|
||||
@@ -637,6 +646,11 @@ namespace OpenRA
|
||||
|
||||
class NullSoundEngine : ISoundEngine
|
||||
{
|
||||
public NullSoundEngine()
|
||||
{
|
||||
Console.WriteLine("Using Null sound engine which disables SFX completely");
|
||||
}
|
||||
|
||||
public ISoundSource AddSoundSourceFromMemory(byte[] data, int channels, int sampleBits, int sampleRate)
|
||||
{
|
||||
return new NullSoundSource();
|
||||
|
||||
58
OpenRA.Game/Traits/BodyOrientation.cs
Executable file
58
OpenRA.Game/Traits/BodyOrientation.cs
Executable file
@@ -0,0 +1,58 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2011 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
public class BodyOrientationInfo : ITraitInfo, IBodyOrientationInfo
|
||||
{
|
||||
[Desc("Camera pitch for rotation calculations")]
|
||||
public readonly WAngle CameraPitch = WAngle.FromDegrees(40);
|
||||
public object Create(ActorInitializer init) { return new BodyOrientation(init.self, this); }
|
||||
}
|
||||
|
||||
public class BodyOrientation : IBodyOrientation
|
||||
{
|
||||
[Sync] public int QuantizedFacings { get; set; }
|
||||
BodyOrientationInfo Info;
|
||||
|
||||
public BodyOrientation(Actor self, BodyOrientationInfo info)
|
||||
{
|
||||
Info = info;
|
||||
}
|
||||
|
||||
public WAngle CameraPitch { get { return Info.CameraPitch; } }
|
||||
|
||||
public WVec LocalToWorld(WVec vec)
|
||||
{
|
||||
// RA's 2d perspective doesn't correspond to an orthonormal 3D
|
||||
// coordinate system, so fudge the y axis to make things look good
|
||||
return new WVec(vec.Y, -Info.CameraPitch.Sin()*vec.X/1024, vec.Z);
|
||||
}
|
||||
|
||||
public WRot QuantizeOrientation(Actor self, WRot orientation)
|
||||
{
|
||||
// Quantization disabled
|
||||
if (QuantizedFacings == 0)
|
||||
return orientation;
|
||||
|
||||
// Map yaw to the closest facing
|
||||
var facing = Util.QuantizeFacing(orientation.Yaw.Angle / 4, QuantizedFacings) * (256 / QuantizedFacings);
|
||||
|
||||
// Roll and pitch are always zero if yaw is quantized
|
||||
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facing));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@ namespace OpenRA.Traits
|
||||
public class CreatesShroud : ITick, ISync
|
||||
{
|
||||
CreatesShroudInfo Info;
|
||||
[Sync] CPos previousLocation;
|
||||
Shroud.ActorVisibility v;
|
||||
[Sync] CPos cachedLocation;
|
||||
[Sync] bool cachedDisabled;
|
||||
|
||||
public CreatesShroud(CreatesShroudInfo info)
|
||||
{
|
||||
@@ -31,29 +31,18 @@ namespace OpenRA.Traits
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
// TODO: don't tick all the time.
|
||||
if (self.Owner == null)
|
||||
return;
|
||||
|
||||
var shrouds = self.World.ActorsWithTrait<Shroud>().Select(s => s.Actor.Owner.Shroud);
|
||||
if (previousLocation != self.Location && v != null)
|
||||
var disabled = self.TraitsImplementing<IDisable>().Any(d => d.Disabled);
|
||||
if (cachedLocation != self.Location || cachedDisabled != disabled)
|
||||
{
|
||||
previousLocation = self.Location;
|
||||
cachedLocation = self.Location;
|
||||
cachedDisabled = disabled;
|
||||
|
||||
foreach (var shroud in shrouds)
|
||||
shroud.UnhideActor(self, v, Info.Range);
|
||||
var shroud = self.World.Players.Select(p => p.Shroud);
|
||||
foreach (var s in shroud)
|
||||
s.UpdateShroudGeneration(self);
|
||||
}
|
||||
|
||||
if (!self.TraitsImplementing<IDisable>().Any(d => d.Disabled))
|
||||
foreach (var shroud in shrouds)
|
||||
shroud.HideActor(self, Info.Range);
|
||||
else
|
||||
foreach (var shroud in shrouds)
|
||||
shroud.UnhideActor(self, v, Info.Range);
|
||||
|
||||
v = new Shroud.ActorVisibility {
|
||||
vis = Shroud.GetVisOrigins(self).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public int Range { get { return cachedDisabled ? 0 : Info.Range; } }
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ namespace OpenRA.Traits
|
||||
.Concat(self.Owner.PlayerActor.TraitsImplementing<INotifyKilled>()))
|
||||
nd.Killed(self, ai);
|
||||
|
||||
if( RemoveOnDeath )
|
||||
if (RemoveOnDeath)
|
||||
self.Destroy();
|
||||
|
||||
Log.Write("debug", "{0} #{1} killed by {2} #{3}", self.Info.Name, self.ActorID, attacker.Info.Name, attacker.ActorID);
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace OpenRA.Traits
|
||||
public bool UnlimitedPower;
|
||||
public bool BuildAnywhere;
|
||||
public bool ShowMuzzles;
|
||||
public bool ShowDebugGeometry;
|
||||
|
||||
public object Create (ActorInitializer init) { return new DeveloperMode(this); }
|
||||
}
|
||||
@@ -39,6 +40,7 @@ namespace OpenRA.Traits
|
||||
|
||||
// Client size only
|
||||
public bool ShowMuzzles;
|
||||
public bool ShowDebugGeometry;
|
||||
|
||||
public DeveloperMode(DeveloperModeInfo info)
|
||||
{
|
||||
@@ -50,6 +52,7 @@ namespace OpenRA.Traits
|
||||
UnlimitedPower = info.UnlimitedPower;
|
||||
BuildAnywhere = info.BuildAnywhere;
|
||||
ShowMuzzles = info.ShowMuzzles;
|
||||
ShowDebugGeometry = info.ShowDebugGeometry;
|
||||
}
|
||||
|
||||
public void ResolveOrder (Actor self, Order order)
|
||||
|
||||
@@ -23,42 +23,6 @@ namespace OpenRA.Traits
|
||||
public object Create(ActorInitializer init) { return new PlayerResources(init.self, this); }
|
||||
}
|
||||
|
||||
public class DebugResourceCashInfo : ITraitInfo, Requires<PlayerResourcesInfo>
|
||||
{
|
||||
public object Create(ActorInitializer init) { return new DebugResourceCash(init.self); }
|
||||
}
|
||||
|
||||
public class DebugResourceCash : ISync
|
||||
{
|
||||
readonly PlayerResources pr;
|
||||
public DebugResourceCash(Actor self) { pr = self.Trait<PlayerResources>(); }
|
||||
[Sync] public int foo { get { return pr.Cash; } }
|
||||
}
|
||||
|
||||
public class DebugResourceOreInfo : ITraitInfo, Requires<PlayerResourcesInfo>
|
||||
{
|
||||
public object Create(ActorInitializer init) { return new DebugResourceOre(init.self); }
|
||||
}
|
||||
|
||||
public class DebugResourceOre : ISync
|
||||
{
|
||||
readonly PlayerResources pr;
|
||||
public DebugResourceOre(Actor self) { pr = self.Trait<PlayerResources>(); }
|
||||
[Sync] public int foo { get { return pr.Ore; } }
|
||||
}
|
||||
|
||||
public class DebugResourceOreCapacityInfo : ITraitInfo
|
||||
{
|
||||
public object Create(ActorInitializer init) { return new DebugResourceOreCapacity(init.self); }
|
||||
}
|
||||
|
||||
public class DebugResourceOreCapacity : ISync
|
||||
{
|
||||
readonly PlayerResources pr;
|
||||
public DebugResourceOreCapacity(Actor self) { pr = self.Trait<PlayerResources>(); }
|
||||
[Sync] public int foo { get { return pr.OreCapacity; } }
|
||||
}
|
||||
|
||||
public class PlayerResources : ITick, ISync
|
||||
{
|
||||
readonly Player Owner;
|
||||
|
||||
@@ -16,103 +16,35 @@ using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
public class RenderSimpleInfo : ITraitInfo, LocalCoordinatesModelInfo
|
||||
public class RenderSimpleInfo : RenderSpritesInfo, Requires<IBodyOrientationInfo>
|
||||
{
|
||||
[Desc("Defaults to the actor name.")]
|
||||
public readonly string Image = null;
|
||||
[Desc("custom palette name")]
|
||||
public readonly string Palette = null;
|
||||
[Desc("custom PlayerColorPalette: BaseName")]
|
||||
public readonly string PlayerPalette = "player";
|
||||
[Desc("Change the sprite image size.")]
|
||||
public readonly float Scale = 1f;
|
||||
public override object Create(ActorInitializer init) { return new RenderSimple(init.self); }
|
||||
|
||||
[Desc("Number of facings for gameplay calculations. -1 indiciates auto-detection from sequence")]
|
||||
public readonly int QuantizedFacings = -1;
|
||||
|
||||
public readonly WAngle CameraPitch = WAngle.FromDegrees(40);
|
||||
public virtual object Create(ActorInitializer init) { return new RenderSimple(init.self); }
|
||||
|
||||
public virtual IEnumerable<Renderable> RenderPreview(ActorInfo building, PaletteReference pr)
|
||||
public virtual IEnumerable<IRenderable> RenderPreview(ActorInfo ai, PaletteReference pr)
|
||||
{
|
||||
var anim = new Animation(RenderSimple.GetImage(building), () => 0);
|
||||
var anim = new Animation(RenderSimple.GetImage(ai), () => 0);
|
||||
anim.PlayRepeating("idle");
|
||||
|
||||
yield return new Renderable(anim.Image, 0.5f * anim.Image.size * (1 - Scale), pr, 0, Scale);
|
||||
yield return new SpriteRenderable(anim.Image, WPos.Zero, 0, pr, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
public class RenderSimple : IRender, ILocalCoordinatesModel, IAutoSelectionSize, ITick, INotifyOwnerChanged
|
||||
public class RenderSimple : RenderSprites, IAutoSelectionSize
|
||||
{
|
||||
public Dictionary<string, AnimationWithOffset> anims = new Dictionary<string, AnimationWithOffset>();
|
||||
|
||||
public static Func<int> MakeFacingFunc(Actor self)
|
||||
{
|
||||
var facing = self.TraitOrDefault<IFacing>();
|
||||
if (facing == null) return () => 0;
|
||||
return () => facing.Facing;
|
||||
}
|
||||
|
||||
public Animation anim
|
||||
{
|
||||
get { return anims[""].Animation; }
|
||||
protected set { anims[""].Animation = value; }
|
||||
}
|
||||
|
||||
public static string GetImage(ActorInfo actor)
|
||||
{
|
||||
var Info = actor.Traits.Get<RenderSimpleInfo>();
|
||||
return Info.Image ?? actor.Name;
|
||||
}
|
||||
|
||||
public string GetImage(Actor self)
|
||||
{
|
||||
if (cachedImage != null)
|
||||
return cachedImage;
|
||||
|
||||
return cachedImage = GetImage(self.Info);
|
||||
}
|
||||
|
||||
RenderSimpleInfo Info;
|
||||
string cachedImage = null;
|
||||
bool initializePalette = true;
|
||||
protected PaletteReference palette;
|
||||
|
||||
public RenderSimple(Actor self, Func<int> baseFacing)
|
||||
: base(self)
|
||||
{
|
||||
anims.Add("", new Animation(GetImage(self), baseFacing));
|
||||
Info = self.Info.Traits.Get<RenderSimpleInfo>();
|
||||
}
|
||||
|
||||
public RenderSimple(Actor self) : this( self, MakeFacingFunc(self) )
|
||||
public RenderSimple(Actor self)
|
||||
: this(self, MakeFacingFunc(self))
|
||||
{
|
||||
anim.PlayRepeating("idle");
|
||||
}
|
||||
|
||||
protected virtual string PaletteName(Actor self)
|
||||
{
|
||||
return Info.Palette ?? Info.PlayerPalette + self.Owner.InternalName;
|
||||
}
|
||||
|
||||
protected void UpdatePalette() { initializePalette = true; }
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { UpdatePalette(); }
|
||||
|
||||
public virtual IEnumerable<Renderable> Render(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (initializePalette)
|
||||
{
|
||||
palette = wr.Palette(PaletteName(self));
|
||||
initializePalette = false;
|
||||
}
|
||||
|
||||
foreach (var a in anims.Values)
|
||||
if (a.DisableFunc == null || !a.DisableFunc())
|
||||
{
|
||||
Renderable ret = a.Image(self, wr, palette);
|
||||
if (Info.Scale != 1f)
|
||||
ret = ret.WithScale(Info.Scale).WithPos(ret.Pos + 0.5f * ret.Sprite.size * (1 - Info.Scale));
|
||||
yield return ret;
|
||||
}
|
||||
self.Trait<IBodyOrientation>().QuantizedFacings = anim.CurrentSequence.Facings;
|
||||
}
|
||||
|
||||
public int2 SelectionSize(Actor self)
|
||||
@@ -123,19 +55,9 @@ namespace OpenRA.Traits
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public virtual void Tick(Actor self)
|
||||
public string NormalizeSequence(Actor self, string baseSequence)
|
||||
{
|
||||
foreach (var a in anims.Values)
|
||||
a.Animation.Tick();
|
||||
}
|
||||
|
||||
protected virtual string NormalizeSequence(Actor self, string baseSequence)
|
||||
{
|
||||
string damageState = self.GetDamageState() >= DamageState.Heavy ? "damaged-" : "";
|
||||
if (anim.HasSequence(damageState + baseSequence))
|
||||
return damageState + baseSequence;
|
||||
else
|
||||
return baseSequence;
|
||||
return NormalizeSequence(anim, self.GetDamageState(), baseSequence);
|
||||
}
|
||||
|
||||
public void PlayCustomAnim(Actor self, string name)
|
||||
@@ -144,22 +66,5 @@ namespace OpenRA.Traits
|
||||
anim.PlayThen(NormalizeSequence(self, name),
|
||||
() => anim.PlayRepeating(NormalizeSequence(self, "idle")));
|
||||
}
|
||||
|
||||
public WVec LocalToWorld(WVec vec)
|
||||
{
|
||||
// RA's 2d perspective doesn't correspond to an orthonormal 3D
|
||||
// coordinate system, so fudge the y axis to make things look good
|
||||
return new WVec(vec.Y, -Info.CameraPitch.Sin()*vec.X/1024, vec.Z);
|
||||
}
|
||||
|
||||
public WRot QuantizeOrientation(Actor self, WRot orientation)
|
||||
{
|
||||
// Map yaw to the closest facing
|
||||
var numDirs = Info.QuantizedFacings == -1 ? anim.CurrentSequence.Facings : Info.QuantizedFacings;
|
||||
var facing = Util.QuantizeFacing(orientation.Yaw.Angle / 4, numDirs) * (256 / numDirs);
|
||||
|
||||
// Roll and pitch are always zero
|
||||
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facing));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
120
OpenRA.Game/Traits/Render/RenderSprites.cs
Executable file
120
OpenRA.Game/Traits/Render/RenderSprites.cs
Executable file
@@ -0,0 +1,120 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2011 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
public class RenderSpritesInfo : ITraitInfo
|
||||
{
|
||||
[Desc("Defaults to the actor name.")]
|
||||
public readonly string Image = null;
|
||||
|
||||
[Desc("Custom palette name")]
|
||||
public readonly string Palette = null;
|
||||
[Desc("Custom PlayerColorPalette: BaseName")]
|
||||
public readonly string PlayerPalette = "player";
|
||||
[Desc("Change the sprite image size.")]
|
||||
public readonly float Scale = 1f;
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new RenderSprites(init.self); }
|
||||
}
|
||||
|
||||
public class RenderSprites : IRender, ITick, INotifyOwnerChanged
|
||||
{
|
||||
public Dictionary<string, AnimationWithOffset> anims = new Dictionary<string, AnimationWithOffset>();
|
||||
|
||||
public static Func<int> MakeFacingFunc(Actor self)
|
||||
{
|
||||
var facing = self.TraitOrDefault<IFacing>();
|
||||
if (facing == null) return () => 0;
|
||||
return () => facing.Facing;
|
||||
}
|
||||
|
||||
public Animation anim
|
||||
{
|
||||
get { return anims[""].Animation; }
|
||||
protected set { anims[""] = new AnimationWithOffset(value,
|
||||
anims[""].OffsetFunc, anims[""].DisableFunc, anims[""].ZOffset); }
|
||||
}
|
||||
|
||||
RenderSpritesInfo Info;
|
||||
string cachedImage = null;
|
||||
bool initializePalette = true;
|
||||
protected PaletteReference palette;
|
||||
|
||||
public RenderSprites(Actor self)
|
||||
{
|
||||
Info = self.Info.Traits.Get<RenderSpritesInfo>();
|
||||
}
|
||||
|
||||
public static string GetImage(ActorInfo actor)
|
||||
{
|
||||
var Info = actor.Traits.Get<RenderSpritesInfo>();
|
||||
return Info.Image ?? actor.Name;
|
||||
}
|
||||
|
||||
public string GetImage(Actor self)
|
||||
{
|
||||
if (cachedImage != null)
|
||||
return cachedImage;
|
||||
|
||||
return cachedImage = GetImage(self.Info);
|
||||
}
|
||||
|
||||
protected virtual string PaletteName(Actor self)
|
||||
{
|
||||
return Info.Palette ?? Info.PlayerPalette + self.Owner.InternalName;
|
||||
}
|
||||
|
||||
protected void UpdatePalette() { initializePalette = true; }
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { UpdatePalette(); }
|
||||
|
||||
public virtual IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
|
||||
{
|
||||
if (initializePalette)
|
||||
{
|
||||
palette = wr.Palette(PaletteName(self));
|
||||
initializePalette = false;
|
||||
}
|
||||
|
||||
foreach (var a in anims.Values)
|
||||
if (a.DisableFunc == null || !a.DisableFunc())
|
||||
yield return a.Image(self, wr, palette, Info.Scale);
|
||||
}
|
||||
|
||||
public virtual void Tick(Actor self)
|
||||
{
|
||||
foreach (var a in anims.Values)
|
||||
a.Animation.Tick();
|
||||
}
|
||||
|
||||
public static string NormalizeSequence(Animation anim, DamageState state, string baseSequence)
|
||||
{
|
||||
var states = new Pair<DamageState, string>[]
|
||||
{
|
||||
Pair.New(DamageState.Critical, "critical-"),
|
||||
Pair.New(DamageState.Heavy, "damaged-"),
|
||||
Pair.New(DamageState.Medium, "scratched-"),
|
||||
Pair.New(DamageState.Light, "scuffed-")
|
||||
};
|
||||
|
||||
foreach (var s in states)
|
||||
if (state >= s.First && anim.HasSequence(s.Second+baseSequence))
|
||||
return s.Second+baseSequence;
|
||||
|
||||
return baseSequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
public class RevealsShroudInfo : ITraitInfo
|
||||
@@ -19,7 +21,7 @@ namespace OpenRA.Traits
|
||||
public class RevealsShroud : ITick, ISync
|
||||
{
|
||||
RevealsShroudInfo Info;
|
||||
[Sync] CPos previousLocation;
|
||||
[Sync] CPos cachedLocation;
|
||||
|
||||
public RevealsShroud(RevealsShroudInfo info)
|
||||
{
|
||||
@@ -28,26 +30,15 @@ namespace OpenRA.Traits
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
// TODO: don't tick all the time.
|
||||
World w = self.World;
|
||||
if(self.Owner == null) return;
|
||||
|
||||
if (previousLocation != self.Location)
|
||||
if (cachedLocation != self.Location)
|
||||
{
|
||||
previousLocation = self.Location;
|
||||
var actors = w.ActorsWithTrait<Shroud>();
|
||||
cachedLocation = self.Location;
|
||||
|
||||
foreach( var s in actors )
|
||||
s.Actor.Owner.Shroud.RemoveActor(self);
|
||||
|
||||
self.UpdateSight();
|
||||
|
||||
foreach( var s in actors )
|
||||
s.Actor.Owner.Shroud.AddActor(self);
|
||||
|
||||
foreach (var s in self.World.Players.Select(p => p.Shroud))
|
||||
s.UpdateVisibility(self);
|
||||
}
|
||||
}
|
||||
|
||||
public int RevealRange { get { return Info.Range; } }
|
||||
public int Range { get { return Info.Range; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace OpenRA.Traits
|
||||
var pipImages = new Animation("pips");
|
||||
pipImages.PlayFetchIndex("groups", () => (int)group);
|
||||
pipImages.Tick();
|
||||
pipImages.Image.DrawAt(wr, basePosition + new float2(-8, 1), "chrome");
|
||||
pipImages.Image.DrawAt(basePosition + new float2(-8, 1), wr.Palette("chrome"));
|
||||
}
|
||||
|
||||
void DrawPips(WorldRenderer wr, Actor self, float2 basePosition)
|
||||
@@ -69,6 +69,7 @@ namespace OpenRA.Traits
|
||||
var pipSize = pipImages.Image.size;
|
||||
var pipxyBase = basePosition + new float2(1, -pipSize.Y);
|
||||
var pipxyOffset = new float2(0, 0); // Correct for offset due to multiple columns/rows
|
||||
var pal = wr.Palette("chrome");
|
||||
|
||||
foreach (var pips in pipSources)
|
||||
{
|
||||
@@ -86,7 +87,7 @@ namespace OpenRA.Traits
|
||||
pipxyOffset.Y -= pipSize.Y;
|
||||
}
|
||||
pipImages.PlayRepeating(pipStrings[(int)pip]);
|
||||
pipImages.Image.DrawAt(wr, pipxyBase + pipxyOffset, "chrome");
|
||||
pipImages.Image.DrawAt(pipxyBase + pipxyOffset, pal);
|
||||
pipxyOffset += new float2(pipSize.X, 0);
|
||||
}
|
||||
|
||||
@@ -104,7 +105,7 @@ namespace OpenRA.Traits
|
||||
// If a mod wants to implement a unit with multiple tags, then they are placed on multiple rows
|
||||
var tagxyBase = basePosition + new float2(-16, 2); // Correct for the offset in the shp file
|
||||
var tagxyOffset = new float2(0, 0); // Correct for offset due to multiple rows
|
||||
|
||||
var pal = wr.Palette("chrome");
|
||||
foreach (var tags in self.TraitsImplementing<ITags>())
|
||||
{
|
||||
foreach (var tag in tags.GetTags())
|
||||
@@ -114,7 +115,7 @@ namespace OpenRA.Traits
|
||||
|
||||
var tagImages = new Animation("pips");
|
||||
tagImages.PlayRepeating(tagStrings[(int)tag]);
|
||||
tagImages.Image.DrawAt(wr, tagxyBase + tagxyOffset, "chrome");
|
||||
tagImages.Image.DrawAt(tagxyBase + tagxyOffset, pal);
|
||||
|
||||
// Increment row
|
||||
tagxyOffset.Y += 8;
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace OpenRA.Traits
|
||||
public static Target[] NoTargets = {};
|
||||
|
||||
Actor actor;
|
||||
Player owner;
|
||||
PPos pos;
|
||||
bool valid;
|
||||
int generation;
|
||||
|
||||
public static Target FromActor(Actor a)
|
||||
{
|
||||
@@ -25,7 +25,7 @@ namespace OpenRA.Traits
|
||||
{
|
||||
actor = a,
|
||||
valid = (a != null),
|
||||
owner = (a != null) ? a.Owner : null
|
||||
generation = a.Generation,
|
||||
};
|
||||
}
|
||||
public static Target FromPos(PPos p) { return new Target { pos = p, valid = true }; }
|
||||
@@ -39,7 +39,7 @@ namespace OpenRA.Traits
|
||||
|
||||
public static readonly Target None = new Target();
|
||||
|
||||
public bool IsValid { get { return valid && (actor == null || (actor.IsInWorld && !actor.IsDead() && actor.Owner == owner)); } }
|
||||
public bool IsValid { get { return valid && (actor == null || (actor.IsInWorld && !actor.IsDead() && actor.Generation == generation)); } }
|
||||
public PPos PxPosition { get { return IsActor ? actor.Trait<IHasLocation>().PxPosition : pos; } }
|
||||
public PPos CenterLocation { get { return PxPosition; } }
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace OpenRA.Traits
|
||||
|
||||
public interface ITick { void Tick(Actor self); }
|
||||
public interface ITickRender { void TickRender(WorldRenderer wr, Actor self); }
|
||||
public interface IRender { IEnumerable<Renderable> Render(Actor self, WorldRenderer wr); }
|
||||
public interface IRender { IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr); }
|
||||
public interface IAutoSelectionSize { int2 SelectionSize(Actor self); }
|
||||
|
||||
public interface IIssueOrder
|
||||
@@ -44,12 +44,19 @@ namespace OpenRA.Traits
|
||||
Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued);
|
||||
}
|
||||
|
||||
[Flags] public enum TargetModifiers { None = 0, ForceAttack = 1, ForceQueue = 2, ForceMove = 4 };
|
||||
|
||||
public static class TargetModifiersExts
|
||||
{
|
||||
public static bool HasModifier(this TargetModifiers self, TargetModifiers m) { return (self & m) == m; }
|
||||
}
|
||||
|
||||
public interface IOrderTargeter
|
||||
{
|
||||
string OrderID { get; }
|
||||
int OrderPriority { get; }
|
||||
bool CanTargetActor(Actor self, Actor target, bool forceAttack, bool forceQueue, ref string cursor);
|
||||
bool CanTargetLocation(Actor self, CPos location, List<Actor> actorsAtLocation, bool forceAttack, bool forceQueue, ref string cursor);
|
||||
bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor);
|
||||
bool CanTargetLocation(Actor self, CPos location, List<Actor> actorsAtLocation, TargetModifiers modifiers, ref string cursor);
|
||||
bool IsQueued { get; }
|
||||
}
|
||||
|
||||
@@ -67,6 +74,8 @@ namespace OpenRA.Traits
|
||||
public interface INotifyOwnerChanged { void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner); }
|
||||
public interface INotifyCapture { void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner); }
|
||||
public interface INotifyOtherCaptured { void OnActorCaptured(Actor self, Actor captured, Actor captor, Player oldOwner, Player newOwner); }
|
||||
public interface INotifyHarvest { void Harvested(Actor self, ResourceType resource); }
|
||||
|
||||
public interface IAcceptInfiltrator { void OnInfiltrate(Actor self, Actor infiltrator); }
|
||||
public interface IStoreOre { int Capacity { get; } }
|
||||
public interface IToolTip
|
||||
@@ -116,7 +125,7 @@ namespace OpenRA.Traits
|
||||
}
|
||||
|
||||
public interface INotifyAttack { void Attacking(Actor self, Target target); }
|
||||
public interface IRenderModifier { IEnumerable<Renderable> ModifyRender(Actor self, WorldRenderer wr, IEnumerable<Renderable> r); }
|
||||
public interface IRenderModifier { IEnumerable<IRenderable> ModifyRender(Actor self, WorldRenderer wr, IEnumerable<IRenderable> r); }
|
||||
public interface IDamageModifier { float GetDamageModifier(Actor attacker, WarheadInfo warhead); }
|
||||
public interface ISpeedModifier { decimal GetSpeedModifier(); }
|
||||
public interface IFirepowerModifier { float GetFirepowerModifier(); }
|
||||
@@ -152,37 +161,6 @@ namespace OpenRA.Traits
|
||||
bool CrushableBy(string[] crushClasses, Player owner);
|
||||
}
|
||||
|
||||
public struct Renderable
|
||||
{
|
||||
public readonly Sprite Sprite;
|
||||
public readonly float2 Pos;
|
||||
public readonly PaletteReference Palette;
|
||||
public readonly int Z;
|
||||
public readonly int ZOffset;
|
||||
public float Scale;
|
||||
|
||||
public Renderable(Sprite sprite, float2 pos, PaletteReference palette, int z, int zOffset, float scale)
|
||||
{
|
||||
Sprite = sprite;
|
||||
Pos = pos;
|
||||
Palette = palette;
|
||||
Z = z;
|
||||
ZOffset = zOffset;
|
||||
Scale = scale; /* default */
|
||||
}
|
||||
|
||||
public Renderable(Sprite sprite, float2 pos, PaletteReference palette, int z)
|
||||
: this(sprite, pos, palette, z, 0, 1f) { }
|
||||
|
||||
public Renderable(Sprite sprite, float2 pos, PaletteReference palette, int z, float scale)
|
||||
: this(sprite, pos, palette, z, 0, scale) { }
|
||||
|
||||
public Renderable WithScale(float newScale) { return new Renderable(Sprite, Pos, Palette, Z, ZOffset, newScale); }
|
||||
public Renderable WithPalette(PaletteReference newPalette) { return new Renderable(Sprite, Pos, newPalette, Z, ZOffset, Scale); }
|
||||
public Renderable WithZOffset(int newOffset) { return new Renderable(Sprite, Pos, Palette, Z, newOffset, Scale); }
|
||||
public Renderable WithPos(float2 newPos) { return new Renderable(Sprite, newPos, Palette, Z, ZOffset, Scale); }
|
||||
}
|
||||
|
||||
public interface ITraitInfo { object Create(ActorInitializer init); }
|
||||
|
||||
public class TraitInfo<T> : ITraitInfo where T : new() { public virtual object Create(ActorInitializer init) { return new T(); } }
|
||||
@@ -210,13 +188,15 @@ namespace OpenRA.Traits
|
||||
|
||||
public interface IPostRenderSelection { void RenderAfterWorld(WorldRenderer wr); }
|
||||
public interface IPreRenderSelection { void RenderBeforeWorld(WorldRenderer wr, Actor self); }
|
||||
public interface IRenderAsTerrain { IEnumerable<Renderable> RenderAsTerrain(WorldRenderer wr, Actor self); }
|
||||
public interface ILocalCoordinatesModel
|
||||
public interface IRenderAsTerrain { IEnumerable<IRenderable> RenderAsTerrain(WorldRenderer wr, Actor self); }
|
||||
public interface IBodyOrientation
|
||||
{
|
||||
WAngle CameraPitch { get; }
|
||||
int QuantizedFacings { get; set; }
|
||||
WVec LocalToWorld(WVec vec);
|
||||
WRot QuantizeOrientation(Actor self, WRot orientation);
|
||||
}
|
||||
public interface LocalCoordinatesModelInfo {}
|
||||
public interface IBodyOrientationInfo {}
|
||||
|
||||
public interface ITargetable
|
||||
{
|
||||
|
||||
@@ -110,19 +110,19 @@ namespace OpenRA.Traits
|
||||
(next, a) => { a.Queue( next ); return a; });
|
||||
}
|
||||
|
||||
public static Activity RunActivity( Actor self, Activity act )
|
||||
public static Activity RunActivity(Actor self, Activity act)
|
||||
{
|
||||
while( act != null )
|
||||
while (act != null)
|
||||
{
|
||||
var prev = act;
|
||||
|
||||
var sw = new Stopwatch();
|
||||
act = act.Tick( self );
|
||||
act = act.Tick(self);
|
||||
var dt = sw.ElapsedTime();
|
||||
if(dt > Game.Settings.Debug.LongTickThreshold)
|
||||
if (dt > Game.Settings.Debug.LongTickThreshold)
|
||||
Log.Write("perf", "[{2}] Activity: {0} ({1:0.000} ms)", prev, dt * 1000, Game.LocalTick);
|
||||
|
||||
if( prev == act )
|
||||
if (prev == act)
|
||||
break;
|
||||
}
|
||||
return act;
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace OpenRA.Traits
|
||||
if (c.image != null)
|
||||
c.image[c.density].DrawAt(
|
||||
new CPos(x, y).ToPPos().ToFloat2(),
|
||||
c.type.info.PaletteRef.Index);
|
||||
c.type.info.PaletteRef);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,14 @@ namespace OpenRA.Traits
|
||||
Actor self;
|
||||
Map map;
|
||||
|
||||
int[,] visibleCells;
|
||||
bool[,] exploredCells;
|
||||
bool[,] foggedCells;
|
||||
int[,] visibleCount;
|
||||
int[,] generatedShroudCount;
|
||||
bool[,] explored;
|
||||
|
||||
// Cache of visibility that was added, so no matter what crazy trait code does, it
|
||||
// can't make us invalid.
|
||||
Dictionary<Actor, CPos[]> visibility = new Dictionary<Actor, CPos[]>();
|
||||
Dictionary<Actor, CPos[]> generation = new Dictionary<Actor, CPos[]>();
|
||||
|
||||
public Rectangle ExploredBounds { get; private set; }
|
||||
|
||||
@@ -43,11 +48,15 @@ namespace OpenRA.Traits
|
||||
this.self = self;
|
||||
map = self.World.Map;
|
||||
|
||||
visibleCells = new int[map.MapSize.X, map.MapSize.Y];
|
||||
exploredCells = new bool[map.MapSize.X, map.MapSize.Y];
|
||||
foggedCells = new bool[map.MapSize.X, map.MapSize.Y];
|
||||
self.World.ActorAdded += AddActor;
|
||||
self.World.ActorRemoved += RemoveActor;
|
||||
visibleCount = new int[map.MapSize.X, map.MapSize.Y];
|
||||
generatedShroudCount = new int[map.MapSize.X, map.MapSize.Y];
|
||||
explored = new bool[map.MapSize.X, map.MapSize.Y];
|
||||
|
||||
self.World.ActorAdded += AddVisibility;
|
||||
self.World.ActorRemoved += RemoveVisibility;
|
||||
|
||||
self.World.ActorAdded += AddShroudGeneration;
|
||||
self.World.ActorRemoved += RemoveShroudGeneration;
|
||||
|
||||
if (!info.Shroud)
|
||||
ExploredBounds = map.Bounds;
|
||||
@@ -58,16 +67,6 @@ namespace OpenRA.Traits
|
||||
Hash = Sync.hash_player(self.Owner) + self.World.FrameNumber * 3;
|
||||
}
|
||||
|
||||
// cache of positions that were added, so no matter what crazy trait code does, it
|
||||
// can't make us invalid.
|
||||
public class ActorVisibility
|
||||
{
|
||||
[Sync] public int range;
|
||||
[Sync] public CPos[] vis;
|
||||
}
|
||||
|
||||
public Dictionary<Actor, ActorVisibility> vis = new Dictionary<Actor, ActorVisibility>();
|
||||
|
||||
static IEnumerable<CPos> FindVisibleTiles(World world, CPos a, int r)
|
||||
{
|
||||
var min = a - new CVec(r, r);
|
||||
@@ -86,77 +85,99 @@ namespace OpenRA.Traits
|
||||
|
||||
for (var j = min.Y; j <= max.Y; j++)
|
||||
for (var i = min.X; i <= max.X; i++)
|
||||
if (r * r >= (new CPos(i, j) - a).LengthSquared)
|
||||
if (r*r >= (new CPos(i, j) - a).LengthSquared)
|
||||
yield return new CPos(i, j);
|
||||
}
|
||||
|
||||
public void AddActor(Actor a)
|
||||
void AddVisibility(Actor a)
|
||||
{
|
||||
if (!a.HasTrait<RevealsShroud>() || !a.Owner.IsAlliedWith(self.Owner))
|
||||
var rs = a.TraitOrDefault<RevealsShroud>();
|
||||
if (rs == null || !a.Owner.IsAlliedWith(self.Owner) || rs.Range == 0)
|
||||
return;
|
||||
|
||||
ActorVisibility v = a.Sight;
|
||||
if (v.range == 0)
|
||||
return;
|
||||
var origins = GetVisOrigins(a);
|
||||
var visible = origins.SelectMany(o => FindVisibleTiles(a.World, o, rs.Range))
|
||||
.Distinct().ToArray();
|
||||
|
||||
foreach (var p in v.vis)
|
||||
// Update bounding rect
|
||||
foreach (var o in origins)
|
||||
{
|
||||
foreach (var q in FindVisibleTiles(a.World, p, v.range))
|
||||
{
|
||||
++visibleCells[q.X, q.Y];
|
||||
exploredCells[q.X, q.Y] = true;
|
||||
foggedCells[q.X, q.Y] = true;
|
||||
}
|
||||
|
||||
var box = new Rectangle(p.X - v.range, p.Y - v.range, 2 * v.range + 1, 2 * v.range + 1);
|
||||
var box = new Rectangle(o.X - rs.Range, o.Y - rs.Range, 2*rs.Range + 1, 2*rs.Range + 1);
|
||||
ExploredBounds = Rectangle.Union(ExploredBounds, box);
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void HideActor(Actor a, int range)
|
||||
{
|
||||
if (a.Owner.IsAlliedWith(self.Owner))
|
||||
return;
|
||||
|
||||
var v = new ActorVisibility
|
||||
// Update visibility
|
||||
foreach (var c in visible)
|
||||
{
|
||||
vis = GetVisOrigins(a).ToArray()
|
||||
};
|
||||
|
||||
foreach (var p in v.vis)
|
||||
foreach (var q in FindVisibleTiles(a.World, p, range))
|
||||
foggedCells[q.X, q.Y] = visibleCells[q.X, q.Y] > 0;
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void UnhideActor(Actor a, ActorVisibility v, int range)
|
||||
{
|
||||
if (a.Owner.IsAlliedWith(self.Owner) || v == null)
|
||||
return;
|
||||
|
||||
foreach (var p in v.vis)
|
||||
foreach (var q in FindVisibleTiles(a.World, p, range))
|
||||
foggedCells[q.X, q.Y] = exploredCells[q.X, q.Y];
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void MergeShroud(Shroud s)
|
||||
{
|
||||
for (int i = map.Bounds.Left; i < map.Bounds.Right; i++)
|
||||
{
|
||||
for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
|
||||
{
|
||||
if (s.exploredCells[i,j] == true)
|
||||
exploredCells[i, j] = true;
|
||||
if (s.foggedCells[i,j] == true)
|
||||
foggedCells[i, j] = true;
|
||||
}
|
||||
ExploredBounds = Rectangle.Union(ExploredBounds, s.ExploredBounds);
|
||||
visibleCount[c.X, c.Y]++;
|
||||
explored[c.X, c.Y] = true;
|
||||
}
|
||||
|
||||
if (visibility.ContainsKey(a))
|
||||
throw new InvalidOperationException("Attempting to add duplicate actor visibility");
|
||||
|
||||
visibility[a] = visible;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void RemoveVisibility(Actor a)
|
||||
{
|
||||
CPos[] visible;
|
||||
if (!visibility.TryGetValue(a, out visible))
|
||||
return;
|
||||
|
||||
foreach (var c in visible)
|
||||
visibleCount[c.X, c.Y]--;
|
||||
|
||||
visibility.Remove(a);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void UpdateVisibility(Actor a)
|
||||
{
|
||||
// Actors outside the world don't have any vis
|
||||
if (!a.IsInWorld)
|
||||
return;
|
||||
|
||||
RemoveVisibility(a);
|
||||
AddVisibility(a);
|
||||
}
|
||||
|
||||
void AddShroudGeneration(Actor a)
|
||||
{
|
||||
var cs = a.TraitOrDefault<CreatesShroud>();
|
||||
if (cs == null || a.Owner.IsAlliedWith(self.Owner) || cs.Range == 0)
|
||||
return;
|
||||
|
||||
var shrouded = GetVisOrigins(a).SelectMany(o => FindVisibleTiles(a.World, o, cs.Range))
|
||||
.Distinct().ToArray();
|
||||
foreach (var c in shrouded)
|
||||
generatedShroudCount[c.X, c.Y]++;
|
||||
|
||||
if (generation.ContainsKey(a))
|
||||
throw new InvalidOperationException("Attempting to add duplicate shroud generation");
|
||||
|
||||
generation[a] = shrouded;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void RemoveShroudGeneration(Actor a)
|
||||
{
|
||||
CPos[] shrouded;
|
||||
if (!generation.TryGetValue(a, out shrouded))
|
||||
return;
|
||||
|
||||
foreach (var c in shrouded)
|
||||
generatedShroudCount[c.X, c.Y]--;
|
||||
|
||||
generation.Remove(a);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void UpdateShroudGeneration(Actor a)
|
||||
{
|
||||
RemoveShroudGeneration(a);
|
||||
AddShroudGeneration(a);
|
||||
}
|
||||
|
||||
public void UpdatePlayerStance(World w, Player player, Stance oldStance, Stance newStance)
|
||||
@@ -164,29 +185,11 @@ namespace OpenRA.Traits
|
||||
if (oldStance == newStance)
|
||||
return;
|
||||
|
||||
// No longer our ally; remove unit vis
|
||||
if (oldStance == Stance.Ally)
|
||||
foreach (var a in w.Actors.Where(a => a.Owner == player))
|
||||
{
|
||||
var toRemove = w.Actors.Where(a => a.Owner == player).ToList();
|
||||
foreach (var a in toRemove)
|
||||
RemoveActor(a);
|
||||
UpdateVisibility(a);
|
||||
UpdateShroudGeneration(a);
|
||||
}
|
||||
|
||||
// Is now our ally; add unit vis
|
||||
if (newStance == Stance.Ally)
|
||||
foreach (var a in w.Actors.Where( a => a.Owner == player ))
|
||||
AddActor(a);
|
||||
}
|
||||
|
||||
public int Explored()
|
||||
{
|
||||
int seen = 0;
|
||||
for (int i = map.Bounds.Left; i < map.Bounds.Right; i++)
|
||||
for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
|
||||
if (foggedCells[i, j])
|
||||
seen++;
|
||||
|
||||
return seen;
|
||||
}
|
||||
|
||||
public static IEnumerable<CPos> GetVisOrigins(Actor a)
|
||||
@@ -195,64 +198,40 @@ namespace OpenRA.Traits
|
||||
if (ios != null)
|
||||
{
|
||||
var cells = ios.OccupiedCells();
|
||||
if (cells.Any()) return cells.Select(c => c.First);
|
||||
if (cells.Any())
|
||||
return cells.Select(c => c.First);
|
||||
}
|
||||
|
||||
return new[] { a.CenterLocation.ToCPos() };
|
||||
}
|
||||
|
||||
public void RemoveActor(Actor a)
|
||||
{
|
||||
ActorVisibility v = a.Sight;
|
||||
if (!a.Owner.IsAlliedWith(self.Owner))
|
||||
{
|
||||
if (a.HasTrait<CreatesShroud>())
|
||||
foreach (var p in v.vis)
|
||||
foreach (var q in FindVisibleTiles(a.World, p, v.range))
|
||||
foggedCells[q.X, q.Y] = exploredCells[q.X, q.Y];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!a.HasTrait<RevealsShroud>())
|
||||
return;
|
||||
|
||||
foreach (var p in v.vis)
|
||||
foreach (var q in FindVisibleTiles(a.World, p, v.range))
|
||||
--visibleCells[q.X, q.Y];
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void UpdateActor(Actor a)
|
||||
{
|
||||
if (!a.Owner.IsAlliedWith(self.Owner))
|
||||
return;
|
||||
|
||||
RemoveActor(a);
|
||||
AddActor(a);
|
||||
}
|
||||
|
||||
public void Explore(World world, CPos center, int range)
|
||||
{
|
||||
foreach (var q in FindVisibleTiles(world, center, range)) {
|
||||
exploredCells[q.X, q.Y] = true;
|
||||
foggedCells[q.X, q.Y] = true;
|
||||
}
|
||||
foreach (var q in FindVisibleTiles(world, center, range))
|
||||
explored[q.X, q.Y] = true;
|
||||
|
||||
var box = new Rectangle(center.X - range, center.Y - range, 2 * range + 1, 2 * range + 1);
|
||||
var box = new Rectangle(center.X - range, center.Y - range, 2*range + 1, 2*range + 1);
|
||||
ExploredBounds = Rectangle.Union(ExploredBounds, box);
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void Explore(Shroud s)
|
||||
{
|
||||
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
|
||||
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
|
||||
if (s.explored[i,j] == true)
|
||||
explored[i, j] = true;
|
||||
|
||||
ExploredBounds = Rectangle.Union(ExploredBounds, s.ExploredBounds);
|
||||
}
|
||||
|
||||
public void ExploreAll(World world)
|
||||
{
|
||||
for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) {
|
||||
for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) {
|
||||
exploredCells[i, j] = true;
|
||||
foggedCells[i, j] = true;
|
||||
}
|
||||
}
|
||||
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
|
||||
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
|
||||
explored[i, j] = true;
|
||||
|
||||
ExploredBounds = world.Map.Bounds;
|
||||
|
||||
Invalidate();
|
||||
@@ -260,13 +239,9 @@ namespace OpenRA.Traits
|
||||
|
||||
public void ResetExploration()
|
||||
{
|
||||
for (var j = 0; j <= exploredCells.GetUpperBound(1); j++)
|
||||
for (var i = 0; i <= exploredCells.GetUpperBound(0); i++)
|
||||
exploredCells[i, j] = visibleCells[i, j] > 0;
|
||||
|
||||
for (var j = 0; j <= foggedCells.GetUpperBound(1); j++)
|
||||
for (var i = 0; i <= foggedCells.GetUpperBound(0); i++)
|
||||
foggedCells[i, j] = visibleCells[i, j] > 0;
|
||||
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
|
||||
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
|
||||
explored[i, j] = visibleCount[i, j] > 0;
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
@@ -280,7 +255,7 @@ namespace OpenRA.Traits
|
||||
if (!Info.Shroud)
|
||||
return true;
|
||||
|
||||
return foggedCells[x,y];
|
||||
return explored[x, y] && (generatedShroudCount[x, y] == 0 || visibleCount[x, y] > 0);
|
||||
}
|
||||
|
||||
public bool IsExplored(Actor a)
|
||||
@@ -299,7 +274,7 @@ namespace OpenRA.Traits
|
||||
if (!Info.Fog)
|
||||
return true;
|
||||
|
||||
return visibleCells[x,y] != 0;
|
||||
return visibleCount[x, y] > 0;
|
||||
}
|
||||
|
||||
// Actors are hidden under shroud, but not under fog by default
|
||||
@@ -311,7 +286,8 @@ namespace OpenRA.Traits
|
||||
return a.Owner.IsAlliedWith(self.Owner) || IsExplored(a);
|
||||
}
|
||||
|
||||
public bool IsTargetable(Actor a) {
|
||||
public bool IsTargetable(Actor a)
|
||||
{
|
||||
if (a.TraitsImplementing<IVisibilityModifier>().Any(t => !t.IsVisible(a, self.Owner)))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Widgets
|
||||
{
|
||||
static class ChromeMetrics
|
||||
public static class ChromeMetrics
|
||||
{
|
||||
static Dictionary<string, string> data = new Dictionary<string, string>();
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace OpenRA.Widgets
|
||||
|
||||
public static T Get<T>(string key)
|
||||
{
|
||||
return FieldLoader.GetValue<T>( key, data[key] );
|
||||
return FieldLoader.GetValue<T>(key, data[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace OpenRA.Widgets
|
||||
public string Background = "scrollpanel-bg";
|
||||
public int ContentHeight = 0;
|
||||
public ILayout Layout;
|
||||
public int MinimumThumbSize = 10;
|
||||
protected float ListOffset = 0;
|
||||
protected bool UpPressed = false;
|
||||
protected bool DownPressed = false;
|
||||
@@ -79,7 +80,7 @@ namespace OpenRA.Widgets
|
||||
|
||||
var ScrollbarHeight = rb.Height - 2 * ScrollbarWidth;
|
||||
|
||||
var thumbHeight = ContentHeight == 0 ? 0 : (int)(ScrollbarHeight*Math.Min(rb.Height*1f/ContentHeight, 1f));
|
||||
var thumbHeight = ContentHeight == 0 ? 0 : Math.Max(MinimumThumbSize, (int)(ScrollbarHeight*Math.Min(rb.Height*1f/ContentHeight, 1f)));
|
||||
var thumbOrigin = rb.Y + ScrollbarWidth + (int)((ScrollbarHeight - thumbHeight)*(-1f*ListOffset/(ContentHeight - rb.Height)));
|
||||
if (thumbHeight == ScrollbarHeight)
|
||||
thumbHeight = 0;
|
||||
@@ -206,7 +207,7 @@ namespace OpenRA.Widgets
|
||||
{
|
||||
var rb = RenderBounds;
|
||||
var ScrollbarHeight = rb.Height - 2 * ScrollbarWidth;
|
||||
var thumbHeight = ContentHeight == 0 ? 0 : (int)(ScrollbarHeight*Math.Min(rb.Height*1f/ContentHeight, 1f));
|
||||
var thumbHeight = ContentHeight == 0 ? 0 : Math.Max(MinimumThumbSize, (int)(ScrollbarHeight*Math.Min(rb.Height*1f/ContentHeight, 1f)));
|
||||
var oldOffset = ListOffset;
|
||||
ListOffset += (int)((lastMouseLocation.Y - mi.Location.Y)*(ContentHeight - rb.Height)*1f/(ScrollbarHeight - thumbHeight));
|
||||
ListOffset = Math.Min(0,Math.Max(rb.Height - ContentHeight, ListOffset));
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace OpenRA.Widgets
|
||||
public string Image = "";
|
||||
public int Frame = 0;
|
||||
public string Palette = "chrome";
|
||||
public bool LoopAnimation = false;
|
||||
|
||||
public Func<string> GetImage;
|
||||
public Func<int> GetFrame;
|
||||
@@ -26,12 +27,13 @@ namespace OpenRA.Widgets
|
||||
readonly WorldRenderer worldRenderer;
|
||||
|
||||
[ObjectCreator.UseCtor]
|
||||
public ShpImageWidget( WorldRenderer worldRenderer)
|
||||
public ShpImageWidget(WorldRenderer worldRenderer)
|
||||
: base()
|
||||
{
|
||||
GetImage = () => { return Image; };
|
||||
GetFrame = () => { return Frame; };
|
||||
GetPalette = () => { return Palette; };
|
||||
|
||||
this.worldRenderer = worldRenderer;
|
||||
}
|
||||
|
||||
@@ -41,9 +43,12 @@ namespace OpenRA.Widgets
|
||||
Image = other.Image;
|
||||
Frame = other.Frame;
|
||||
Palette = other.Palette;
|
||||
LoopAnimation = other.LoopAnimation;
|
||||
|
||||
GetImage = other.GetImage;
|
||||
GetFrame = other.GetFrame;
|
||||
GetPalette = other.GetPalette;
|
||||
|
||||
worldRenderer = other.worldRenderer;
|
||||
}
|
||||
|
||||
@@ -66,7 +71,34 @@ namespace OpenRA.Widgets
|
||||
cachedFrame = frame;
|
||||
}
|
||||
|
||||
Game.Renderer.SpriteRenderer.DrawSprite(sprite, RenderOrigin, worldRenderer, palette);
|
||||
Game.Renderer.SpriteRenderer.DrawSprite(sprite, RenderOrigin, worldRenderer.Palette(palette));
|
||||
}
|
||||
|
||||
public int FrameCount
|
||||
{
|
||||
get { return Game.modData.SpriteLoader.LoadAllSprites(Image).Length-1; }
|
||||
}
|
||||
|
||||
public void RenderNextFrame()
|
||||
{
|
||||
if (Frame < FrameCount)
|
||||
Frame++;
|
||||
else
|
||||
Frame = 0;
|
||||
}
|
||||
|
||||
public void RenderPreviousFrame()
|
||||
{
|
||||
if (Frame > 0)
|
||||
Frame--;
|
||||
else
|
||||
Frame = FrameCount;
|
||||
}
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
if (LoopAnimation)
|
||||
RenderNextFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,15 @@ namespace OpenRA.Widgets
|
||||
public float MinimumValue = 0;
|
||||
public float MaximumValue = 1;
|
||||
public float Value = 0;
|
||||
public Func<float> GetValue;
|
||||
|
||||
protected bool isMoving = false;
|
||||
|
||||
public SliderWidget() : base() {}
|
||||
public SliderWidget()
|
||||
: base()
|
||||
{
|
||||
GetValue = () => Value;
|
||||
}
|
||||
|
||||
public SliderWidget(SliderWidget other)
|
||||
: base(other)
|
||||
@@ -38,6 +43,7 @@ namespace OpenRA.Widgets
|
||||
MaximumValue = other.MaximumValue;
|
||||
Value = other.Value;
|
||||
TrackHeight = other.TrackHeight;
|
||||
GetValue = other.GetValue;
|
||||
}
|
||||
|
||||
void UpdateValue(float newValue)
|
||||
@@ -53,7 +59,7 @@ namespace OpenRA.Widgets
|
||||
if (mi.Event == MouseInputEvent.Down && !TakeFocus(mi)) return false;
|
||||
if (!Focused) return false;
|
||||
|
||||
switch( mi.Event )
|
||||
switch(mi.Event)
|
||||
{
|
||||
case MouseInputEvent.Up:
|
||||
isMoving = false;
|
||||
@@ -99,6 +105,8 @@ namespace OpenRA.Widgets
|
||||
if (!IsVisible())
|
||||
return;
|
||||
|
||||
Value = GetValue();
|
||||
|
||||
var tr = ThumbRect;
|
||||
var rb = RenderBounds;
|
||||
var trackWidth = rb.Width;
|
||||
|
||||
@@ -48,6 +48,15 @@ namespace OpenRA.Widgets
|
||||
return window;
|
||||
}
|
||||
|
||||
public static T LoadWidget<T>(string id, Widget parent, WidgetArgs args) where T : Widget
|
||||
{
|
||||
var widget = LoadWidget(id, parent, args) as T;
|
||||
if (widget == null)
|
||||
throw new InvalidOperationException(
|
||||
"Widget {0} is not of type {1}".F(id, typeof(T).Name));
|
||||
return widget;
|
||||
}
|
||||
|
||||
public static Widget LoadWidget(string id, Widget parent, WidgetArgs args)
|
||||
{
|
||||
return Game.modData.WidgetLoader.LoadWidget(args, parent, id);
|
||||
|
||||
@@ -31,12 +31,12 @@ namespace OpenRA.Widgets
|
||||
|
||||
public static void DrawSHP(Sprite s, float2 pos, WorldRenderer wr)
|
||||
{
|
||||
Game.Renderer.SpriteRenderer.DrawSprite(s,pos, wr, "chrome");
|
||||
Game.Renderer.SpriteRenderer.DrawSprite(s, pos, wr.Palette("chrome"));
|
||||
}
|
||||
|
||||
public static void DrawSHP(Sprite s, float2 pos, WorldRenderer wr, float2 size)
|
||||
{
|
||||
Game.Renderer.SpriteRenderer.DrawSprite(s, pos, wr, "chrome", size);
|
||||
Game.Renderer.SpriteRenderer.DrawSprite(s, pos, wr.Palette("chrome"), size);
|
||||
}
|
||||
|
||||
public static void DrawPanel(string collection, Rectangle Bounds)
|
||||
@@ -227,6 +227,12 @@ namespace OpenRA.Widgets
|
||||
return Mod.AllMods[mod].Title;
|
||||
}
|
||||
|
||||
public static string ActiveModId()
|
||||
{
|
||||
var mod = Game.modData.Manifest.Mods[0];
|
||||
return Mod.AllMods[mod].Id;
|
||||
}
|
||||
|
||||
public static string ChooseInitialMap(string map)
|
||||
{
|
||||
var availableMaps = Game.modData.AvailableMaps;
|
||||
|
||||
@@ -120,7 +120,6 @@ namespace OpenRA.Mods.Cnc
|
||||
public void StartGame()
|
||||
{
|
||||
TestAndContinue();
|
||||
Game.JoinExternalGame();
|
||||
}
|
||||
|
||||
void TestAndContinue()
|
||||
|
||||
@@ -32,10 +32,10 @@ namespace OpenRA.Mods.Cnc.Effects
|
||||
|
||||
public void Tick(World world) { anim.Tick(); }
|
||||
|
||||
public IEnumerable<Renderable> Render(WorldRenderer wr)
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
yield return new Renderable(anim.Image,
|
||||
target.CenterLocation.ToFloat2() - new float2(.5f * anim.Image.size.X, anim.Image.size.Y - Game.CellSize),
|
||||
yield return new SpriteRenderable(anim.Image,
|
||||
target.CenterLocation.ToFloat2() - new float2(0, 0.5f*anim.Image.size.Y - Game.CellSize),
|
||||
wr.Palette("effect"), (int)target.CenterLocation.Y);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user