Compare commits
549 Commits
playtest-2
...
playtest-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3c5aa53be | ||
|
|
adfb905f97 | ||
|
|
c2ead21bf0 | ||
|
|
aaaa5c78de | ||
|
|
b5105e5e50 | ||
|
|
b18110012b | ||
|
|
38bf3eb30d | ||
|
|
7ad4ba7adb | ||
|
|
9d624579fc | ||
|
|
46f0978789 | ||
|
|
880f635e83 | ||
|
|
3bfb43a305 | ||
|
|
4ca4aebf50 | ||
|
|
8df18da419 | ||
|
|
98b4d6e828 | ||
|
|
5f1d4a4ff1 | ||
|
|
dc722b0a0c | ||
|
|
142e6776af | ||
|
|
baa786a4fd | ||
|
|
75ccdfce7a | ||
|
|
1672041dd4 | ||
|
|
f63bd278e1 | ||
|
|
0a2731fc72 | ||
|
|
c304a0682e | ||
|
|
1372b5dd85 | ||
|
|
1f861ce35c | ||
|
|
fce4afd448 | ||
|
|
210de9440c | ||
|
|
d3f1f39635 | ||
|
|
789676919c | ||
|
|
9cafc1d7b4 | ||
|
|
76497eaea6 | ||
|
|
93cef0241d | ||
|
|
a641ac08f8 | ||
|
|
2d0ee684b8 | ||
|
|
f83d680197 | ||
|
|
b54e45f4a4 | ||
|
|
55b8f42ac5 | ||
|
|
8f16a792cc | ||
|
|
66282cbd87 | ||
|
|
96ce888d97 | ||
|
|
0c7a1ec3bf | ||
|
|
5c8aa7f0f7 | ||
|
|
45fa0dc41e | ||
|
|
3cb807329a | ||
|
|
6215e4358a | ||
|
|
caddb39e79 | ||
|
|
fff1d0bfa9 | ||
|
|
f26a7d7a3f | ||
|
|
b9d1a83158 | ||
|
|
b568023489 | ||
|
|
f6bc07894b | ||
|
|
da451896e8 | ||
|
|
7d26e78cb3 | ||
|
|
4c28776a93 | ||
|
|
6a8f4301fb | ||
|
|
6ddbab929c | ||
|
|
42779c72cf | ||
|
|
35acae244c | ||
|
|
965de05b0a | ||
|
|
75f5076ac3 | ||
|
|
70cddb72f2 | ||
|
|
19da3bc4c2 | ||
|
|
f950a61886 | ||
|
|
d3fbb83877 | ||
|
|
8e85431266 | ||
|
|
ecd4d996c7 | ||
|
|
378fecd2db | ||
|
|
67d3010d78 | ||
|
|
eee71368a8 | ||
|
|
527b4da3f2 | ||
|
|
7ae2822b6e | ||
|
|
8fdddd238b | ||
|
|
8dfcc716c8 | ||
|
|
892b60d0eb | ||
|
|
987f2a26fa | ||
|
|
cc2912b993 | ||
|
|
76b15d7e37 | ||
|
|
1a0b975d31 | ||
|
|
b55a1b86db | ||
|
|
7840f92f39 | ||
|
|
c13a0e2c9e | ||
|
|
edd92c7d3b | ||
|
|
beb9cae9ca | ||
|
|
1ecb921364 | ||
|
|
ffc7314a7f | ||
|
|
a4420bad23 | ||
|
|
b8c90ef506 | ||
|
|
f30b659b3f | ||
|
|
35ee5b807a | ||
|
|
7f21a868bd | ||
|
|
1eb8c395e7 | ||
|
|
d2a8a8f19a | ||
|
|
b826c5e22a | ||
|
|
65f5b763e9 | ||
|
|
b167424654 | ||
|
|
0777fe534d | ||
|
|
c3a8065b99 | ||
|
|
59dcab4db0 | ||
|
|
4631cbfc2a | ||
|
|
315f1188ed | ||
|
|
eae3f297f3 | ||
|
|
6de92de8d9 | ||
|
|
b05d246c48 | ||
|
|
3a1656c3dd | ||
|
|
b2649749d9 | ||
|
|
137d3be346 | ||
|
|
e36ef57e35 | ||
|
|
481e5e03d8 | ||
|
|
4d3db0d454 | ||
|
|
01f5c67036 | ||
|
|
f929087d15 | ||
|
|
ae38133c9f | ||
|
|
0c7158efcd | ||
|
|
6b9a2a3c29 | ||
|
|
f5d788f4fc | ||
|
|
caead311cb | ||
|
|
f26905f5d0 | ||
|
|
a77d2f15b1 | ||
|
|
5a8f7f1a5f | ||
|
|
3d9e877eb2 | ||
|
|
8a6a68feef | ||
|
|
6fc291a634 | ||
|
|
4578ea09ba | ||
|
|
baac3f3ee9 | ||
|
|
816cb2cdc2 | ||
|
|
0c540cd41e | ||
|
|
6b4ba96e34 | ||
|
|
b0188cc476 | ||
|
|
eaa9b49793 | ||
|
|
d7c54d74ad | ||
|
|
bb5e0eafba | ||
|
|
11b064a333 | ||
|
|
501c029579 | ||
|
|
7cde528969 | ||
|
|
a162cdda41 | ||
|
|
3aedeefced | ||
|
|
3ec2f23109 | ||
|
|
4d56ecb3a8 | ||
|
|
e23b6f8a9d | ||
|
|
8c94f262b6 | ||
|
|
f18d874524 | ||
|
|
63f76fc277 | ||
|
|
7ccfe0d2e7 | ||
|
|
59f2f5669f | ||
|
|
d179f6eaae | ||
|
|
54c2894b4e | ||
|
|
b74ff33039 | ||
|
|
cc004b3546 | ||
|
|
1facff6ab1 | ||
|
|
d751c055b5 | ||
|
|
f5d12bfde1 | ||
|
|
0ff4e466ee | ||
|
|
9c4cb9091e | ||
|
|
8689030f79 | ||
|
|
7608922ff0 | ||
|
|
809f57e48d | ||
|
|
a0089d97e6 | ||
|
|
00faccdecc | ||
|
|
2a2ad71db9 | ||
|
|
4dea39fffe | ||
|
|
69105180eb | ||
|
|
601990aa27 | ||
|
|
83e44bee66 | ||
|
|
b41c178cb9 | ||
|
|
7184f5f97e | ||
|
|
a2ac95d140 | ||
|
|
85a97998aa | ||
|
|
863091d5cc | ||
|
|
be310ab6a6 | ||
|
|
305d82f887 | ||
|
|
e292e88bff | ||
|
|
680ffffff2 | ||
|
|
3507167e79 | ||
|
|
bbc83c1799 | ||
|
|
9b3ddee517 | ||
|
|
e5f34a99ac | ||
|
|
3817c7b96f | ||
|
|
65269f7950 | ||
|
|
7ed67338f3 | ||
|
|
5efbcf19f2 | ||
|
|
6db27b1839 | ||
|
|
d823d38e8c | ||
|
|
e71a31925f | ||
|
|
d10d48c25f | ||
|
|
9914848356 | ||
|
|
d46710d6ce | ||
|
|
04c34741c8 | ||
|
|
9f30e2ecb0 | ||
|
|
224377f078 | ||
|
|
5f79c31a57 | ||
|
|
c34dd4b824 | ||
|
|
0406b89a96 | ||
|
|
2ac7e451b4 | ||
|
|
8eeb6d68e7 | ||
|
|
3e490e5843 | ||
|
|
f238e2c5cc | ||
|
|
b048e9c77b | ||
|
|
98b80d44eb | ||
|
|
04359206ff | ||
|
|
b7317f2202 | ||
|
|
cc707f0037 | ||
|
|
081182b60f | ||
|
|
9cee77ed8c | ||
|
|
ee221f3e0d | ||
|
|
48360bad8b | ||
|
|
fe05dad670 | ||
|
|
ea9f12ffbc | ||
|
|
4723e5ddb9 | ||
|
|
f6768fe624 | ||
|
|
1d98b8b8f0 | ||
|
|
07fc67f58d | ||
|
|
38f341ac1d | ||
|
|
9be7298311 | ||
|
|
13f5ef50b9 | ||
|
|
3e7caa2faa | ||
|
|
d9f8afdbe5 | ||
|
|
73198dc45a | ||
|
|
4b6853b433 | ||
|
|
72923b9572 | ||
|
|
e6668bbb59 | ||
|
|
bdbc19376a | ||
|
|
20ba45d467 | ||
|
|
ac3e601edf | ||
|
|
d8220b390a | ||
|
|
1553a8a5cb | ||
|
|
87fa8a77c2 | ||
|
|
eaca8b6287 | ||
|
|
7c9856ded7 | ||
|
|
7503919659 | ||
|
|
eb86160021 | ||
|
|
4de0d0fcb9 | ||
|
|
c2d6b78b18 | ||
|
|
6ac7f887c0 | ||
|
|
9e85aefca8 | ||
|
|
7454427b13 | ||
|
|
8a95241fd5 | ||
|
|
89161b61ec | ||
|
|
fac271245b | ||
|
|
66464a6164 | ||
|
|
26363e5811 | ||
|
|
7ddcc2e958 | ||
|
|
451a38338b | ||
|
|
c195699476 | ||
|
|
9c08e430e2 | ||
|
|
32968e4f4b | ||
|
|
67cba65800 | ||
|
|
22bece2dc9 | ||
|
|
9b4db3468b | ||
|
|
2d4d6cdc1b | ||
|
|
807a40c209 | ||
|
|
80842fd4b8 | ||
|
|
3be008f592 | ||
|
|
5f2cc5981d | ||
|
|
10e51db236 | ||
|
|
1eb573bcbc | ||
|
|
fcb09d069b | ||
|
|
310b63150f | ||
|
|
c3f4bc484d | ||
|
|
53032576e2 | ||
|
|
6a599e57f6 | ||
|
|
99de33bbe3 | ||
|
|
e01953afa3 | ||
|
|
1af9efe246 | ||
|
|
3b6024c086 | ||
|
|
b8d3c9f73a | ||
|
|
560d7b4ee8 | ||
|
|
7d695f0c8f | ||
|
|
bc009634e5 | ||
|
|
ad4b4dc7f8 | ||
|
|
0fca984463 | ||
|
|
89051d40e8 | ||
|
|
7323db1492 | ||
|
|
b1a44086a0 | ||
|
|
db64dc82c3 | ||
|
|
cd82382f68 | ||
|
|
16e78b8ca8 | ||
|
|
becfc154c5 | ||
|
|
4987d45f23 | ||
|
|
3224843d70 | ||
|
|
51ec97fb2c | ||
|
|
74fa8752c9 | ||
|
|
4c9c8bf7fc | ||
|
|
2064dc7c30 | ||
|
|
d653614e75 | ||
|
|
ae0a0163cc | ||
|
|
31f2441709 | ||
|
|
927b6cd561 | ||
|
|
04c69efc30 | ||
|
|
71fb670def | ||
|
|
92912c6c94 | ||
|
|
e2227b9450 | ||
|
|
577fc1c409 | ||
|
|
54d3656205 | ||
|
|
a3d9822bb3 | ||
|
|
b1db79cce2 | ||
|
|
faad2b8653 | ||
|
|
d9ecbb0351 | ||
|
|
dd39ab5b12 | ||
|
|
dd92ec4d02 | ||
|
|
a7279415dc | ||
|
|
81d53a4f1a | ||
|
|
f05e3e871f | ||
|
|
f968b169ad | ||
|
|
272d9b99fd | ||
|
|
cde18221e6 | ||
|
|
c0ee199ad1 | ||
|
|
a03abe78af | ||
|
|
4a4415c74b | ||
|
|
394e33dcc2 | ||
|
|
ea3731a7cc | ||
|
|
5e5183549c | ||
|
|
7f255a17da | ||
|
|
557c87eecb | ||
|
|
fc6ada38f4 | ||
|
|
47a470e945 | ||
|
|
e77aaa1a47 | ||
|
|
e57087cb5b | ||
|
|
7bc53dd266 | ||
|
|
94088d37a6 | ||
|
|
492bcdd9a7 | ||
|
|
18b84750aa | ||
|
|
0901a7d9de | ||
|
|
1b9f23eca0 | ||
|
|
78a2d9aa23 | ||
|
|
ae3bfb73a1 | ||
|
|
3d6b170ec3 | ||
|
|
346e670563 | ||
|
|
7e67ce0139 | ||
|
|
4ea3e8382d | ||
|
|
e42094625d | ||
|
|
5899636e10 | ||
|
|
8f1d8a67cc | ||
|
|
2485029452 | ||
|
|
139d5efba8 | ||
|
|
ed7d12506d | ||
|
|
db58b35856 | ||
|
|
7e20bdd7ea | ||
|
|
89e3b62f61 | ||
|
|
8e00ddedc7 | ||
|
|
95c5c683e3 | ||
|
|
a12bb54ded | ||
|
|
c05527c561 | ||
|
|
5a0c1459b2 | ||
|
|
d46e47d16d | ||
|
|
8b8a14e0b8 | ||
|
|
2ad8179672 | ||
|
|
6b7f1c6458 | ||
|
|
eb61c45e14 | ||
|
|
dee6d03626 | ||
|
|
0b89883012 | ||
|
|
e05baf6ec7 | ||
|
|
1a16ef3537 | ||
|
|
9cbe7bc62d | ||
|
|
cc036cfd62 | ||
|
|
98006d3870 | ||
|
|
2dcd377aaf | ||
|
|
5f17f0b5b0 | ||
|
|
f066655bb7 | ||
|
|
f18ce8cfda | ||
|
|
e2050dfdc9 | ||
|
|
b815e80e99 | ||
|
|
f3cbc5f72b | ||
|
|
93977782a7 | ||
|
|
3e73357619 | ||
|
|
422ba16ed9 | ||
|
|
cf4dc42c8f | ||
|
|
a6716e2c2d | ||
|
|
ed5bb1b1a1 | ||
|
|
a06cfb4004 | ||
|
|
fec9fe1ad4 | ||
|
|
9cf8cba750 | ||
|
|
9972d69c5d | ||
|
|
faa35946b8 | ||
|
|
47c4be9191 | ||
|
|
9bcb222a2d | ||
|
|
5b00c12ca3 | ||
|
|
53304a0353 | ||
|
|
831ec0aeda | ||
|
|
e038b86742 | ||
|
|
14607f55c5 | ||
|
|
26b0a06a17 | ||
|
|
450dc70375 | ||
|
|
c71f97e2c6 | ||
|
|
52900f8112 | ||
|
|
1abfaa94af | ||
|
|
769f9429f9 | ||
|
|
de8fa56461 | ||
|
|
22bd7fd90b | ||
|
|
b1b35c1e1b | ||
|
|
ccad3bd185 | ||
|
|
a4405009c8 | ||
|
|
132268bc49 | ||
|
|
e50b0b193d | ||
|
|
db1f794beb | ||
|
|
d97a2a5fa0 | ||
|
|
50423b13fb | ||
|
|
4d2f1f8942 | ||
|
|
bab34252dd | ||
|
|
a53ef6e503 | ||
|
|
588a5d784f | ||
|
|
3b16938ae5 | ||
|
|
b88b84c05a | ||
|
|
00dc161628 | ||
|
|
38e8bed9c9 | ||
|
|
eb0e2eeb9d | ||
|
|
416f529cb0 | ||
|
|
143951a409 | ||
|
|
cc9da63323 | ||
|
|
01d340db09 | ||
|
|
f60b2275fc | ||
|
|
536f5f05a8 | ||
|
|
dd3c600258 | ||
|
|
a7de079a53 | ||
|
|
cfaf5a6467 | ||
|
|
9f82ef999f | ||
|
|
e353c8c176 | ||
|
|
52a7d39e51 | ||
|
|
3bfcecd539 | ||
|
|
6cd1919cca | ||
|
|
abdb1f7547 | ||
|
|
adc03c41b1 | ||
|
|
893e20ee76 | ||
|
|
693b5a54af | ||
|
|
48248266a8 | ||
|
|
28623ce54c | ||
|
|
640078a2b1 | ||
|
|
83cd7cf485 | ||
|
|
ade85f8977 | ||
|
|
1f7edf9f0b | ||
|
|
13769d48a1 | ||
|
|
43693d84d1 | ||
|
|
0b0c3b7170 | ||
|
|
b53c13dca4 | ||
|
|
2bb2cd51cc | ||
|
|
4298584af2 | ||
|
|
a86f41cd5c | ||
|
|
ec15acbc80 | ||
|
|
28c89920ac | ||
|
|
619457828f | ||
|
|
8144fca5be | ||
|
|
9fb8f6c6f8 | ||
|
|
288dfdbf03 | ||
|
|
09d8aafddf | ||
|
|
096de8f5aa | ||
|
|
f668e18f37 | ||
|
|
f342ecf18a | ||
|
|
a68ea0fe2d | ||
|
|
091f660dc7 | ||
|
|
a0ad79e555 | ||
|
|
d7f81d4a20 | ||
|
|
fac758f38e | ||
|
|
c2b1a5f4e0 | ||
|
|
8834cee13c | ||
|
|
7057e32902 | ||
|
|
90c2249317 | ||
|
|
cf84e6f8d5 | ||
|
|
e353ff326e | ||
|
|
bb7c19ad02 | ||
|
|
1098804f9b | ||
|
|
9bcb754836 | ||
|
|
e8eb4d9563 | ||
|
|
7d59602c0b | ||
|
|
978d447d42 | ||
|
|
92e8fbf4d0 | ||
|
|
d9946f63e4 | ||
|
|
2e3f2e079a | ||
|
|
cb05d8a98d | ||
|
|
4e7a35b50f | ||
|
|
399e451ada | ||
|
|
7438af8266 | ||
|
|
3f4d5fa68c | ||
|
|
91adc61abd | ||
|
|
c242333922 | ||
|
|
953d76a20a | ||
|
|
3f9fde8855 | ||
|
|
95558a36ab | ||
|
|
ef7c49c116 | ||
|
|
16ff9fbc8e | ||
|
|
bbf6e38faa | ||
|
|
8533aa8d26 | ||
|
|
0a507f3d33 | ||
|
|
3abc85b588 | ||
|
|
024722a96c | ||
|
|
5d1c37a4c5 | ||
|
|
48f2519811 | ||
|
|
0c098e74f1 | ||
|
|
8475bd6294 | ||
|
|
cdfa52d918 | ||
|
|
67d9ad6a93 | ||
|
|
1f71377d82 | ||
|
|
8634c001f9 | ||
|
|
e374c8e6c3 | ||
|
|
77bb39304b | ||
|
|
9ec22e48a6 | ||
|
|
03adceb656 | ||
|
|
efccd610d3 | ||
|
|
d37119655b | ||
|
|
8c5caaf154 | ||
|
|
4a5525d1af | ||
|
|
6df243f79b | ||
|
|
e56cb9901b | ||
|
|
20dbf76e81 | ||
|
|
4375dc2fc1 | ||
|
|
6f864b055d | ||
|
|
e6d552eee7 | ||
|
|
828106cf82 | ||
|
|
1977e64e07 | ||
|
|
c983dda077 | ||
|
|
09b9ed3506 | ||
|
|
1bfe70e923 | ||
|
|
ba231636f5 | ||
|
|
46cee82027 | ||
|
|
77d03ce1e1 | ||
|
|
fc79e04c49 | ||
|
|
3f9aab7e86 | ||
|
|
6810ac92ba | ||
|
|
c3a0d129a3 | ||
|
|
ae92255ded | ||
|
|
96032d1953 | ||
|
|
bd569c9ae2 | ||
|
|
3711a695c5 | ||
|
|
94c47fdac0 | ||
|
|
60a492803d | ||
|
|
4473059c60 | ||
|
|
1b1521812c | ||
|
|
d110a21bfe | ||
|
|
0b53c1f139 | ||
|
|
1927b88a18 | ||
|
|
e57f1dcd8c | ||
|
|
6c401f0f9a | ||
|
|
35600d9291 | ||
|
|
a51b916eaa | ||
|
|
5c42f55b3a | ||
|
|
3f81df9c52 | ||
|
|
a0dcd9e106 | ||
|
|
d6737ccfab | ||
|
|
3661dbdfd0 | ||
|
|
599e87c4aa | ||
|
|
567ab47765 | ||
|
|
c97f36793c | ||
|
|
e179a9529e | ||
|
|
606bf6d9bb | ||
|
|
e8068cf329 | ||
|
|
8f15535da0 | ||
|
|
d062523700 | ||
|
|
6e18caa096 | ||
|
|
6027a123d4 | ||
|
|
f9bc1113b5 |
8
AUTHORS
8
AUTHORS
@@ -54,6 +54,7 @@ Also thanks to:
|
||||
* David Russell (DavidARussell)
|
||||
* DeadlySurprise
|
||||
* Dmitri Suvorov (suvjunmd)
|
||||
* dtluna
|
||||
* Erasmus Schroder (rasco)
|
||||
* Eric Bajumpaa (SteelPhase)
|
||||
* Evgeniy Sergeev (evgeniysergeev)
|
||||
@@ -61,11 +62,10 @@ Also thanks to:
|
||||
* Florian Wiesbauer (FiveAces)
|
||||
* Frank Razenberg (zzattack)
|
||||
* Gareth Needham (Ripley`)
|
||||
* Glen Anderson (GlenLife)
|
||||
* Glen Anderson (glen7)
|
||||
* Glenn Martin Jensen (Baxxster)
|
||||
* Gordon Martin (Happy0)
|
||||
* Guido Lipke (LipkeGu)
|
||||
* Gyula Zimmermann (Graion Dilach)
|
||||
* Hervé Matysiak (Herve-M)
|
||||
* Huw Pascoe
|
||||
* Ian T. Jacobsen (Smilex)
|
||||
@@ -89,10 +89,12 @@ Also thanks to:
|
||||
* Kenny Hoxworth (hoxworth)
|
||||
* Kevin Azzam (ChaoticMind)
|
||||
* Krishnakanth Mallik
|
||||
* Kyle Smith (Smitty)
|
||||
* Kyrre Soerensen (zypres)
|
||||
* Lawrence Wang
|
||||
* Lesueur Benjamin (Valkirie)
|
||||
* Maarten Meuris (Nyerguds)
|
||||
* Manuel Geiger (Ectras)
|
||||
* Mark Olson (markolson)
|
||||
* Markus Hartung (hartmark)
|
||||
* Matija Hustic (matija-hustic)
|
||||
@@ -104,9 +106,11 @@ Also thanks to:
|
||||
* Michael Rätzel
|
||||
* Michael Silber (frühstück)
|
||||
* Michael Sztolcman (s1w_)
|
||||
* Mike Gagné (AngryBirdz)
|
||||
* Muh
|
||||
* Mustafa Alperen Seki (MustaphaTR)
|
||||
* Neil Shivkar (havok13888)
|
||||
* Nikolay Fomin (netnazgul)
|
||||
* Nooze
|
||||
* Nukem
|
||||
* Okunev Yu Dmitry (xaionaro)
|
||||
|
||||
71
INSTALL.md
71
INSTALL.md
@@ -18,52 +18,69 @@ You need to fetch the thirdparty dependencies and place them at the appropriate
|
||||
|
||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||
|
||||
Run the game with `OpenRA.Game.exe Game.Mod=ra` for Red Alert or `OpenRA.Game.exe Game.Mod=cnc` for Tiberian Dawn.
|
||||
Run the game with `launch-game.cmd`. It can be handed arguments that specify the exact mod one wishes to run, for example, run `launch-game.cmd Game.Mod=ra` to launch Red Alert, `launch-game.cmd Game.Mod=cnc` to start Tiberian dawn or `launch-game.cmd Game.Mod=d2k` to launch Dune 2000.
|
||||
|
||||
Linux
|
||||
=====
|
||||
|
||||
Use `make dependencies` to map the native libraries to your system and fetch the remaining CLI dependencies to place them at the appropriate places.
|
||||
|
||||
To compile OpenRA, run `make all` from the command line.
|
||||
To compile OpenRA, run `make all` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||
|
||||
Run with either `launch-game.sh` or `mono --debug OpenRA.Game.exe`.
|
||||
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
|
||||
|
||||
Type `sudo make install` for system wide installation. Run `make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run from the `openra` shortcut.
|
||||
Arch Linux
|
||||
----------
|
||||
|
||||
It is important to note there is an unofficial [`openra-git`](https://aur.archlinux.org/packages/openra-git) package in the Arch User Repository (AUR) of Arch Linux. If manually compiling is the way you wish to go the build and runtime dependencies can be installed with:
|
||||
|
||||
```
|
||||
sudo pacman -S mono openal libgl freetype2 sdl2 lua51 xdg-utils zenity
|
||||
```
|
||||
|
||||
Debian/Ubuntu
|
||||
-------------
|
||||
|
||||
* mono-devel
|
||||
* libfreetype6
|
||||
* libopenal1
|
||||
* liblua5.1-0
|
||||
* libsdl2-2.0-0
|
||||
* xdg-utils
|
||||
* zenity
|
||||
* curl
|
||||
```
|
||||
sudo apt install mono-devel libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0 xdg-utils zenity wget
|
||||
```
|
||||
|
||||
openSUSE
|
||||
--------
|
||||
Fedora
|
||||
------
|
||||
|
||||
```
|
||||
sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity curl
|
||||
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
|
||||
```
|
||||
|
||||
Gentoo
|
||||
------
|
||||
|
||||
* dev-lang/mono
|
||||
* dev-dotnet/libgdiplus
|
||||
* media-libs/freetype:2
|
||||
* media-libs/libsdl2
|
||||
* media-libs/openal
|
||||
* virtual/jpeg
|
||||
* virtual/opengl
|
||||
* dev-lang/lua-5.1.5
|
||||
* x11-misc/xdg-utils
|
||||
* gnome-extra/zenity
|
||||
* net-misc/curl
|
||||
```
|
||||
sudo emerge -av dev-lang/mono dev-dotnet/libgdiplus media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/jpeg virtual/opengl '=dev-lang/lua-5.1.5*' x11-misc/xdg-utils gnome-extra/zenity
|
||||
```
|
||||
|
||||
Mageia
|
||||
------
|
||||
|
||||
```
|
||||
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft xdg-utils zenity
|
||||
```
|
||||
|
||||
openSUSE
|
||||
--------
|
||||
|
||||
```
|
||||
sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity
|
||||
```
|
||||
|
||||
Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
|
||||
----------------------------------------------------
|
||||
|
||||
The EPEL repository is required in order for the following command to run properly.
|
||||
|
||||
```
|
||||
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
|
||||
```
|
||||
|
||||
OSX
|
||||
=====
|
||||
@@ -72,4 +89,4 @@ Use `make dependencies` to map the native libraries to your system.
|
||||
|
||||
To compile OpenRA, run `make` from the command line.
|
||||
|
||||
Run with `mono --debug OpenRA.Game.exe`.
|
||||
Run with `./launch-game.sh`.
|
||||
|
||||
@@ -200,6 +200,24 @@ namespace OpenRA.Activities
|
||||
/// </summary>
|
||||
protected virtual void OnLastRun(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs once on Actor.Dispose() (through OnActorDisposeOuter) and can be used to perform activity clean-up on actor death/disposal,
|
||||
/// for example by force-triggering OnLastRun (which would otherwise be skipped).
|
||||
/// </summary>
|
||||
protected virtual void OnActorDispose(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs once on Actor.Dispose().
|
||||
/// Main purpose is to ensure ChildActivity.OnActorDispose runs as well (which isn't otherwise accessible due to protection level).
|
||||
/// </summary>
|
||||
internal void OnActorDisposeOuter(Actor self)
|
||||
{
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.OnActorDisposeOuter(self);
|
||||
|
||||
OnActorDispose(self);
|
||||
}
|
||||
|
||||
public virtual bool Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
if (!IsInterruptible)
|
||||
|
||||
@@ -42,11 +42,13 @@ namespace OpenRA
|
||||
public Player Owner { get; internal set; }
|
||||
|
||||
public bool IsInWorld { get; internal set; }
|
||||
public bool WillDispose { get; private set; }
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
public Activity CurrentActivity { get; private set; }
|
||||
|
||||
public int Generation;
|
||||
public Actor ReplacedByActor;
|
||||
|
||||
public IEffectiveOwner EffectiveOwner { get; private set; }
|
||||
public IOccupySpace OccupiesSpace { get; private set; }
|
||||
@@ -77,6 +79,8 @@ namespace OpenRA
|
||||
readonly IMouseBounds[] mouseBounds;
|
||||
readonly IVisibilityModifier[] visibilityModifiers;
|
||||
readonly IDefaultVisibility defaultVisibility;
|
||||
readonly INotifyBecomingIdle[] becomingIdles;
|
||||
readonly INotifyIdle[] tickIdles;
|
||||
readonly ITargetablePositions[] targetablePositions;
|
||||
WPos[] staticTargetablePositions;
|
||||
|
||||
@@ -119,14 +123,16 @@ namespace OpenRA
|
||||
mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
|
||||
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
|
||||
defaultVisibility = Trait<IDefaultVisibility>();
|
||||
becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
|
||||
tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
|
||||
Targetables = TraitsImplementing<ITargetable>().ToArray();
|
||||
targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
|
||||
world.AddFrameEndTask(w =>
|
||||
{
|
||||
// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
|
||||
// All actors that can move should have IMove, if not it's pretty safe to assume the actor is immobile and
|
||||
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
|
||||
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
|
||||
if (!Info.HasTraitInfo<IMoveInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
|
||||
if (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
|
||||
staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
|
||||
});
|
||||
|
||||
@@ -139,8 +145,18 @@ namespace OpenRA
|
||||
CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
|
||||
|
||||
if (!wasIdle && IsIdle)
|
||||
foreach (var n in TraitsImplementing<INotifyBecomingIdle>())
|
||||
{
|
||||
foreach (var n in becomingIdles)
|
||||
n.OnBecomingIdle(this);
|
||||
|
||||
// If IsIdle is true, it means the last CurrentActivity.Tick returned null.
|
||||
// If a next activity has been queued via OnBecomingIdle, we need to start running it now,
|
||||
// to avoid an 'empty' null tick where the actor will (visibly, if moving) do nothing.
|
||||
CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
|
||||
}
|
||||
else if (wasIdle)
|
||||
foreach (var tickIdle in tickIdles)
|
||||
tickIdle.TickIdle(this);
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
@@ -265,6 +281,14 @@ namespace OpenRA
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
|
||||
// This should be done before the FrameEndTask to avoid dependency issues.
|
||||
if (CurrentActivity != null)
|
||||
CurrentActivity.RootActivity.OnActorDisposeOuter(this);
|
||||
|
||||
// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
|
||||
WillDispose = true;
|
||||
|
||||
World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (Disposed)
|
||||
@@ -312,6 +336,8 @@ namespace OpenRA
|
||||
foreach (var t in TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
World.Selection.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
if (wasInWorld)
|
||||
World.Add(this);
|
||||
}
|
||||
|
||||
@@ -18,11 +18,28 @@ namespace OpenRA
|
||||
{
|
||||
public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
|
||||
{
|
||||
public readonly int X, Y;
|
||||
public readonly byte Layer;
|
||||
// Coordinates are packed in a 32 bit signed int
|
||||
// X and Y are 12 bits (signed): -2048...2047
|
||||
// Layer is an unsigned byte
|
||||
// Packing is XXXX XXXX XXXX YYYY YYYY YYYY LLLL LLLL
|
||||
public readonly int Bits;
|
||||
|
||||
// X is padded to MSB, so bit shift does the correct sign extension
|
||||
public int X { get { return Bits >> 20; } }
|
||||
|
||||
// Align Y with a short, cast, then shift the rest of the way
|
||||
// The signed short bit shift does the correct sign extension
|
||||
public int Y { get { return ((short)(Bits >> 4)) >> 4; } }
|
||||
|
||||
public byte Layer { get { return (byte)Bits; } }
|
||||
|
||||
public CPos(int bits) { Bits = bits; }
|
||||
public CPos(int x, int y) : this(x, y, 0) { }
|
||||
public CPos(int x, int y, byte layer)
|
||||
{
|
||||
Bits = (x & 0xFFF) << 20 | (y & 0xFFF) << 8 | layer;
|
||||
}
|
||||
|
||||
public CPos(int x, int y) { X = x; Y = y; Layer = 0; }
|
||||
public CPos(int x, int y, byte layer) { X = x; Y = y; Layer = layer; }
|
||||
public static readonly CPos Zero = new CPos(0, 0, 0);
|
||||
|
||||
public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); }
|
||||
@@ -32,12 +49,12 @@ namespace OpenRA
|
||||
public static CPos operator -(CPos a, CVec b) { return new CPos(a.X - b.X, a.Y - b.Y, a.Layer); }
|
||||
public static CVec operator -(CPos a, CPos b) { return new CVec(a.X - b.X, a.Y - b.Y); }
|
||||
|
||||
public static bool operator ==(CPos me, CPos other) { return me.X == other.X && me.Y == other.Y && me.Layer == other.Layer; }
|
||||
public static bool operator ==(CPos me, CPos other) { return me.Bits == other.Bits; }
|
||||
public static bool operator !=(CPos me, CPos other) { return !(me == other); }
|
||||
|
||||
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Layer.GetHashCode(); }
|
||||
public override int GetHashCode() { return Bits.GetHashCode(); }
|
||||
|
||||
public bool Equals(CPos other) { return X == other.X && Y == other.Y && Layer == other.Layer; }
|
||||
public bool Equals(CPos other) { return Bits == other.Bits; }
|
||||
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
|
||||
|
||||
public override string ToString() { return X + "," + Y; }
|
||||
|
||||
@@ -123,14 +123,14 @@ namespace OpenRA
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER
|
||||
// Modulus is padded with a zero to avoid issues with the sign bit
|
||||
writer.Write((byte)0x02);
|
||||
writer.Write((byte)0x02);
|
||||
WriteTLVLength(writer, parameters.Modulus.Length + 1);
|
||||
writer.Write((byte)0);
|
||||
writer.Write(parameters.Modulus);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER
|
||||
// Exponent is padded with a zero to avoid issues with the sign bit
|
||||
writer.Write((byte)0x02);
|
||||
writer.Write((byte)0x02);
|
||||
WriteTLVLength(writer, parameters.Exponent.Length + 1);
|
||||
writer.Write((byte)0);
|
||||
writer.Write(parameters.Exponent);
|
||||
@@ -180,7 +180,7 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(data), false));
|
||||
}
|
||||
@@ -203,7 +203,7 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
using (var csp = SHA1.Create())
|
||||
return Convert.ToBase64String(rsa.SignHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1")));
|
||||
@@ -227,7 +227,7 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
using (var csp = SHA1.Create())
|
||||
return rsa.VerifyHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(signature));
|
||||
|
||||
@@ -43,8 +43,19 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
void EnableTLS12OnWindows()
|
||||
{
|
||||
// Enable TLS 1.2 on Windows: .NET 4.7 on Windows 10 only supports obsolete protocols by default
|
||||
// SecurityProtocolType.Tls12 is not defined in the .NET 4.5 reference dlls used by mono,
|
||||
// so we must use the enum's constant value directly
|
||||
if (Platform.CurrentPlatform == PlatformType.Windows)
|
||||
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
|
||||
}
|
||||
|
||||
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
|
||||
{
|
||||
EnableTLS12OnWindows();
|
||||
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
@@ -56,6 +67,8 @@ namespace OpenRA
|
||||
|
||||
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
|
||||
{
|
||||
EnableTLS12OnWindows();
|
||||
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -488,6 +488,11 @@ namespace OpenRA
|
||||
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static byte ParseByte(string s)
|
||||
{
|
||||
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static bool TryParseIntegerInvariant(string s, out int i)
|
||||
{
|
||||
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class PngLoader
|
||||
{
|
||||
public static Bitmap Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return Load(s);
|
||||
}
|
||||
|
||||
public static Bitmap Load(Stream s)
|
||||
{
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
var signature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
|
||||
foreach (var b in signature)
|
||||
if (br.ReadByte() != b)
|
||||
throw new InvalidDataException("PNG Signature is bogus");
|
||||
|
||||
Bitmap bitmap = null;
|
||||
Color[] palette = null;
|
||||
var data = new List<byte>();
|
||||
|
||||
try
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(br.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(br.ReadBytes(4));
|
||||
var content = br.ReadBytes(length);
|
||||
/*var crc = */br.ReadInt32();
|
||||
|
||||
if (bitmap == null && type != "IHDR")
|
||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||
|
||||
using (var ms = new MemoryStream(content))
|
||||
using (var cr = new BinaryReader(ms))
|
||||
switch (type)
|
||||
{
|
||||
case "IHDR":
|
||||
{
|
||||
if (bitmap != null)
|
||||
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
||||
|
||||
var width = IPAddress.NetworkToHostOrder(cr.ReadInt32());
|
||||
var height = IPAddress.NetworkToHostOrder(cr.ReadInt32());
|
||||
var bitDepth = cr.ReadByte();
|
||||
var colorType = (PngColorType)cr.ReadByte();
|
||||
var compression = cr.ReadByte();
|
||||
/*var filter = */cr.ReadByte();
|
||||
var interlace = cr.ReadByte();
|
||||
|
||||
if (compression != 0) throw new InvalidDataException("Compression method not supported");
|
||||
if (interlace != 0) throw new InvalidDataException("Interlacing not supported");
|
||||
|
||||
bitmap = new Bitmap(width, height, MakePixelFormat(bitDepth, colorType));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "PLTE":
|
||||
{
|
||||
palette = new Color[256];
|
||||
for (var i = 0; i < length / 3; i++)
|
||||
{
|
||||
var r = cr.ReadByte(); var g = cr.ReadByte(); var b = cr.ReadByte();
|
||||
palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "tRNS":
|
||||
{
|
||||
if (palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
palette[i] = Color.FromArgb(cr.ReadByte(), palette[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "IDAT":
|
||||
{
|
||||
data.AddRange(content);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "IEND":
|
||||
{
|
||||
var bits = bitmap.LockBits(bitmap.Bounds(),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
|
||||
|
||||
using (var ns = new MemoryStream(data.ToArray()))
|
||||
{
|
||||
// 'zlib' flags bytes; confuses the DeflateStream.
|
||||
/*var flags = (byte)*/ns.ReadByte();
|
||||
/*var moreFlags = (byte)*/ns.ReadByte();
|
||||
|
||||
using (var ds = new DeflateStream(ns, CompressionMode.Decompress))
|
||||
using (var dr = new BinaryReader(ds))
|
||||
{
|
||||
var prevLine = new byte[bitmap.Width]; // all zero
|
||||
for (var y = 0; y < bitmap.Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)dr.ReadByte();
|
||||
var line = dr.ReadBytes(bitmap.Width);
|
||||
|
||||
for (var i = 0; i < bitmap.Width; i++)
|
||||
line[i] = i > 0
|
||||
? UnapplyFilter(filter, line[i], line[i - 1], prevLine[i], prevLine[i - 1])
|
||||
: UnapplyFilter(filter, line[i], 0, prevLine[i], 0);
|
||||
|
||||
Marshal.Copy(line, 0, new IntPtr(bits.Scan0.ToInt64() + y * bits.Stride), line.Length);
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bits);
|
||||
|
||||
if (palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
using (var temp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
|
||||
{
|
||||
var cp = temp.Palette;
|
||||
for (var i = 0; i < 256; i++)
|
||||
cp.Entries[i] = palette[i]; // finalize the palette.
|
||||
bitmap.Palette = cp;
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (bitmap != null)
|
||||
bitmap.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static PixelFormat MakePixelFormat(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
return PixelFormat.Format8bppIndexed;
|
||||
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,6 +258,10 @@ namespace OpenRA
|
||||
|
||||
static void Initialize(Arguments args)
|
||||
{
|
||||
var supportDirArg = args.GetValue("Engine.SupportDir", null);
|
||||
if (supportDirArg != null)
|
||||
Platform.OverrideSupportDir(supportDirArg);
|
||||
|
||||
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
|
||||
|
||||
// Load the engine version as early as possible so it can be written to exception logs
|
||||
@@ -566,7 +570,7 @@ namespace OpenRA
|
||||
var integralTickTimestep = (uiTickDelta / Timestep) * Timestep;
|
||||
Ui.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : Timestep;
|
||||
|
||||
Sync.CheckSyncUnchanged(world, Ui.Tick);
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, Ui.Tick);
|
||||
Cursor.Tick();
|
||||
}
|
||||
|
||||
@@ -584,7 +588,7 @@ namespace OpenRA
|
||||
orderManager.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : worldTimestep;
|
||||
|
||||
Sound.Tick();
|
||||
Sync.CheckSyncUnchanged(world, orderManager.TickImmediate);
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, orderManager.TickImmediate);
|
||||
|
||||
if (world == null)
|
||||
return;
|
||||
@@ -603,7 +607,7 @@ namespace OpenRA
|
||||
if (isNetTick)
|
||||
orderManager.Tick();
|
||||
|
||||
Sync.CheckSyncUnchanged(world, () =>
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () =>
|
||||
{
|
||||
world.OrderGenerator.Tick(world);
|
||||
world.Selection.Tick(world);
|
||||
@@ -618,7 +622,7 @@ namespace OpenRA
|
||||
|
||||
// Wait until we have done our first world Tick before TickRendering
|
||||
if (orderManager.LocalFrameNumber > 0)
|
||||
Sync.CheckSyncUnchanged(world, () => world.TickRender(worldRenderer));
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace OpenRA
|
||||
|
||||
public string MapUid;
|
||||
public string MapTitle;
|
||||
public int FinalGameTick;
|
||||
|
||||
/// <summary>Game start timestamp (when the recoding started).</summary>
|
||||
public DateTime StartTimeUtc;
|
||||
|
||||
@@ -147,20 +147,6 @@ namespace OpenRA
|
||||
.Select(t => t.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
public IEnumerable<Pair<string, Type>> GetInitKeys()
|
||||
{
|
||||
var inits = traits.WithInterface<ITraitInfo>().SelectMany(
|
||||
t => t.GetType().GetInterfaces()
|
||||
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(UsesInit<>))
|
||||
.Select(i => i.GetGenericArguments()[0])).ToList();
|
||||
|
||||
inits.Add(typeof(OwnerInit)); /* not exposed by a trait; this is used by the Actor itself */
|
||||
|
||||
return inits.Select(
|
||||
i => Pair.New(
|
||||
i.Name.Replace("Init", ""), i));
|
||||
}
|
||||
|
||||
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
||||
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
||||
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -20,6 +19,7 @@ namespace OpenRA.GameRules
|
||||
public readonly string Filename;
|
||||
public readonly string Title;
|
||||
public readonly bool Hidden;
|
||||
public readonly float VolumeModifier = 1f;
|
||||
|
||||
public int Length { get; private set; } // seconds
|
||||
public bool Exists { get; private set; }
|
||||
@@ -32,6 +32,9 @@ namespace OpenRA.GameRules
|
||||
if (nd.ContainsKey("Hidden"))
|
||||
bool.TryParse(nd["Hidden"].Value, out Hidden);
|
||||
|
||||
if (nd.ContainsKey("VolumeModifier"))
|
||||
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", nd["VolumeModifier"].Value);
|
||||
|
||||
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
|
||||
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
|
||||
}
|
||||
|
||||
@@ -33,19 +33,48 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
FieldLoader.Load(this, y);
|
||||
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(a.Value)));
|
||||
NotificationsPools = Exts.Lazy(() => Notifications.ToDictionary(a => a.Key, a => new SoundPool(a.Value)));
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(0, 1f, a.Value)));
|
||||
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
|
||||
}
|
||||
|
||||
Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||
{
|
||||
var ret = new Dictionary<string, SoundPool>();
|
||||
var classifiction = y.Nodes.First(x => x.Key == key);
|
||||
foreach (var t in classifiction.Value.Nodes)
|
||||
{
|
||||
var rateLimit = 0;
|
||||
var rateLimitNode = t.Value.Nodes.FirstOrDefault(x => x.Key == "RateLimit");
|
||||
if (rateLimitNode != null)
|
||||
rateLimit = FieldLoader.GetValue<int>(rateLimitNode.Key, rateLimitNode.Value.Value);
|
||||
|
||||
var volumeModifier = 1f;
|
||||
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == "VolumeModifier");
|
||||
if (volumeModifierNode != null)
|
||||
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
||||
|
||||
var names = FieldLoader.GetValue<string[]>(t.Key, t.Value.Value);
|
||||
var sp = new SoundPool(rateLimit, volumeModifier, names);
|
||||
ret.Add(t.Key, sp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public class SoundPool
|
||||
{
|
||||
public readonly float VolumeModifier;
|
||||
readonly string[] clips;
|
||||
readonly int rateLimit;
|
||||
readonly List<string> liveclips = new List<string>();
|
||||
long lastPlayed = 0;
|
||||
|
||||
public SoundPool(params string[] clips)
|
||||
public SoundPool(int rateLimit, float volumeModifier, params string[] clips)
|
||||
{
|
||||
VolumeModifier = volumeModifier;
|
||||
this.clips = clips;
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
public string GetNext()
|
||||
@@ -53,8 +82,19 @@ namespace OpenRA.GameRules
|
||||
if (liveclips.Count == 0)
|
||||
liveclips.AddRange(clips);
|
||||
|
||||
// Avoid crashing if there's no clips at all
|
||||
if (liveclips.Count == 0)
|
||||
return null; /* avoid crashing if there's no clips at all */
|
||||
return null;
|
||||
|
||||
// Perform rate limiting if necessary
|
||||
if (rateLimit != 0)
|
||||
{
|
||||
var now = Game.RunTime;
|
||||
if (lastPlayed != 0 && now < lastPlayed + rateLimit)
|
||||
return null;
|
||||
|
||||
lastPlayed = now;
|
||||
}
|
||||
|
||||
var i = Game.CosmeticRandom.Next(liveclips.Count);
|
||||
var s = liveclips[i];
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace OpenRA.GameRules
|
||||
public int[] InaccuracyModifiers;
|
||||
public int[] RangeModifiers;
|
||||
public int Facing;
|
||||
public Func<int> CurrentMuzzleFacing;
|
||||
public WPos Source;
|
||||
public Func<WPos> CurrentSource;
|
||||
public Actor SourceActor;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Support;
|
||||
@@ -50,7 +49,7 @@ namespace OpenRA.Graphics
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
public int CurrentFrame { get { return backwards ? CurrentSequence.Start + CurrentSequence.Length - frame - 1 : frame; } }
|
||||
public int CurrentFrame { get { return backwards ? CurrentSequence.Length - frame - 1 : frame; } }
|
||||
public Sprite Image { get { return CurrentSequence.GetSprite(CurrentFrame, facingFunc()); } }
|
||||
|
||||
public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -26,21 +27,18 @@ namespace OpenRA.Graphics
|
||||
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
|
||||
s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
|
||||
var shadowIndex = new int[] { };
|
||||
|
||||
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
|
||||
if (nodesDict.ContainsKey("ShadowIndex"))
|
||||
{
|
||||
Array.Resize(ref shadowIndex, shadowIndex.Length + 1);
|
||||
Exts.TryParseIntegerInvariant(nodesDict["ShadowIndex"].Value,
|
||||
out shadowIndex[shadowIndex.Length - 1]);
|
||||
}
|
||||
|
||||
var palettes = new Dictionary<string, ImmutablePalette>();
|
||||
foreach (var p in nodesDict["Palettes"].Nodes)
|
||||
palettes.Add(p.Key, new ImmutablePalette(fileSystem.Open(p.Value.Value), shadowIndex));
|
||||
// Overwrite previous definitions if there are duplicates
|
||||
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
||||
foreach (var p in modData.DefaultRules.Actors["world"].TraitInfos<IProvidesCursorPaletteInfo>())
|
||||
if (p.Palette != null)
|
||||
pals[p.Palette] = p;
|
||||
|
||||
Palettes = palettes.AsReadOnly();
|
||||
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
|
||||
.Distinct()
|
||||
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem))
|
||||
.AsReadOnly();
|
||||
|
||||
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
||||
var cursors = new Dictionary<string, CursorSequence>();
|
||||
|
||||
@@ -45,11 +45,6 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < Size; i++)
|
||||
pal.Entries[i] = palette.GetColor(i);
|
||||
|
||||
// hack around a mono bug -- the palette flags get set wrong.
|
||||
if (Platform.CurrentPlatform != PlatformType.Windows)
|
||||
typeof(ColorPalette).GetField("flags",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pal, 1);
|
||||
|
||||
return pal;
|
||||
}
|
||||
|
||||
@@ -117,6 +112,12 @@ namespace OpenRA.Graphics
|
||||
var r = (byte)(reader.ReadByte() << 2);
|
||||
var g = (byte)(reader.ReadByte() << 2);
|
||||
var b = (byte)(reader.ReadByte() << 2);
|
||||
|
||||
// Replicate high bits into the (currently zero) low bits.
|
||||
r |= (byte)(r >> 6);
|
||||
g |= (byte)(g >> 6);
|
||||
b |= (byte)(b >> 6);
|
||||
|
||||
colors[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ISpriteLoader
|
||||
{
|
||||
bool TryParseSprite(Stream s, out ISpriteFrame[] frames);
|
||||
bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata);
|
||||
}
|
||||
|
||||
public interface ISpriteFrame
|
||||
@@ -50,6 +50,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
readonly Dictionary<string, List<Sprite[]>> sprites = new Dictionary<string, List<Sprite[]>>();
|
||||
readonly Dictionary<string, ISpriteFrame[]> unloadedFrames = new Dictionary<string, ISpriteFrame[]>();
|
||||
readonly Dictionary<string, TypeDictionary> metadata = new Dictionary<string, TypeDictionary>();
|
||||
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, SheetBuilder sheetBuilder)
|
||||
{
|
||||
@@ -80,8 +81,10 @@ namespace OpenRA.Graphics
|
||||
// the loaded cache (initially empty)
|
||||
if (sprite == null)
|
||||
{
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders);
|
||||
TypeDictionary fileMetadata = null;
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
unloadedFrames[filename] = unloaded;
|
||||
metadata[filename] = fileMetadata;
|
||||
|
||||
sprite = new Sprite[unloaded.Length];
|
||||
allSprites.Add(sprite);
|
||||
@@ -111,6 +114,22 @@ namespace OpenRA.Graphics
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a TypeDictionary containing any metadata defined by the frame
|
||||
/// or null if the frame does not define metadata.
|
||||
/// </summary>
|
||||
public TypeDictionary FrameMetadata(string filename)
|
||||
{
|
||||
TypeDictionary fileMetadata;
|
||||
if (!metadata.TryGetValue(filename, out fileMetadata))
|
||||
{
|
||||
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
metadata[filename] = fileMetadata;
|
||||
}
|
||||
|
||||
return fileMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
public class FrameCache
|
||||
@@ -119,7 +138,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public FrameCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
{
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders));
|
||||
TypeDictionary metadata;
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out metadata));
|
||||
}
|
||||
|
||||
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
|
||||
@@ -127,11 +147,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
public static class FrameLoader
|
||||
{
|
||||
public static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders)
|
||||
public static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders, out TypeDictionary metadata)
|
||||
{
|
||||
using (var stream = fileSystem.Open(filename))
|
||||
{
|
||||
var spriteFrames = GetFrames(stream, loaders);
|
||||
var spriteFrames = GetFrames(stream, loaders, out metadata);
|
||||
if (spriteFrames == null)
|
||||
throw new InvalidDataException(filename + " is not a valid sprite file!");
|
||||
|
||||
@@ -139,11 +159,13 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders)
|
||||
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, out TypeDictionary metadata)
|
||||
{
|
||||
ISpriteFrame[] frames;
|
||||
metadata = null;
|
||||
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSprite(stream, out frames))
|
||||
if (loader.TryParseSprite(stream, out frames, out metadata))
|
||||
return frames;
|
||||
|
||||
return null;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace OpenRA.Graphics
|
||||
readonly IShader shader;
|
||||
|
||||
readonly Vertex[] vertices;
|
||||
readonly Sheet[] sheets = new Sheet[8];
|
||||
readonly Sheet[] sheets = new Sheet[7];
|
||||
|
||||
BlendMode currentBlend = BlendMode.Alpha;
|
||||
int nv = 0;
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace OpenRA.Graphics
|
||||
readonly HashSet<Actor> onScreenActors = new HashSet<Actor>();
|
||||
readonly HardwarePalette palette = new HardwarePalette();
|
||||
readonly Dictionary<string, PaletteReference> palettes = new Dictionary<string, PaletteReference>();
|
||||
readonly TerrainRenderer terrainRenderer;
|
||||
readonly IRenderTerrain terrainRenderer;
|
||||
readonly Lazy<DebugVisualizations> debugVis;
|
||||
readonly Func<string, PaletteReference> createPaletteReference;
|
||||
readonly bool enableDepthBuffer;
|
||||
@@ -62,7 +62,7 @@ namespace OpenRA.Graphics
|
||||
palette.Initialize();
|
||||
|
||||
Theater = new Theater(world.Map.Rules.TileSet);
|
||||
terrainRenderer = new TerrainRenderer(world, this);
|
||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||
|
||||
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
||||
}
|
||||
@@ -181,7 +181,9 @@ namespace OpenRA.Graphics
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.Context.EnableDepthBuffer();
|
||||
|
||||
terrainRenderer.Draw(this, Viewport);
|
||||
if (terrainRenderer != null)
|
||||
terrainRenderer.RenderTerrain(this, Viewport);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
|
||||
for (var i = 0; i < renderables.Count; i++)
|
||||
@@ -330,7 +332,6 @@ namespace OpenRA.Graphics
|
||||
|
||||
palette.Dispose();
|
||||
Theater.Dispose();
|
||||
terrainRenderer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,17 +37,17 @@ namespace OpenRA
|
||||
|
||||
public void OnKeyInput(KeyInput input)
|
||||
{
|
||||
Sync.CheckSyncUnchanged(world, () => Ui.HandleKeyPress(input));
|
||||
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleKeyPress(input));
|
||||
}
|
||||
|
||||
public void OnTextInput(string text)
|
||||
{
|
||||
Sync.CheckSyncUnchanged(world, () => Ui.HandleTextInput(text));
|
||||
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleTextInput(text));
|
||||
}
|
||||
|
||||
public void OnMouseInput(MouseInput input)
|
||||
{
|
||||
Sync.CheckSyncUnchanged(world, () => Ui.HandleInput(input));
|
||||
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleInput(input));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
@@ -16,7 +16,6 @@ using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Network;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -168,7 +167,11 @@ namespace OpenRA
|
||||
|
||||
public string Sign(params string[] data)
|
||||
{
|
||||
if (State != LinkState.Linked)
|
||||
// If we don't have any keys, or we know for sure that they haven't been linked to the forum
|
||||
// then we can't do much here. If we have keys but don't yet know if they have been linked to the
|
||||
// forum (LinkState.CheckingLink or ConnectionFailed) then we sign to avoid blocking the main thread
|
||||
// but accept that - if the cert is invalid - the server will reject the result.
|
||||
if (State <= LinkState.Unlinked)
|
||||
return null;
|
||||
|
||||
return CryptoUtil.Sign(parameters, data.Where(x => !string.IsNullOrEmpty(x)).JoinWith(string.Empty));
|
||||
@@ -176,7 +179,7 @@ namespace OpenRA
|
||||
|
||||
public string DecryptString(string data)
|
||||
{
|
||||
if (State != LinkState.Linked)
|
||||
if (State <= LinkState.Unlinked)
|
||||
return null;
|
||||
|
||||
return CryptoUtil.DecryptString(parameters, data);
|
||||
|
||||
@@ -18,7 +18,6 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
@@ -786,7 +785,7 @@ namespace OpenRA
|
||||
public WPos CenterOfSubCell(CPos cell, SubCell subCell)
|
||||
{
|
||||
var index = (int)subCell;
|
||||
if (index >= 0 && index <= Grid.SubCellOffsets.Length)
|
||||
if (index >= 0 && index < Grid.SubCellOffsets.Length)
|
||||
return CenterOfCell(cell) + Grid.SubCellOffsets[index];
|
||||
return CenterOfCell(cell);
|
||||
}
|
||||
|
||||
@@ -205,6 +205,10 @@ namespace OpenRA
|
||||
var yaml = MiniYaml.FromString(data);
|
||||
foreach (var kv in yaml)
|
||||
maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
|
||||
|
||||
foreach (var map in maps)
|
||||
if (map.Value.Status != MapStatus.DownloadAvailable)
|
||||
map.Value.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -332,8 +336,8 @@ namespace OpenRA
|
||||
if (string.IsNullOrEmpty(initialUid) || previews[initialUid].Status != MapStatus.Available)
|
||||
{
|
||||
var selected = previews.Values.Where(IsSuitableInitialMap).RandomOrDefault(random) ??
|
||||
previews.Values.First(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Lobby));
|
||||
return selected.Uid;
|
||||
previews.Values.FirstOrDefault(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Lobby));
|
||||
return selected == null ? string.Empty : selected.Uid;
|
||||
}
|
||||
|
||||
return initialUid;
|
||||
|
||||
@@ -168,7 +168,11 @@ namespace OpenRA
|
||||
if (subCell == SubCell.Invalid || subCell == SubCell.Any)
|
||||
return WVec.Zero;
|
||||
|
||||
return SubCellOffsets[(int)subCell];
|
||||
var index = (int)subCell;
|
||||
if (index >= 0 && index < SubCellOffsets.Length)
|
||||
return SubCellOffsets[index];
|
||||
|
||||
return WVec.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
@@ -56,7 +55,7 @@ namespace OpenRA
|
||||
public readonly string[] categories;
|
||||
public readonly int players;
|
||||
public readonly Rectangle bounds;
|
||||
public readonly int[] spawnpoints = { };
|
||||
public readonly short[] spawnpoints = { };
|
||||
public readonly MapGridType map_grid_type;
|
||||
public readonly string minimap;
|
||||
public readonly bool downloading;
|
||||
|
||||
@@ -30,36 +30,10 @@ namespace OpenRA
|
||||
|
||||
public readonly float ZOffset = 0.0f;
|
||||
public readonly float ZRamp = 1.0f;
|
||||
|
||||
public MiniYaml Save(TileSet tileSet)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
if (Height != 0)
|
||||
root.Add(FieldSaver.SaveField(this, "Height"));
|
||||
|
||||
if (RampType != 0)
|
||||
root.Add(FieldSaver.SaveField(this, "RampType"));
|
||||
|
||||
if (LeftColor != tileSet.TerrainInfo[TerrainType].Color)
|
||||
root.Add(FieldSaver.SaveField(this, "LeftColor"));
|
||||
|
||||
if (RightColor != tileSet.TerrainInfo[TerrainType].Color)
|
||||
root.Add(FieldSaver.SaveField(this, "RightColor"));
|
||||
|
||||
if (ZOffset != 0.0f)
|
||||
root.Add(FieldSaver.SaveField(this, "ZOffset"));
|
||||
|
||||
if (ZRamp != 1.0f)
|
||||
root.Add(FieldSaver.SaveField(this, "ZRamp"));
|
||||
|
||||
return new MiniYaml(tileSet.TerrainInfo[TerrainType].Type, root);
|
||||
}
|
||||
}
|
||||
|
||||
public class TerrainTypeInfo
|
||||
{
|
||||
static readonly TerrainTypeInfo Default = new TerrainTypeInfo();
|
||||
|
||||
public readonly string Type;
|
||||
public readonly BitSet<TargetableType> TargetTypes;
|
||||
public readonly HashSet<string> AcceptsSmudgeType = new HashSet<string>();
|
||||
@@ -67,18 +41,11 @@ namespace OpenRA
|
||||
public readonly bool RestrictPlayerColor = false;
|
||||
public readonly string CustomCursor;
|
||||
|
||||
// Private default ctor for serialization comparison
|
||||
TerrainTypeInfo() { }
|
||||
|
||||
public TerrainTypeInfo(MiniYaml my) { FieldLoader.Load(this, my); }
|
||||
|
||||
public MiniYaml Save() { return FieldSaver.SaveDifferences(this, Default); }
|
||||
}
|
||||
|
||||
public class TerrainTemplateInfo
|
||||
{
|
||||
static readonly TerrainTemplateInfo Default = new TerrainTemplateInfo(0, new string[] { null }, int2.Zero, null);
|
||||
|
||||
public readonly ushort Id;
|
||||
public readonly string[] Images;
|
||||
public readonly int[] Frames;
|
||||
@@ -160,21 +127,6 @@ namespace OpenRA
|
||||
{
|
||||
get { return tileInfo.Length; }
|
||||
}
|
||||
|
||||
public MiniYaml Save(TileSet tileSet)
|
||||
{
|
||||
var root = FieldSaver.SaveDifferences(this, Default);
|
||||
|
||||
var tileYaml = tileInfo
|
||||
.Select((ti, i) => Pair.New(i.ToString(), ti))
|
||||
.Where(t => t.Second != null)
|
||||
.Select(t => new MiniYamlNode(t.First, t.Second.Save(tileSet)))
|
||||
.ToList();
|
||||
|
||||
root.Nodes.Add(new MiniYamlNode("Tiles", null, tileYaml));
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
public class TileSet
|
||||
@@ -197,9 +149,6 @@ namespace OpenRA
|
||||
readonly Dictionary<string, byte> terrainIndexByType = new Dictionary<string, byte>();
|
||||
readonly byte defaultWalkableTerrainIndex;
|
||||
|
||||
// Private default ctor for serialization comparison
|
||||
TileSet() { }
|
||||
|
||||
public TileSet(IReadOnlyFileSystem fileSystem, string filepath)
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(fileSystem.Open(filepath), filepath)
|
||||
@@ -298,18 +247,5 @@ namespace OpenRA
|
||||
|
||||
return tpl.Contains(r.Index) ? tpl[r.Index] : null;
|
||||
}
|
||||
|
||||
public void Save(string filepath)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
root.Add(new MiniYamlNode("General", FieldSaver.SaveDifferences(this, new TileSet())));
|
||||
|
||||
root.Add(new MiniYamlNode("Terrain", null,
|
||||
TerrainInfo.Select(t => new MiniYamlNode("TerrainType@{0}".F(t.Type), t.Save())).ToList()));
|
||||
|
||||
root.Add(new MiniYamlNode("Templates", null,
|
||||
Templates.Select(t => new MiniYamlNode("Template@{0}".F(t.Value.Id), t.Value.Save(this))).ToList()));
|
||||
root.WriteToFile(filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace OpenRA.Network
|
||||
public class GameClient
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Fingerprint;
|
||||
public readonly HSLColor Color;
|
||||
public readonly string Faction;
|
||||
public readonly int Team;
|
||||
@@ -33,6 +34,7 @@ namespace OpenRA.Network
|
||||
public GameClient(Session.Client c)
|
||||
{
|
||||
Name = c.Name;
|
||||
Fingerprint = c.Fingerprint;
|
||||
Color = c.Color;
|
||||
Faction = c.Faction;
|
||||
Team = c.Team;
|
||||
@@ -54,7 +56,7 @@ namespace OpenRA.Network
|
||||
"Mod", "Version", "ModTitle", "ModWebsite", "ModIcon32",
|
||||
|
||||
// Current server state
|
||||
"Map", "State", "MaxPlayers", "Protected"
|
||||
"Map", "State", "MaxPlayers", "Protected", "Authentication"
|
||||
};
|
||||
|
||||
public const int ProtocolVersion = 2;
|
||||
@@ -98,6 +100,9 @@ namespace OpenRA.Network
|
||||
/// <summary>Password protected</summary>
|
||||
public readonly bool Protected = false;
|
||||
|
||||
/// <summary>Players must be authenticated with the OpenRA forum</summary>
|
||||
public readonly bool Authentication = false;
|
||||
|
||||
/// <summary>UTC datetime string when the game changed to the Playing state</summary>
|
||||
public readonly string Started = null;
|
||||
|
||||
@@ -222,6 +227,7 @@ namespace OpenRA.Network
|
||||
ModWebsite = manifest.Metadata.Website;
|
||||
ModIcon32 = manifest.Metadata.WebIcon32;
|
||||
Protected = !string.IsNullOrEmpty(server.Settings.Password);
|
||||
Authentication = server.Settings.RequireAuthentication || server.Settings.ProfileIDWhitelist.Any();
|
||||
Clients = server.LobbyInfo.Clients.Select(c => new GameClient(c)).ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -76,10 +75,12 @@ namespace OpenRA
|
||||
|
||||
public static Order Deserialize(World world, BinaryReader r)
|
||||
{
|
||||
var magic = r.ReadByte();
|
||||
switch (magic)
|
||||
try
|
||||
{
|
||||
case 0xFF:
|
||||
var magic = r.ReadByte();
|
||||
switch (magic)
|
||||
{
|
||||
case 0xFF:
|
||||
{
|
||||
var order = r.ReadString();
|
||||
var subjectId = r.ReadUInt32();
|
||||
@@ -111,11 +112,10 @@ namespace OpenRA
|
||||
if (world == null || !TryGetActorFromUInt(world, playerActorID, out playerActor))
|
||||
break;
|
||||
|
||||
var frozenLayer = playerActor.TraitOrDefault<FrozenActorLayer>();
|
||||
if (frozenLayer == null)
|
||||
if (playerActor.Owner.FrozenActorLayer == null)
|
||||
break;
|
||||
|
||||
var frozen = frozenLayer.FromID(frozenActorID);
|
||||
var frozen = playerActor.Owner.FrozenActorLayer.FromID(frozenActorID);
|
||||
if (frozen != null)
|
||||
target = Target.FromFrozenActor(frozen);
|
||||
|
||||
@@ -126,8 +126,8 @@ namespace OpenRA
|
||||
{
|
||||
if (flags.HasField(OrderFields.TargetIsCell))
|
||||
{
|
||||
var cell = new CPos(r.ReadInt32(), r.ReadInt32(), r.ReadByte());
|
||||
var subCell = (SubCell)r.ReadInt32();
|
||||
var cell = new CPos(r.ReadInt32());
|
||||
var subCell = (SubCell)r.ReadByte();
|
||||
if (world != null)
|
||||
target = Target.FromCell(world, cell, subCell);
|
||||
}
|
||||
@@ -144,7 +144,7 @@ namespace OpenRA
|
||||
|
||||
var targetString = flags.HasField(OrderFields.TargetString) ? r.ReadString() : null;
|
||||
var queued = flags.HasField(OrderFields.Queued);
|
||||
var extraLocation = flags.HasField(OrderFields.ExtraLocation) ? new CPos(r.ReadInt32(), r.ReadInt32(), r.ReadByte()) : CPos.Zero;
|
||||
var extraLocation = flags.HasField(OrderFields.ExtraLocation) ? new CPos(r.ReadInt32()) : CPos.Zero;
|
||||
var extraData = flags.HasField(OrderFields.ExtraData) ? r.ReadUInt32() : 0;
|
||||
|
||||
if (world == null)
|
||||
@@ -156,7 +156,7 @@ namespace OpenRA
|
||||
return new Order(order, subject, target, targetString, queued, extraLocation, extraData);
|
||||
}
|
||||
|
||||
case 0xfe:
|
||||
case 0xfe:
|
||||
{
|
||||
var name = r.ReadString();
|
||||
var data = r.ReadString();
|
||||
@@ -164,11 +164,23 @@ namespace OpenRA
|
||||
return new Order(name, null, false) { IsImmediate = true, TargetString = data };
|
||||
}
|
||||
|
||||
default:
|
||||
default:
|
||||
{
|
||||
Log.Write("debug", "Received unknown order with magic {0}", magic);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Caught exception while processing order");
|
||||
Log.Write("debug", e.ToString());
|
||||
|
||||
// HACK: this can hopefully go away in the future
|
||||
Game.Debug("Ignoring malformed order that would have crashed the game");
|
||||
Game.Debug("Please file a bug report and include the replay from this match");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,9 +229,9 @@ namespace OpenRA
|
||||
return new Order("Command", null, false) { IsImmediate = true, TargetString = text };
|
||||
}
|
||||
|
||||
public static Order StartProduction(Actor subject, string item, int count)
|
||||
public static Order StartProduction(Actor subject, string item, int count, bool queued = true)
|
||||
{
|
||||
return new Order("StartProduction", subject, false) { ExtraData = (uint)count, TargetString = item };
|
||||
return new Order("StartProduction", subject, queued) { ExtraData = (uint)count, TargetString = item };
|
||||
}
|
||||
|
||||
public static Order PauseProduction(Actor subject, string item, bool pause)
|
||||
@@ -297,7 +309,7 @@ namespace OpenRA
|
||||
if (fields.HasField(OrderFields.TargetIsCell))
|
||||
{
|
||||
w.Write(Target.SerializableCell.Value);
|
||||
w.Write((int)Target.SerializableSubCell);
|
||||
w.Write((byte)Target.SerializableSubCell);
|
||||
}
|
||||
else
|
||||
w.Write(Target.SerializablePos);
|
||||
|
||||
@@ -58,9 +58,7 @@ namespace OpenRA.Network
|
||||
|
||||
public static void Write(this BinaryWriter w, CPos cell)
|
||||
{
|
||||
w.Write(cell.X);
|
||||
w.Write(cell.Y);
|
||||
w.Write(cell.Layer);
|
||||
w.Write(cell.Bits);
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter w, WPos pos)
|
||||
|
||||
@@ -14,6 +14,7 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
@@ -52,6 +53,7 @@ namespace OpenRA.Network
|
||||
public readonly ReadOnlyList<ChatLine> ChatCache;
|
||||
|
||||
bool disposed;
|
||||
bool generateSyncReport = false;
|
||||
|
||||
void OutOfSync(int frame)
|
||||
{
|
||||
@@ -61,7 +63,12 @@ namespace OpenRA.Network
|
||||
|
||||
public void StartGame()
|
||||
{
|
||||
if (GameStarted) return;
|
||||
if (GameStarted)
|
||||
return;
|
||||
|
||||
// Generating sync reports is expensive, so only do it if we have
|
||||
// other players to compare against if a desync did occur
|
||||
generateSyncReport = !(Connection is ReplayConnection) && LobbyInfo.GlobalSettings.EnableSyncReports;
|
||||
|
||||
NetFrameNumber = 1;
|
||||
for (var i = NetFrameNumber; i <= FramesAhead; i++)
|
||||
@@ -180,7 +187,9 @@ namespace OpenRA.Network
|
||||
|
||||
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash()));
|
||||
|
||||
syncReport.UpdateSyncReport();
|
||||
if (generateSyncReport)
|
||||
using (new PerfSample("sync_report"))
|
||||
syncReport.UpdateSyncReport();
|
||||
|
||||
++NetFrameNumber;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace OpenRA.Network
|
||||
public int LocalClientId { get { return 0; } }
|
||||
public ConnectionState ConnectionState { get { return ConnectionState.Connected; } }
|
||||
public readonly int TickCount;
|
||||
public readonly int FinalGameTick;
|
||||
public readonly bool IsValid;
|
||||
public readonly Session LobbyInfo;
|
||||
public readonly string Filename;
|
||||
@@ -40,6 +41,7 @@ namespace OpenRA.Network
|
||||
public ReplayConnection(string replayFilename)
|
||||
{
|
||||
Filename = replayFilename;
|
||||
FinalGameTick = ReplayMetadata.Read(replayFilename).GameInfo.FinalGameTick;
|
||||
|
||||
// Parse replay data into a struct that can be fed to the game in chunks
|
||||
// to avoid issues with all immediate orders being resolved on the first tick.
|
||||
|
||||
@@ -198,9 +198,9 @@ namespace OpenRA.Network
|
||||
public int OrderLatency = 3; // net tick frames (x 120 = ms)
|
||||
public int RandomSeed = 0;
|
||||
public bool AllowSpectators = true;
|
||||
public bool AllowVersionMismatch;
|
||||
public string GameUid;
|
||||
public bool EnableSingleplayer;
|
||||
public bool EnableSyncReports;
|
||||
public bool Dedicated;
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
@@ -9,12 +9,9 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Network
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -38,7 +39,6 @@
|
||||
<OutputPath>..</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<StartArguments>Game.Mod=ra</StartArguments>
|
||||
@@ -53,7 +53,6 @@
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Optimize>true</Optimize>
|
||||
<StartArguments>Game.Mod=ra</StartArguments>
|
||||
<Commandlineparameters>Game.Mod=ra</Commandlineparameters>
|
||||
@@ -135,7 +134,6 @@
|
||||
<Compile Include="Graphics\SpriteFont.cs" />
|
||||
<Compile Include="Graphics\SpriteLoader.cs" />
|
||||
<Compile Include="Graphics\SpriteRenderer.cs" />
|
||||
<Compile Include="Graphics\TerrainRenderer.cs" />
|
||||
<Compile Include="Graphics\Util.cs" />
|
||||
<Compile Include="Graphics\Viewport.cs" />
|
||||
<Compile Include="Graphics\WorldRenderer.cs" />
|
||||
@@ -275,7 +273,6 @@
|
||||
<Compile Include="FieldSaver.cs" />
|
||||
<Compile Include="Manifest.cs" />
|
||||
<Compile Include="Graphics\Vertex.cs" />
|
||||
<Compile Include="FileFormats\PngLoader.cs" />
|
||||
<Compile Include="Primitives\ActionQueue.cs" />
|
||||
<Compile Include="Primitives\BitSet.cs" />
|
||||
<Compile Include="Primitives\Cache.cs" />
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Orders
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -71,9 +70,30 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public static string SupportDir { get { return supportDir.Value; } }
|
||||
static Lazy<string> supportDir = Exts.Lazy(GetSupportDir);
|
||||
static string supportDirOverride;
|
||||
|
||||
/// <summary>
|
||||
/// Specify a custom support directory that already exists on the filesystem.
|
||||
/// MUST be called before Platform.SupportDir is first accessed.
|
||||
/// </summary>
|
||||
public static void OverrideSupportDir(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
throw new DirectoryNotFoundException(path);
|
||||
|
||||
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) &&
|
||||
!path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||
path += Path.DirectorySeparatorChar;
|
||||
|
||||
supportDirOverride = path;
|
||||
}
|
||||
|
||||
static string GetSupportDir()
|
||||
{
|
||||
// Use the custom override if it has been defined
|
||||
if (supportDirOverride != null)
|
||||
return supportDirOverride;
|
||||
|
||||
// Use a local directory in the game root if it exists (shared with the system support dir)
|
||||
var localSupportDir = Path.Combine(GameDir, "Support");
|
||||
if (Directory.Exists(localSupportDir))
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace OpenRA
|
||||
public readonly PlayerReference PlayerReference;
|
||||
public readonly bool IsBot;
|
||||
public readonly string BotType;
|
||||
public readonly Shroud Shroud;
|
||||
public readonly FrozenActorLayer FrozenActorLayer;
|
||||
|
||||
/// <summary>The faction (including Random, etc) that was selected in the lobby.</summary>
|
||||
public readonly FactionInfo DisplayFaction;
|
||||
@@ -65,7 +67,6 @@ namespace OpenRA
|
||||
public bool HasObjectives = false;
|
||||
public bool Spectating;
|
||||
|
||||
public Shroud Shroud;
|
||||
public World World { get; private set; }
|
||||
|
||||
readonly bool inMissionMap;
|
||||
@@ -156,6 +157,7 @@ namespace OpenRA
|
||||
var playerActorType = world.Type == WorldType.Editor ? "EditorPlayer" : "Player";
|
||||
PlayerActor = world.CreateActor(playerActorType, new TypeDictionary { new OwnerInit(this) });
|
||||
Shroud = PlayerActor.Trait<Shroud>();
|
||||
FrozenActorLayer = PlayerActor.TraitOrDefault<FrozenActorLayer>();
|
||||
|
||||
// Enable the bot logic on the host
|
||||
IsBot = BotType != null;
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA
|
||||
|
||||
@@ -79,6 +79,18 @@ namespace OpenRA.Scripting
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is LuaNumber && t.IsAssignableFrom(typeof(short)))
|
||||
{
|
||||
clrObject = (short)value.ToNumber().Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is LuaNumber && t.IsAssignableFrom(typeof(byte)))
|
||||
{
|
||||
clrObject = (byte)value.ToNumber().Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is LuaString && t.IsAssignableFrom(typeof(string)))
|
||||
{
|
||||
clrObject = value.ToString();
|
||||
|
||||
@@ -13,8 +13,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
|
||||
@@ -21,7 +21,14 @@ namespace OpenRA
|
||||
{
|
||||
public int Hash { get; private set; }
|
||||
public IEnumerable<Actor> Actors { get { return actors; } }
|
||||
|
||||
readonly HashSet<Actor> actors = new HashSet<Actor>();
|
||||
readonly INotifySelection[] worldNotifySelection;
|
||||
|
||||
internal Selection(IEnumerable<INotifySelection> worldNotifySelection)
|
||||
{
|
||||
this.worldNotifySelection = worldNotifySelection.ToArray();
|
||||
}
|
||||
|
||||
void UpdateHash()
|
||||
{
|
||||
@@ -31,17 +38,41 @@ namespace OpenRA
|
||||
Hash += 1;
|
||||
}
|
||||
|
||||
public void Add(World w, Actor a)
|
||||
public void Add(Actor a)
|
||||
{
|
||||
actors.Add(a);
|
||||
UpdateHash();
|
||||
|
||||
foreach (var sel in a.TraitsImplementing<INotifySelected>())
|
||||
sel.Selected(a);
|
||||
foreach (var ns in w.WorldActor.TraitsImplementing<INotifySelection>())
|
||||
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
}
|
||||
|
||||
public void Remove(Actor a)
|
||||
{
|
||||
if (actors.Remove(a))
|
||||
{
|
||||
UpdateHash();
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnOwnerChanged(Actor a, Player oldOwner, Player newOwner)
|
||||
{
|
||||
if (!actors.Contains(a))
|
||||
return;
|
||||
|
||||
// Remove the actor from the original owners selection
|
||||
// Call UpdateHash directly for everyone else so watchers can account for the owner change if needed
|
||||
if (oldOwner == a.World.LocalPlayer)
|
||||
Remove(a);
|
||||
else
|
||||
UpdateHash();
|
||||
}
|
||||
|
||||
public bool Contains(Actor a)
|
||||
{
|
||||
return actors.Contains(a);
|
||||
@@ -77,7 +108,7 @@ namespace OpenRA
|
||||
foreach (var sel in a.TraitsImplementing<INotifySelected>())
|
||||
sel.Selected(a);
|
||||
|
||||
foreach (var ns in world.WorldActor.TraitsImplementing<INotifySelection>())
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
|
||||
if (world.IsGameOver)
|
||||
|
||||
@@ -157,6 +157,7 @@ namespace OpenRA.Server
|
||||
Map = settings.Map,
|
||||
ServerName = settings.Name,
|
||||
EnableSingleplayer = settings.EnableSingleplayer || !dedicated,
|
||||
EnableSyncReports = settings.EnableSyncReports,
|
||||
GameUid = Guid.NewGuid().ToString(),
|
||||
Dedicated = dedicated
|
||||
}
|
||||
@@ -170,7 +171,6 @@ namespace OpenRA.Server
|
||||
Log.Write("server", "Initial mod: {0}", ModData.Manifest.Id);
|
||||
Log.Write("server", "Initial map: {0}", LobbyInfo.GlobalSettings.Map);
|
||||
|
||||
var timeout = serverTraits.WithInterface<ITick>().Min(t => t.TickTimeout);
|
||||
for (;;)
|
||||
{
|
||||
var checkRead = new List<Socket>();
|
||||
@@ -180,7 +180,9 @@ namespace OpenRA.Server
|
||||
checkRead.AddRange(Conns.Select(c => c.Socket));
|
||||
checkRead.AddRange(PreConns.Select(c => c.Socket));
|
||||
|
||||
var localTimeout = waitingForAuthenticationCallback > 0 ? 100000 : timeout;
|
||||
// Block for at most 1 second in order to guarantee a minimum tick rate for ServerTraits
|
||||
// Decrease this to 100ms to improve responsiveness if we are waiting for an authentication query
|
||||
var localTimeout = waitingForAuthenticationCallback > 0 ? 100000 : 1000000;
|
||||
if (checkRead.Count > 0)
|
||||
Socket.Select(checkRead, null, null, localTimeout);
|
||||
|
||||
@@ -323,7 +325,6 @@ namespace OpenRA.Server
|
||||
Name = OpenRA.Settings.SanitizedPlayerName(handshake.Client.Name),
|
||||
IpAddress = ((IPEndPoint)newConn.Socket.RemoteEndPoint).Address.ToString(),
|
||||
Index = newConn.PlayerIndex,
|
||||
Slot = LobbyInfo.FirstEmptySlot(),
|
||||
PreferredColor = handshake.Client.PreferredColor,
|
||||
Color = handshake.Client.Color,
|
||||
Faction = "Random",
|
||||
@@ -333,18 +334,6 @@ namespace OpenRA.Server
|
||||
IsAdmin = !LobbyInfo.Clients.Any(c1 => c1.IsAdmin)
|
||||
};
|
||||
|
||||
if (client.IsObserver && !LobbyInfo.GlobalSettings.AllowSpectators)
|
||||
{
|
||||
SendOrderTo(newConn, "ServerError", "The game is full");
|
||||
DropClient(newConn);
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.Slot != null)
|
||||
SyncClientToPlayerReference(client, Map.Players.Players[client.Slot]);
|
||||
else
|
||||
client.Color = HSLColor.FromRGB(255, 255, 255);
|
||||
|
||||
if (ModData.Manifest.Id != handshake.Mod)
|
||||
{
|
||||
Log.Write("server", "Rejected connection from {0}; mods do not match.",
|
||||
@@ -355,7 +344,7 @@ namespace OpenRA.Server
|
||||
return;
|
||||
}
|
||||
|
||||
if (ModData.Manifest.Metadata.Version != handshake.Version && !LobbyInfo.GlobalSettings.AllowVersionMismatch)
|
||||
if (ModData.Manifest.Metadata.Version != handshake.Version)
|
||||
{
|
||||
Log.Write("server", "Rejected connection from {0}; Not running the same version.",
|
||||
newConn.Socket.RemoteEndPoint);
|
||||
@@ -377,6 +366,20 @@ namespace OpenRA.Server
|
||||
|
||||
Action completeConnection = () =>
|
||||
{
|
||||
client.Slot = LobbyInfo.FirstEmptySlot();
|
||||
|
||||
if (client.IsObserver && !LobbyInfo.GlobalSettings.AllowSpectators)
|
||||
{
|
||||
SendOrderTo(newConn, "ServerError", "The game is full");
|
||||
DropClient(newConn);
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.Slot != null)
|
||||
SyncClientToPlayerReference(client, Map.Players.Players[client.Slot]);
|
||||
else
|
||||
client.Color = HSLColor.FromRGB(255, 255, 255);
|
||||
|
||||
// Promote connection to a valid client
|
||||
PreConns.Remove(newConn);
|
||||
Conns.Add(newConn);
|
||||
@@ -475,11 +478,25 @@ namespace OpenRA.Server
|
||||
|
||||
delayedActions.Add(() =>
|
||||
{
|
||||
if (Dedicated && Settings.RequireAuthIDs.Any() &&
|
||||
(profile == null || !Settings.RequireAuthIDs.Contains(profile.ProfileID)))
|
||||
var notAuthenticated = Dedicated && profile == null && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any());
|
||||
var blacklisted = Dedicated && profile != null && Settings.ProfileIDBlacklist.Contains(profile.ProfileID);
|
||||
var notWhitelisted = Dedicated && Settings.ProfileIDWhitelist.Any() &&
|
||||
(profile == null || !Settings.ProfileIDWhitelist.Contains(profile.ProfileID));
|
||||
|
||||
if (notAuthenticated)
|
||||
{
|
||||
Log.Write("server", "Rejected connection from {0}; Not in server whitelist.", newConn.Socket.RemoteEndPoint);
|
||||
SendOrderTo(newConn, "ServerError", "You are not authenticated for this server");
|
||||
Log.Write("server", "Rejected connection from {0}; Not authenticated.", newConn.Socket.RemoteEndPoint);
|
||||
SendOrderTo(newConn, "ServerError", "Server requires players to have an OpenRA forum account");
|
||||
DropClient(newConn);
|
||||
}
|
||||
else if (blacklisted || notWhitelisted)
|
||||
{
|
||||
if (blacklisted)
|
||||
Log.Write("server", "Rejected connection from {0}; In server blacklist.", newConn.Socket.RemoteEndPoint);
|
||||
else
|
||||
Log.Write("server", "Rejected connection from {0}; Not in server whitelist.", newConn.Socket.RemoteEndPoint);
|
||||
|
||||
SendOrderTo(newConn, "ServerError", "You do not have permission to join this server");
|
||||
DropClient(newConn);
|
||||
}
|
||||
else
|
||||
@@ -493,10 +510,10 @@ namespace OpenRA.Server
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Dedicated && Settings.RequireAuthIDs.Any())
|
||||
if (Dedicated && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any()))
|
||||
{
|
||||
Log.Write("server", "Rejected connection from {0}; Not authenticated and whitelist is set.", newConn.Socket.RemoteEndPoint);
|
||||
SendOrderTo(newConn, "ServerError", "You are not authenticated for this server");
|
||||
Log.Write("server", "Rejected connection from {0}; Not authenticated.", newConn.Socket.RemoteEndPoint);
|
||||
SendOrderTo(newConn, "ServerError", "Server requires players to have an OpenRA forum account");
|
||||
DropClient(newConn);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -23,11 +23,7 @@ namespace OpenRA.Server
|
||||
public interface IStartGame { void GameStarted(Server server); }
|
||||
public interface IClientJoined { void ClientJoined(Server server, Connection conn); }
|
||||
public interface IEndGame { void GameEnded(Server server); }
|
||||
public interface ITick
|
||||
{
|
||||
void Tick(Server server);
|
||||
int TickTimeout { get; }
|
||||
}
|
||||
public interface ITick { void Tick(Server server); }
|
||||
|
||||
public abstract class ServerTrait { }
|
||||
|
||||
|
||||
@@ -59,8 +59,14 @@ namespace OpenRA
|
||||
[Desc("Takes a comma separated list of IP addresses that are not allowed to join.")]
|
||||
public string[] Ban = { };
|
||||
|
||||
[Desc("If non-empty, only allow authenticated players with these user IDs to join.")]
|
||||
public int[] RequireAuthIDs = { };
|
||||
[Desc("For dedicated servers only, allow anonymous clients to join.")]
|
||||
public bool RequireAuthentication = false;
|
||||
|
||||
[Desc("For dedicated servers only, if non-empty, only allow authenticated players with these profile IDs to join.")]
|
||||
public int[] ProfileIDWhitelist = { };
|
||||
|
||||
[Desc("For dedicated servers only, if non-empty, always reject players with these user IDs from joining.")]
|
||||
public int[] ProfileIDBlacklist = { };
|
||||
|
||||
[Desc("For dedicated servers only, controls whether a game can be started with just one human player in the lobby.")]
|
||||
public bool EnableSingleplayer = false;
|
||||
@@ -68,6 +74,9 @@ namespace OpenRA
|
||||
[Desc("Query map information from the Resource Center if they are not available locally.")]
|
||||
public bool QueryMapRepository = true;
|
||||
|
||||
[Desc("Enable client-side report generation to help debug desync errors.")]
|
||||
public bool EnableSyncReports = false;
|
||||
|
||||
public string TimestampFormat = "s";
|
||||
|
||||
public ServerSettings Clone()
|
||||
@@ -78,31 +87,50 @@ namespace OpenRA
|
||||
|
||||
public class DebugSettings
|
||||
{
|
||||
public bool BotDebug = false;
|
||||
public bool LuaDebug = false;
|
||||
[Desc("Display average FPS and tick/render times")]
|
||||
public bool PerfText = false;
|
||||
|
||||
[Desc("Display a graph with various profiling traces")]
|
||||
public bool PerfGraph = false;
|
||||
|
||||
[Desc("Amount of time required for triggering perf.log output.")]
|
||||
public float LongTickThresholdMs = 1;
|
||||
|
||||
public bool SanityCheckUnsyncedCode = false;
|
||||
[Desc("Numer of samples to average over when calculating tick and render times.")]
|
||||
public int Samples = 25;
|
||||
|
||||
[Desc("Show incompatible games in server browser.")]
|
||||
public bool IgnoreVersionMismatch = false;
|
||||
|
||||
public bool StrictActivityChecking = false;
|
||||
|
||||
[Desc("Check whether a newer version is available online.")]
|
||||
public bool CheckVersion = true;
|
||||
|
||||
[Desc("Allow the collection of anonymous data such as Operating System, .NET runtime, OpenGL version and language settings.")]
|
||||
public bool SendSystemInformation = true;
|
||||
|
||||
[Desc("Version of sysinfo that the player last opted in or out of.")]
|
||||
public int SystemInformationVersionPrompt = 0;
|
||||
public string UUID = System.Guid.NewGuid().ToString();
|
||||
|
||||
[Desc("Sysinfo anonymous user identifier.")]
|
||||
public string UUID = Guid.NewGuid().ToString();
|
||||
|
||||
[Desc("Enable hidden developer settings in the Advanced settings tab.")]
|
||||
public bool DisplayDeveloperSettings = false;
|
||||
|
||||
[Desc("Display bot debug messages in the game chat.")]
|
||||
public bool BotDebug = false;
|
||||
|
||||
[Desc("Display Lua debug messages in the game chat.")]
|
||||
public bool LuaDebug = false;
|
||||
|
||||
[Desc("Enable the chat field during replays to allow use of console commands.")]
|
||||
public bool EnableDebugCommandsInReplays = false;
|
||||
|
||||
[Desc("Amount of time required for triggering perf.log output.")]
|
||||
public float LongTickThresholdMs = 1;
|
||||
|
||||
[Desc("Throw an exception if the world sync hash changes while evaluating user input.")]
|
||||
public bool SyncCheckUnsyncedCode = false;
|
||||
|
||||
[Desc("Throw an exception if the world sync hash changes while evaluating BotModules.")]
|
||||
public bool SyncCheckBotModuleCode = false;
|
||||
|
||||
[Desc("Throw an exception if an actor activity is ticked after it has been marked as completed.")]
|
||||
public bool StrictActivityChecking = false;
|
||||
}
|
||||
|
||||
public class GraphicSettings
|
||||
@@ -130,6 +158,9 @@ namespace OpenRA
|
||||
[Desc("Disable high resolution DPI scaling on Windows operating systems.")]
|
||||
public bool DisableWindowsDPIScaling = true;
|
||||
|
||||
[Desc("Disable separate OpenGL render thread on Windows operating systems.")]
|
||||
public bool DisableWindowsRenderThread = true;
|
||||
|
||||
public int BatchSize = 8192;
|
||||
public int SheetSize = 2048;
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace OpenRA
|
||||
|
||||
Func<ISoundFormat, ISound> stream = soundFormat => soundEngine.Play2DStream(
|
||||
soundFormat.GetPCMInputStream(), soundFormat.Channels, soundFormat.SampleBits, soundFormat.SampleRate,
|
||||
false, true, WPos.Zero, MusicVolume);
|
||||
false, true, WPos.Zero, MusicVolume * m.VolumeModifier);
|
||||
music = LoadSound(m.Filename, stream);
|
||||
|
||||
if (music == null)
|
||||
@@ -352,7 +352,7 @@ namespace OpenRA
|
||||
|
||||
var id = voicedActor != null ? voicedActor.ActorID : 0;
|
||||
|
||||
string clip;
|
||||
SoundPool pool;
|
||||
var suffix = rules.DefaultVariant;
|
||||
var prefix = rules.DefaultPrefix;
|
||||
|
||||
@@ -361,16 +361,17 @@ namespace OpenRA
|
||||
if (!rules.VoicePools.Value.ContainsKey(definition))
|
||||
throw new InvalidOperationException("Can't find {0} in voice pool.".F(definition));
|
||||
|
||||
clip = rules.VoicePools.Value[definition].GetNext();
|
||||
pool = rules.VoicePools.Value[definition];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!rules.NotificationsPools.Value.ContainsKey(definition))
|
||||
throw new InvalidOperationException("Can't find {0} in notification pool.".F(definition));
|
||||
|
||||
clip = rules.NotificationsPools.Value[definition].GetNext();
|
||||
pool = rules.NotificationsPools.Value[definition];
|
||||
}
|
||||
|
||||
var clip = pool.GetNext();
|
||||
if (string.IsNullOrEmpty(clip))
|
||||
return false;
|
||||
|
||||
@@ -388,7 +389,7 @@ namespace OpenRA
|
||||
{
|
||||
var sound = soundEngine.Play2D(sounds[name],
|
||||
false, relative, pos,
|
||||
InternalSoundVolume * volumeModifier, attenuateVolume);
|
||||
InternalSoundVolume * volumeModifier * pool.VolumeModifier, attenuateVolume);
|
||||
if (id != 0)
|
||||
{
|
||||
if (currentSounds.ContainsKey(id))
|
||||
|
||||
@@ -168,8 +168,8 @@ namespace OpenRA.Support
|
||||
public readonly Grouping Closes;
|
||||
|
||||
public TokenTypeInfo(string symbol, Precedence precedence, Sides operandSides = Sides.None,
|
||||
Associativity associativity = Associativity.Left,
|
||||
Grouping opens = Grouping.None, Grouping closes = Grouping.None)
|
||||
Associativity associativity = Associativity.Left,
|
||||
Grouping opens = Grouping.None, Grouping closes = Grouping.None)
|
||||
{
|
||||
Symbol = symbol;
|
||||
Precedence = precedence;
|
||||
@@ -181,9 +181,9 @@ namespace OpenRA.Support
|
||||
}
|
||||
|
||||
public TokenTypeInfo(string symbol, Precedence precedence, Sides operandSides,
|
||||
Sides whitespaceSides,
|
||||
Associativity associativity = Associativity.Left,
|
||||
Grouping opens = Grouping.None, Grouping closes = Grouping.None)
|
||||
Sides whitespaceSides,
|
||||
Associativity associativity = Associativity.Left,
|
||||
Grouping opens = Grouping.None, Grouping closes = Grouping.None)
|
||||
{
|
||||
Symbol = symbol;
|
||||
Precedence = precedence;
|
||||
@@ -195,15 +195,14 @@ namespace OpenRA.Support
|
||||
}
|
||||
|
||||
public TokenTypeInfo(string symbol, Precedence precedence, Grouping opens, Grouping closes = Grouping.None,
|
||||
Associativity associativity = Associativity.Left)
|
||||
Associativity associativity = Associativity.Left)
|
||||
{
|
||||
Symbol = symbol;
|
||||
Precedence = precedence;
|
||||
WhitespaceSides = Sides.None;
|
||||
OperandSides = opens == Grouping.None ?
|
||||
(closes == Grouping.None ? Sides.None : Sides.Left)
|
||||
:
|
||||
(closes == Grouping.None ? Sides.Right : Sides.Both);
|
||||
(closes == Grouping.None ? Sides.None : Sides.Left) :
|
||||
(closes == Grouping.None ? Sides.Right : Sides.Both);
|
||||
Associativity = associativity;
|
||||
Opens = opens;
|
||||
Closes = closes;
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace OpenRA
|
||||
foreach (var prop in t.GetProperties(Binding).Where(x => x.HasAttribute<SyncAttribute>()))
|
||||
{
|
||||
il.Emit(OpCodes.Ldloc, this_);
|
||||
il.EmitCall(OpCodes.Call, prop.GetGetMethod(), null);
|
||||
il.EmitCall(OpCodes.Call, prop.GetGetMethod(true), null);
|
||||
|
||||
EmitSyncOpcodes(prop.PropertyType, il);
|
||||
}
|
||||
@@ -161,20 +161,19 @@ namespace OpenRA
|
||||
return t.GetHashCode();
|
||||
}
|
||||
|
||||
public static void CheckSyncUnchanged(World world, Action fn)
|
||||
public static void RunUnsynced(bool checkSyncHash, World world, Action fn)
|
||||
{
|
||||
CheckSyncUnchanged(world, () => { fn(); return true; });
|
||||
RunUnsynced(checkSyncHash, world, () => { fn(); return true; });
|
||||
}
|
||||
|
||||
static bool inUnsyncedCode = false;
|
||||
|
||||
public static T CheckSyncUnchanged<T>(World world, Func<T> fn)
|
||||
public static T RunUnsynced<T>(bool checkSyncHash, World world, Func<T> fn)
|
||||
{
|
||||
if (world == null)
|
||||
return fn();
|
||||
|
||||
var shouldCheckSync = Game.Settings.Debug.SanityCheckUnsyncedCode;
|
||||
var sync = shouldCheckSync ? world.SyncHash() : 0;
|
||||
var sync = checkSyncHash ? world.SyncHash() : 0;
|
||||
var prevInUnsyncedCode = inUnsyncedCode;
|
||||
inUnsyncedCode = true;
|
||||
|
||||
@@ -185,8 +184,8 @@ namespace OpenRA
|
||||
finally
|
||||
{
|
||||
inUnsyncedCode = prevInUnsyncedCode;
|
||||
if (shouldCheckSync && sync != world.SyncHash())
|
||||
throw new InvalidOperationException("CheckSyncUnchanged: sync-changing code may not run here");
|
||||
if (checkSyncHash && sync != world.SyncHash())
|
||||
throw new InvalidOperationException("RunUnsynced: sync-changing code may not run here");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
|
||||
@@ -37,6 +37,19 @@ namespace OpenRA.Traits
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class LocomotorReferenceAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class NotificationReferenceAttribute : Attribute
|
||||
{
|
||||
public readonly string NotificationTypeFieldName = null;
|
||||
public readonly string NotificationType = null;
|
||||
|
||||
public NotificationReferenceAttribute(string type = null, string typeFromField = null)
|
||||
{
|
||||
NotificationType = type;
|
||||
NotificationTypeFieldName = typeFromField;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class SequenceReferenceAttribute : Attribute
|
||||
{
|
||||
|
||||
@@ -18,6 +18,11 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
public interface ICreatesFrozenActors
|
||||
{
|
||||
void OnVisibilityChanged(FrozenActor frozen);
|
||||
}
|
||||
|
||||
[Desc("Required for FrozenUnderFog to work. Attach this to the player actor.")]
|
||||
public class FrozenActorLayerInfo : Requires<ShroudInfo>, ITraitInfo
|
||||
{
|
||||
@@ -32,11 +37,13 @@ namespace OpenRA.Traits
|
||||
public readonly PPos[] Footprint;
|
||||
public readonly WPos CenterPosition;
|
||||
readonly Actor actor;
|
||||
readonly ICreatesFrozenActors frozenTrait;
|
||||
readonly Player viewer;
|
||||
readonly Shroud shroud;
|
||||
|
||||
public Player Owner { get; private set; }
|
||||
public BitSet<TargetableType> TargetTypes { get; private set; }
|
||||
public WPos[] TargetablePositions { get; private set; }
|
||||
|
||||
public ITooltipInfo TooltipInfo { get; private set; }
|
||||
public Player TooltipOwner { get; private set; }
|
||||
@@ -46,7 +53,17 @@ namespace OpenRA.Traits
|
||||
public DamageState DamageState { get; private set; }
|
||||
readonly IHealth health;
|
||||
|
||||
// The Visible flag is tied directly to the actor visibility under the fog.
|
||||
// If Visible is true, the actor is made invisible (via FrozenUnderFog/IDefaultVisibility)
|
||||
// and this FrozenActor is rendered instead.
|
||||
// The Hidden flag covers the edge case that occurs when the backing actor was last "seen"
|
||||
// to be cloaked or otherwise not CanBeViewedByPlayer()ed. Setting Visible to true when
|
||||
// the actor is hidden under the fog would leak the actors position via the tooltips and
|
||||
// AutoTargetability, and keeping Visible as false would cause the actor to be rendered
|
||||
// under the fog.
|
||||
public bool Visible = true;
|
||||
public bool Hidden = false;
|
||||
|
||||
public bool Shrouded { get; private set; }
|
||||
public bool NeedRenderables { get; set; }
|
||||
public IRenderable[] Renderables = NoRenderables;
|
||||
@@ -60,9 +77,10 @@ namespace OpenRA.Traits
|
||||
|
||||
int flashTicks;
|
||||
|
||||
public FrozenActor(Actor actor, PPos[] footprint, Player viewer, bool startsRevealed)
|
||||
public FrozenActor(Actor actor, ICreatesFrozenActors frozenTrait, PPos[] footprint, Player viewer, bool startsRevealed)
|
||||
{
|
||||
this.actor = actor;
|
||||
this.frozenTrait = frozenTrait;
|
||||
this.viewer = viewer;
|
||||
shroud = viewer.Shroud;
|
||||
NeedRenderables = startsRevealed;
|
||||
@@ -101,6 +119,8 @@ namespace OpenRA.Traits
|
||||
{
|
||||
Owner = actor.Owner;
|
||||
TargetTypes = actor.GetEnabledTargetTypes();
|
||||
TargetablePositions = actor.GetTargetablePositions().ToArray();
|
||||
Hidden = !actor.CanBeViewedByPlayer(viewer);
|
||||
|
||||
if (health != null)
|
||||
{
|
||||
@@ -142,9 +162,19 @@ namespace OpenRA.Traits
|
||||
Shrouded = false;
|
||||
}
|
||||
|
||||
// Force the backing trait to update so other actors can't
|
||||
// query inconsistent state (both hidden or both visible)
|
||||
if (Visible != wasVisible)
|
||||
frozenTrait.OnVisibilityChanged(this);
|
||||
|
||||
NeedRenderables |= Visible && !wasVisible;
|
||||
}
|
||||
|
||||
public void Invalidate()
|
||||
{
|
||||
Owner = null;
|
||||
}
|
||||
|
||||
public void Flash()
|
||||
{
|
||||
flashTicks = 5;
|
||||
@@ -323,11 +353,28 @@ namespace OpenRA.Traits
|
||||
return fa;
|
||||
}
|
||||
|
||||
public IEnumerable<FrozenActor> FrozenActorsInRegion(CellRegion region)
|
||||
public IEnumerable<FrozenActor> FrozenActorsInRegion(CellRegion region, bool onlyVisible = true)
|
||||
{
|
||||
var tl = region.TopLeft;
|
||||
var br = region.BottomRight;
|
||||
return partitionedFrozenActorIds.InBox(Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y)).Select(FromID);
|
||||
return partitionedFrozenActorIds.InBox(Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y))
|
||||
.Select(FromID)
|
||||
.Where(fa => fa.IsValid && (!onlyVisible || fa.Visible));
|
||||
}
|
||||
|
||||
public IEnumerable<FrozenActor> FrozenActorsInCircle(World world, WPos origin, WDist r, bool onlyVisible = true)
|
||||
{
|
||||
var centerCell = world.Map.CellContaining(origin);
|
||||
var cellRange = (r.Length + 1023) / 1024;
|
||||
var tl = centerCell - new CVec(cellRange, cellRange);
|
||||
var br = centerCell + new CVec(cellRange, cellRange);
|
||||
|
||||
// Target ranges are calculated in 2D, so ignore height differences
|
||||
return partitionedFrozenActorIds.InBox(Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y))
|
||||
.Select(FromID)
|
||||
.Where(fa => fa.IsValid &&
|
||||
(!onlyVisible || fa.Visible) &&
|
||||
(fa.CenterPosition - origin).HorizontalLengthSquared <= r.LengthSquared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
[Desc("This actor is selectable. Defines bounds of selectable area, selection class and selection priority.")]
|
||||
|
||||
@@ -19,42 +19,74 @@ namespace OpenRA.Traits
|
||||
public struct Target
|
||||
{
|
||||
public static readonly Target[] None = { };
|
||||
public static readonly Target Invalid = new Target { type = TargetType.Invalid };
|
||||
public static readonly Target Invalid = new Target();
|
||||
|
||||
TargetType type;
|
||||
Actor actor;
|
||||
FrozenActor frozen;
|
||||
WPos pos;
|
||||
CPos? cell;
|
||||
SubCell? subCell;
|
||||
int generation;
|
||||
readonly TargetType type;
|
||||
readonly Actor actor;
|
||||
readonly FrozenActor frozen;
|
||||
readonly WPos terrainCenterPosition;
|
||||
readonly WPos[] terrainPositions;
|
||||
readonly CPos? cell;
|
||||
readonly SubCell? subCell;
|
||||
readonly int generation;
|
||||
|
||||
public static Target FromPos(WPos p) { return new Target { pos = p, type = TargetType.Terrain }; }
|
||||
public static Target FromCell(World w, CPos c, SubCell subCell = SubCell.FullCell)
|
||||
Target(WPos terrainCenterPosition, WPos[] terrainPositions = null)
|
||||
{
|
||||
return new Target
|
||||
{
|
||||
pos = w.Map.CenterOfSubCell(c, subCell),
|
||||
cell = c,
|
||||
subCell = subCell,
|
||||
type = TargetType.Terrain
|
||||
};
|
||||
type = TargetType.Terrain;
|
||||
this.terrainCenterPosition = terrainCenterPosition;
|
||||
this.terrainPositions = terrainPositions ?? new[] { terrainCenterPosition };
|
||||
|
||||
actor = null;
|
||||
frozen = null;
|
||||
cell = null;
|
||||
subCell = null;
|
||||
generation = 0;
|
||||
}
|
||||
|
||||
public static Target FromActor(Actor a)
|
||||
Target(World w, CPos c, SubCell subCell)
|
||||
{
|
||||
if (a == null)
|
||||
return Invalid;
|
||||
type = TargetType.Terrain;
|
||||
terrainCenterPosition = w.Map.CenterOfSubCell(c, subCell);
|
||||
terrainPositions = new[] { terrainCenterPosition };
|
||||
cell = c;
|
||||
this.subCell = subCell;
|
||||
|
||||
return new Target
|
||||
{
|
||||
actor = a,
|
||||
type = TargetType.Actor,
|
||||
generation = a.Generation,
|
||||
};
|
||||
actor = null;
|
||||
frozen = null;
|
||||
generation = 0;
|
||||
}
|
||||
|
||||
public static Target FromFrozenActor(FrozenActor a) { return new Target { frozen = a, type = TargetType.FrozenActor }; }
|
||||
Target(Actor a)
|
||||
{
|
||||
type = TargetType.Actor;
|
||||
actor = a;
|
||||
generation = a.Generation;
|
||||
|
||||
terrainCenterPosition = WPos.Zero;
|
||||
terrainPositions = null;
|
||||
frozen = null;
|
||||
cell = null;
|
||||
subCell = null;
|
||||
}
|
||||
|
||||
Target(FrozenActor fa)
|
||||
{
|
||||
type = TargetType.FrozenActor;
|
||||
frozen = fa;
|
||||
|
||||
terrainCenterPosition = WPos.Zero;
|
||||
terrainPositions = null;
|
||||
actor = null;
|
||||
cell = null;
|
||||
subCell = null;
|
||||
generation = 0;
|
||||
}
|
||||
|
||||
public static Target FromPos(WPos p) { return new Target(p); }
|
||||
public static Target FromTargetPositions(Target t) { return new Target(t.CenterPosition, t.Positions.ToArray()); }
|
||||
public static Target FromCell(World w, CPos c, SubCell subCell = SubCell.FullCell) { return new Target(w, c, subCell); }
|
||||
public static Target FromActor(Actor a) { return a != null ? new Target(a) : Invalid; }
|
||||
public static Target FromFrozenActor(FrozenActor fa) { return new Target(fa); }
|
||||
|
||||
public Actor Actor { get { return actor; } }
|
||||
public FrozenActor FrozenActor { get { return frozen; } }
|
||||
@@ -80,13 +112,21 @@ namespace OpenRA.Traits
|
||||
|
||||
public bool IsValidFor(Actor targeter)
|
||||
{
|
||||
if (targeter == null || Type == TargetType.Invalid)
|
||||
if (targeter == null)
|
||||
return false;
|
||||
|
||||
if (actor != null && !actor.IsTargetableBy(targeter))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
switch (Type)
|
||||
{
|
||||
case TargetType.Actor:
|
||||
return actor.IsTargetableBy(targeter);
|
||||
case TargetType.FrozenActor:
|
||||
return frozen.IsValid && frozen.Visible && !frozen.Hidden;
|
||||
case TargetType.Invalid:
|
||||
return false;
|
||||
default:
|
||||
case TargetType.Terrain:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Currently all or nothing.
|
||||
@@ -126,7 +166,7 @@ namespace OpenRA.Traits
|
||||
case TargetType.FrozenActor:
|
||||
return frozen.CenterPosition;
|
||||
case TargetType.Terrain:
|
||||
return pos;
|
||||
return terrainCenterPosition;
|
||||
default:
|
||||
case TargetType.Invalid:
|
||||
throw new InvalidOperationException("Attempting to query the position of an invalid Target");
|
||||
@@ -143,14 +183,11 @@ namespace OpenRA.Traits
|
||||
switch (Type)
|
||||
{
|
||||
case TargetType.Actor:
|
||||
if (!actor.Targetables.Any(Exts.IsTraitEnabled))
|
||||
return new[] { actor.CenterPosition };
|
||||
|
||||
return actor.GetTargetablePositions();
|
||||
case TargetType.FrozenActor:
|
||||
return new[] { frozen.CenterPosition };
|
||||
return frozen.TargetablePositions;
|
||||
case TargetType.Terrain:
|
||||
return new[] { pos };
|
||||
return terrainPositions;
|
||||
default:
|
||||
case TargetType.Invalid:
|
||||
return NoPositions;
|
||||
@@ -178,7 +215,7 @@ namespace OpenRA.Traits
|
||||
return frozen.ToString();
|
||||
|
||||
case TargetType.Terrain:
|
||||
return pos.ToString();
|
||||
return terrainCenterPosition.ToString();
|
||||
|
||||
default:
|
||||
case TargetType.Invalid:
|
||||
@@ -191,6 +228,6 @@ namespace OpenRA.Traits
|
||||
internal Actor SerializableActor { get { return actor; } }
|
||||
internal CPos? SerializableCell { get { return cell; } }
|
||||
internal SubCell? SerializableSubCell { get { return subCell; } }
|
||||
internal WPos SerializablePos { get { return pos; } }
|
||||
internal WPos SerializablePos { get { return terrainCenterPosition; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
@@ -38,6 +38,11 @@ namespace OpenRA.Traits
|
||||
/// </summary>
|
||||
public sealed class DamageType { DamageType() { } }
|
||||
|
||||
public interface IHealthInfo : ITraitInfo
|
||||
{
|
||||
int MaxHP { get; }
|
||||
}
|
||||
|
||||
public interface IHealth
|
||||
{
|
||||
DamageState DamageState { get; }
|
||||
@@ -266,6 +271,13 @@ namespace OpenRA.Traits
|
||||
IEnumerable<Rectangle> ModifyScreenBounds(Actor self, WorldRenderer wr, IEnumerable<Rectangle> r);
|
||||
}
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface IProvidesCursorPaletteInfo : ITraitInfoInterface
|
||||
{
|
||||
string Palette { get; }
|
||||
ImmutablePalette ReadPalette(IReadOnlyFileSystem fileSystem);
|
||||
}
|
||||
|
||||
public interface ILoadsPalettes { void LoadPalettes(WorldRenderer wr); }
|
||||
public interface ILoadsPlayerPalettes { void LoadPlayerPalettes(WorldRenderer wr, string playerName, HSLColor playerColor, bool replaceExisting); }
|
||||
public interface IPaletteModifier { void AdjustPalette(IReadOnlyDictionary<string, MutablePalette> b); }
|
||||
@@ -287,7 +299,7 @@ namespace OpenRA.Traits
|
||||
Pair<CPos, SubCell>[] OccupiedCells();
|
||||
}
|
||||
|
||||
public enum SubCell { Invalid = int.MinValue, Any = int.MinValue / 2, FullCell = 0, First = 1 }
|
||||
public enum SubCell : byte { Invalid = byte.MaxValue, Any = byte.MaxValue - 1, FullCell = 0, First = 1 }
|
||||
|
||||
public interface IPositionableInfo : IOccupySpaceInfo
|
||||
{
|
||||
@@ -331,8 +343,6 @@ namespace OpenRA.Traits
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:InterfaceNamesMustBeginWithI", Justification = "Not a real interface, but more like a tag.")]
|
||||
public interface Requires<T> where T : class, ITraitInfoInterface { }
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:InterfaceNamesMustBeginWithI", Justification = "Not a real interface, but more like a tag.")]
|
||||
public interface UsesInit<T> : ITraitInfo where T : IActorInit { }
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface INotifySelected { void Selected(Actor self); }
|
||||
@@ -353,7 +363,9 @@ namespace OpenRA.Traits
|
||||
public interface IBot
|
||||
{
|
||||
void Activate(Player p);
|
||||
void QueueOrder(Order order);
|
||||
IBotInfo Info { get; }
|
||||
Player Player { get; }
|
||||
}
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
@@ -361,12 +373,16 @@ namespace OpenRA.Traits
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface INotifyBecomingIdle { void OnBecomingIdle(Actor self); }
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface INotifyIdle { void TickIdle(Actor self); }
|
||||
|
||||
public interface IRenderAboveWorld { void RenderAboveWorld(Actor self, WorldRenderer wr); }
|
||||
public interface IRenderShroud { void RenderShroud(Shroud shroud, WorldRenderer wr); }
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface IRenderTerrain { void RenderTerrain(WorldRenderer wr, Viewport viewport); }
|
||||
|
||||
public interface IRenderAboveShroud
|
||||
{
|
||||
IEnumerable<IRenderable> RenderAboveShroud(Actor self, WorldRenderer wr);
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace OpenRA
|
||||
public Session LobbyInfo { get { return OrderManager.LobbyInfo; } }
|
||||
|
||||
public readonly MersenneTwister SharedRandom;
|
||||
public readonly MersenneTwister LocalRandom;
|
||||
public readonly IModelCache ModelCache;
|
||||
|
||||
public Player[] Players = new Player[0];
|
||||
@@ -65,7 +66,7 @@ namespace OpenRA
|
||||
|
||||
foreach (var t in WorldActor.TraitsImplementing<IGameOver>())
|
||||
t.GameOver(this);
|
||||
|
||||
gameInfo.FinalGameTick = WorldTick;
|
||||
GameOver();
|
||||
}
|
||||
}
|
||||
@@ -141,7 +142,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Selection Selection = new Selection();
|
||||
public readonly Selection Selection;
|
||||
|
||||
public void CancelInputMode() { OrderGenerator = new UnitOrderGenerator(); }
|
||||
|
||||
@@ -169,6 +170,7 @@ namespace OpenRA
|
||||
Map = map;
|
||||
Timestep = orderManager.LobbyInfo.GlobalSettings.Timestep;
|
||||
SharedRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
|
||||
LocalRandom = new MersenneTwister();
|
||||
|
||||
ModelCache = modData.ModelSequenceLoader.CacheModels(map, modData, map.Rules.ModelSequences);
|
||||
|
||||
@@ -177,6 +179,8 @@ namespace OpenRA
|
||||
ActorMap = WorldActor.Trait<IActorMap>();
|
||||
ScreenMap = WorldActor.Trait<ScreenMap>();
|
||||
|
||||
Selection = new Selection(WorldActor.TraitsImplementing<INotifySelection>());
|
||||
|
||||
// Add players
|
||||
foreach (var cmp in WorldActor.TraitsImplementing<ICreatePlayers>())
|
||||
cmp.CreatePlayers(this);
|
||||
@@ -355,12 +359,7 @@ namespace OpenRA
|
||||
{
|
||||
WorldTick++;
|
||||
|
||||
using (new PerfSample("tick_idle"))
|
||||
foreach (var ni in ActorsWithTrait<INotifyIdle>())
|
||||
if (ni.Actor.IsIdle)
|
||||
ni.Trait.TickIdle(ni.Actor);
|
||||
|
||||
using (new PerfSample("tick_activities"))
|
||||
using (new PerfSample("tick_actors"))
|
||||
foreach (var a in actors.Values)
|
||||
a.Tick();
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -20,32 +21,49 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
class Infiltrate : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly Infiltrates infiltrates;
|
||||
readonly INotifyInfiltration[] notifiers;
|
||||
Actor enterActor;
|
||||
|
||||
public Infiltrate(Actor self, Actor target, Infiltrates infiltrate)
|
||||
: base(self, target, infiltrate.Info.EnterBehaviour)
|
||||
public Infiltrate(Actor self, Target target, Infiltrates infiltrates)
|
||||
: base(self, target, Color.Red)
|
||||
{
|
||||
this.target = target;
|
||||
infiltrates = infiltrate;
|
||||
this.infiltrates = infiltrates;
|
||||
notifiers = self.TraitsImplementing<INotifyInfiltration>().ToArray();
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override void TickInner(Actor self, Target target, bool targetIsDeadOrHiddenActor)
|
||||
{
|
||||
if (target.IsDead)
|
||||
return;
|
||||
if (infiltrates.IsTraitDisabled)
|
||||
Cancel(self, true);
|
||||
}
|
||||
|
||||
var stance = self.Owner.Stances[target.Owner];
|
||||
if (!infiltrates.Info.ValidStances.HasStance(stance))
|
||||
protected override bool TryStartEnter(Actor self, Actor targetActor)
|
||||
{
|
||||
// Make sure we can still demolish the target before entering
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
if (!infiltrates.CanInfiltrateTarget(self, Target.FromActor(targetActor)))
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
enterActor = targetActor;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (targetActor != enterActor || !infiltrates.CanInfiltrateTarget(self, Target.FromActor(targetActor)))
|
||||
return;
|
||||
|
||||
foreach (var ini in notifiers)
|
||||
ini.Infiltrating(self);
|
||||
|
||||
foreach (var t in target.TraitsImplementing<INotifyInfiltrated>())
|
||||
t.Infiltrated(target, self, infiltrates.Info.Types);
|
||||
foreach (var t in targetActor.TraitsImplementing<INotifyInfiltrated>())
|
||||
t.Infiltrated(targetActor, self, infiltrates.Info.Types);
|
||||
|
||||
var exp = self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>();
|
||||
if (exp != null)
|
||||
@@ -54,14 +72,11 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
if (!string.IsNullOrEmpty(infiltrates.Info.Notification))
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech",
|
||||
infiltrates.Info.Notification, self.Owner.Faction.InternalName);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (infiltrates.IsTraitDisabled)
|
||||
Cancel(self);
|
||||
|
||||
return base.Tick(self);
|
||||
if (infiltrates.Info.EnterBehaviour == EnterBehaviour.Dispose)
|
||||
self.Dispose();
|
||||
else if (infiltrates.Info.EnterBehaviour == EnterBehaviour.Suicide)
|
||||
self.Kill(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
@@ -27,7 +26,7 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
readonly MinelayerInfo info;
|
||||
readonly AmmoPool[] ammoPools;
|
||||
readonly IMove movement;
|
||||
readonly HashSet<string> rearmBuildings;
|
||||
readonly RearmableInfo rearmableInfo;
|
||||
|
||||
public LayMines(Actor self)
|
||||
{
|
||||
@@ -35,7 +34,7 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
info = self.Info.TraitInfo<MinelayerInfo>();
|
||||
ammoPools = self.TraitsImplementing<AmmoPool>().ToArray();
|
||||
movement = self.Trait<IMove>();
|
||||
rearmBuildings = info.RearmBuildings;
|
||||
rearmableInfo = self.Info.TraitInfoOrDefault<RearmableInfo>();
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
@@ -43,21 +42,21 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
if (ammoPools != null && ammoPools.Any(p => p.Info.Name == info.AmmoPoolName && !p.HasAmmo()))
|
||||
if (rearmableInfo != null && ammoPools.Any(p => p.Info.Name == info.AmmoPoolName && !p.HasAmmo()))
|
||||
{
|
||||
// Rearm (and possibly repair) at rearm building, then back out here to refill the minefield some more
|
||||
var rearmTarget = self.World.Actors.Where(a => self.Owner.Stances[a.Owner] == Stance.Ally
|
||||
&& rearmBuildings.Contains(a.Info.Name))
|
||||
&& rearmableInfo.RearmActors.Contains(a.Info.Name))
|
||||
.ClosestTo(self);
|
||||
|
||||
if (rearmTarget == null)
|
||||
return new Wait(20);
|
||||
|
||||
// Add a CloseEnough range of 512 to the Repair activity in order to ensure that we're at the host actor
|
||||
// Add a CloseEnough range of 512 to the Rearm/Repair activities in order to ensure that we're at the host actor
|
||||
return ActivityUtils.SequenceActivities(
|
||||
new MoveAdjacentTo(self, Target.FromActor(rearmTarget)),
|
||||
movement.MoveTo(self.World.Map.CellContaining(rearmTarget.CenterPosition), rearmTarget),
|
||||
new Rearm(self),
|
||||
new Rearm(self, rearmTarget, new WDist(512)),
|
||||
new Repair(self, rearmTarget, new WDist(512)),
|
||||
this);
|
||||
}
|
||||
|
||||
@@ -13,70 +13,126 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
class Leap : Activity
|
||||
public class Leap : Activity
|
||||
{
|
||||
readonly Mobile mobile;
|
||||
readonly WeaponInfo weapon;
|
||||
readonly int length;
|
||||
readonly Mobile targetMobile;
|
||||
readonly int speed;
|
||||
readonly AttackLeap attack;
|
||||
readonly EdibleByLeap edible;
|
||||
readonly Target target;
|
||||
|
||||
WPos from;
|
||||
WPos to;
|
||||
int ticks;
|
||||
WAngle angle;
|
||||
BitSet<DamageType> damageTypes;
|
||||
CPos destinationCell;
|
||||
SubCell destinationSubCell = SubCell.Any;
|
||||
WPos destination, origin;
|
||||
int length;
|
||||
bool canceled = false;
|
||||
bool jumpComplete = false;
|
||||
int ticks = 0;
|
||||
WPos targetPosition;
|
||||
|
||||
public Leap(Actor self, Actor target, Armament a, WDist speed, WAngle angle, BitSet<DamageType> damageTypes)
|
||||
public Leap(Actor self, Target target, Mobile mobile, Mobile targetMobile, int speed, AttackLeap attack, EdibleByLeap edible)
|
||||
{
|
||||
var targetMobile = target.TraitOrDefault<Mobile>();
|
||||
if (targetMobile == null)
|
||||
throw new InvalidOperationException("Leap requires a target actor with the Mobile trait");
|
||||
this.mobile = mobile;
|
||||
this.targetMobile = targetMobile;
|
||||
this.attack = attack;
|
||||
this.target = target;
|
||||
this.edible = edible;
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
this.weapon = a.Weapon;
|
||||
this.angle = angle;
|
||||
this.damageTypes = damageTypes;
|
||||
mobile = self.Trait<Mobile>();
|
||||
mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, targetMobile.FromCell, targetMobile.FromSubCell);
|
||||
mobile.IsMoving = true;
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
destinationCell = target.Actor.Location;
|
||||
if (targetMobile != null)
|
||||
destinationSubCell = targetMobile.ToSubCell;
|
||||
|
||||
from = self.CenterPosition;
|
||||
to = self.World.Map.CenterOfSubCell(targetMobile.FromCell, targetMobile.FromSubCell);
|
||||
length = Math.Max((to - from).Length / speed.Length, 1);
|
||||
origin = self.CenterPosition;
|
||||
destination = self.World.Map.CenterOfSubCell(destinationCell, destinationSubCell);
|
||||
length = Math.Max((origin - destination).Length / speed, 1);
|
||||
|
||||
// HACK: why isn't this using the interface?
|
||||
self.Trait<WithInfantryBody>().Attacking(self, Target.FromActor(target), a);
|
||||
// First check if we are still allowed to leap
|
||||
// We need an extra boolean as using Cancel() in OnFirstRun doesn't work
|
||||
canceled = !edible.GetLeapAtBy(self) || target.Type != TargetType.Actor;
|
||||
|
||||
if (weapon.Report != null && weapon.Report.Any())
|
||||
Game.Sound.Play(SoundType.World, weapon.Report.Random(self.World.SharedRandom), self.CenterPosition);
|
||||
IsInterruptible = false;
|
||||
|
||||
if (canceled)
|
||||
return;
|
||||
|
||||
targetPosition = target.CenterPosition;
|
||||
attack.GrantLeapCondition(self);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (ticks == 0 && IsCanceled)
|
||||
if (canceled)
|
||||
return NextActivity;
|
||||
|
||||
mobile.SetVisualPosition(self, WPos.LerpQuadratic(from, to, angle, ++ticks, length));
|
||||
if (ticks >= length)
|
||||
// Correct the visual position after we jumped
|
||||
if (jumpComplete)
|
||||
{
|
||||
if (ChildActivity == null)
|
||||
return NextActivity;
|
||||
|
||||
ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (target.Type != TargetType.Invalid)
|
||||
targetPosition = target.CenterPosition;
|
||||
|
||||
var position = length > 1 ? WPos.Lerp(origin, targetPosition, ticks, length - 1) : targetPosition;
|
||||
mobile.SetVisualPosition(self, position);
|
||||
|
||||
// We are at the destination
|
||||
if (++ticks >= length)
|
||||
{
|
||||
mobile.SetLocation(mobile.ToCell, mobile.ToSubCell, mobile.ToCell, mobile.ToSubCell);
|
||||
mobile.FinishedMoving(self);
|
||||
mobile.IsMoving = false;
|
||||
|
||||
self.World.ActorMap.GetActorsAt(mobile.ToCell, mobile.ToSubCell)
|
||||
.Except(new[] { self }).Where(t => weapon.IsValidAgainst(t, self))
|
||||
.Do(t => t.Kill(self, damageTypes));
|
||||
// Revoke the run condition
|
||||
attack.IsAiming = false;
|
||||
|
||||
return NextActivity;
|
||||
// Move to the correct subcells, if our target actor uses subcells
|
||||
// (This does not update the visual position!)
|
||||
mobile.SetLocation(destinationCell, destinationSubCell, destinationCell, destinationSubCell);
|
||||
|
||||
// Revoke the condition before attacking, as it is usually used to pause the attack trait
|
||||
attack.RevokeLeapCondition(self);
|
||||
attack.DoAttack(self, target);
|
||||
|
||||
jumpComplete = true;
|
||||
QueueChild(mobile.VisualMove(self, position, self.World.Map.CenterOfSubCell(destinationCell, destinationSubCell)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
mobile.IsMoving = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void OnLastRun(Actor self)
|
||||
{
|
||||
attack.RevokeLeapCondition(self);
|
||||
base.OnLastRun(self);
|
||||
}
|
||||
|
||||
protected override void OnActorDispose(Actor self)
|
||||
{
|
||||
attack.RevokeLeapCondition(self);
|
||||
base.OnActorDispose(self);
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
yield return Target.FromPos(ticks < length / 2 ? origin : destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
146
OpenRA.Mods.Cnc/Activities/LeapAttack.cs
Normal file
146
OpenRA.Mods.Cnc/Activities/LeapAttack.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
public class LeapAttack : Activity
|
||||
{
|
||||
readonly AttackLeapInfo info;
|
||||
readonly AttackLeap attack;
|
||||
readonly Mobile mobile;
|
||||
readonly bool allowMovement;
|
||||
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
bool useLastVisibleTarget;
|
||||
WDist lastVisibleMinRange;
|
||||
WDist lastVisibleMaxRange;
|
||||
|
||||
public LeapAttack(Actor self, Target target, bool allowMovement, AttackLeap attack, AttackLeapInfo info)
|
||||
{
|
||||
this.target = target;
|
||||
this.info = info;
|
||||
this.attack = attack;
|
||||
this.allowMovement = allowMovement;
|
||||
mobile = self.Trait<Mobile>();
|
||||
|
||||
// The target may become hidden between the initial order request and the first tick (e.g. if queued)
|
||||
// Moving to any position (even if quite stale) is still better than immediately giving up
|
||||
if ((target.Type == TargetType.Actor && target.Actor.CanBeViewedByPlayer(self.Owner))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
{
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
lastVisibleMinRange = attack.GetMinimumRangeVersusTarget(target);
|
||||
lastVisibleMaxRange = attack.GetMaximumRangeVersusTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
attack.IsAiming = true;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
// Run this even if the target became invalid to avoid visual glitches
|
||||
if (ChildActivity != null)
|
||||
{
|
||||
ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
{
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleMinRange = attack.GetMinimumRangeVersusTarget(target);
|
||||
lastVisibleMaxRange = attack.GetMaximumRangeVersusTarget(target);
|
||||
}
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, Color.Red, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
if (!checkTarget.IsInRange(pos, lastVisibleMaxRange) || checkTarget.IsInRange(pos, lastVisibleMinRange))
|
||||
{
|
||||
if (!allowMovement || lastVisibleMaxRange == WDist.Zero || lastVisibleMaxRange < lastVisibleMinRange)
|
||||
return NextActivity;
|
||||
|
||||
QueueChild(mobile.MoveWithinRange(target, lastVisibleMinRange, lastVisibleMaxRange, checkTarget.CenterPosition, Color.Red));
|
||||
return this;
|
||||
}
|
||||
|
||||
// Ready to leap, but target isn't visible
|
||||
if (targetIsHiddenActor || target.Type != TargetType.Actor)
|
||||
return NextActivity;
|
||||
|
||||
// Target is not valid
|
||||
if (!target.IsValidFor(self) || !attack.HasAnyValidWeapons(target))
|
||||
return NextActivity;
|
||||
|
||||
var edible = target.Actor.TraitOrDefault<EdibleByLeap>();
|
||||
if (edible == null || !edible.CanLeap(self))
|
||||
return NextActivity;
|
||||
|
||||
// Can't leap yet
|
||||
if (attack.Armaments.All(a => a.IsReloading))
|
||||
return this;
|
||||
|
||||
// Use CenterOfSubCell with ToSubCell instead of target.Centerposition
|
||||
// to avoid continuous facing adjustments as the target moves
|
||||
var targetMobile = target.Actor.TraitOrDefault<Mobile>();
|
||||
var targetSubcell = targetMobile != null ? targetMobile.ToSubCell : SubCell.Any;
|
||||
|
||||
var destination = self.World.Map.CenterOfSubCell(target.Actor.Location, targetSubcell);
|
||||
var origin = self.World.Map.CenterOfSubCell(self.Location, mobile.FromSubCell);
|
||||
var desiredFacing = (destination - origin).Yaw.Facing;
|
||||
if (mobile.Facing != desiredFacing)
|
||||
{
|
||||
QueueChild(new Turn(self, desiredFacing));
|
||||
return this;
|
||||
}
|
||||
|
||||
QueueChild(new Leap(self, target, mobile, targetMobile, info.Speed.Length, attack, edible));
|
||||
|
||||
// Re-queue the child activities to kill the target if it didn't die in one go
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void OnLastRun(Actor self)
|
||||
{
|
||||
attack.IsAiming = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,25 @@ using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
public interface IPreventsTeleport { bool PreventsTeleport(Actor self); }
|
||||
|
||||
public class Teleport : Activity
|
||||
{
|
||||
readonly Actor teleporter;
|
||||
readonly int? maximumDistance;
|
||||
readonly bool killOnFailure;
|
||||
readonly BitSet<DamageType> killDamageTypes;
|
||||
CPos destination;
|
||||
bool killCargo;
|
||||
bool screenFlash;
|
||||
string sound;
|
||||
|
||||
public Teleport(Actor teleporter, CPos destination, int? maximumDistance, bool killCargo, bool screenFlash, string sound)
|
||||
public Teleport(Actor teleporter, CPos destination, int? maximumDistance,
|
||||
bool killCargo, bool screenFlash, string sound, bool interruptable = true,
|
||||
bool killOnFailure = false, BitSet<DamageType> killDamageTypes = default(BitSet<DamageType>))
|
||||
{
|
||||
var max = teleporter.World.Map.Grid.MaximumTileSearchRange;
|
||||
if (maximumDistance > max)
|
||||
@@ -42,21 +45,32 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
this.killCargo = killCargo;
|
||||
this.screenFlash = screenFlash;
|
||||
this.sound = sound;
|
||||
this.killOnFailure = killOnFailure;
|
||||
this.killDamageTypes = killDamageTypes;
|
||||
|
||||
if (!interruptable)
|
||||
IsInterruptible = false;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var pc = self.TraitOrDefault<PortableChrono>();
|
||||
if (teleporter == self && pc != null && !pc.CanTeleport)
|
||||
return NextActivity;
|
||||
{
|
||||
if (killOnFailure)
|
||||
self.Kill(teleporter, killDamageTypes);
|
||||
|
||||
foreach (var condition in self.TraitsImplementing<IPreventsTeleport>())
|
||||
if (condition.PreventsTeleport(self))
|
||||
return NextActivity;
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
var bestCell = ChooseBestDestinationCell(self, destination);
|
||||
if (bestCell == null)
|
||||
{
|
||||
if (killOnFailure)
|
||||
self.Kill(teleporter, killDamageTypes);
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
destination = bestCell.Value;
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.LoadScreens;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
@@ -9,12 +9,9 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Effects
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace OpenRA.Mods.Cnc.Effects
|
||||
visibilityModifiers = actor.TraitsImplementing<IVisibilityModifier>().ToArray();
|
||||
|
||||
dotStates = new PlayerDictionary<DotState>(actor.World,
|
||||
p => new DotState(actor, p.PlayerActor.Trait<GpsWatcher>(), p.PlayerActor.TraitOrDefault<FrozenActorLayer>()));
|
||||
p => new DotState(actor, p.PlayerActor.Trait<GpsWatcher>(), p.FrozenActorLayer));
|
||||
}
|
||||
|
||||
bool ShouldRender(DotState state, Player toPlayer)
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -13,7 +13,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Mods.Cnc.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>5</LangVersion>
|
||||
</PropertyGroup>
|
||||
@@ -51,7 +50,6 @@
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@@ -64,9 +62,12 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Activities\LeapAttack.cs" />
|
||||
<Compile Include="CncLoadScreen.cs" />
|
||||
<Compile Include="Traits\EdibleByLeap.cs" />
|
||||
<Compile Include="Traits\Attack\AttackPopupTurreted.cs" />
|
||||
<Compile Include="Traits\Buildings\ProductionAirdrop.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForTransform.cs" />
|
||||
<Compile Include="Traits\Render\WithGunboatBody.cs" />
|
||||
<Compile Include="Traits\Render\WithEmbeddedTurretSpriteBody.cs" />
|
||||
<Compile Include="Traits\Render\WithCargo.cs" />
|
||||
|
||||
@@ -19,7 +19,10 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public class PlaceSimpleBeaconInfo : ITraitInfo
|
||||
{
|
||||
public readonly int Duration = 30 * 25;
|
||||
|
||||
public readonly string NotificationType = "Sounds";
|
||||
|
||||
[NotificationReference(typeFromField: "NotificationType")]
|
||||
public readonly string Notification = "Beacon";
|
||||
|
||||
public readonly bool IsPlayerPalette = false;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Graphics;
|
||||
|
||||
@@ -17,9 +17,9 @@ using OpenRA.Traits;
|
||||
namespace OpenRA.Mods.Cnc.Scripting
|
||||
{
|
||||
[ScriptPropertyGroup("Support Powers")]
|
||||
public class ChronsphereProperties : ScriptActorProperties, Requires<ChronoshiftPowerInfo>
|
||||
public class ChronosphereProperties : ScriptActorProperties, Requires<ChronoshiftPowerInfo>
|
||||
{
|
||||
public ChronsphereProperties(ScriptContext context, Actor self)
|
||||
public ChronosphereProperties(ScriptContext context, Actor self)
|
||||
: base(context, self) { }
|
||||
|
||||
[Desc("Chronoshift a group of actors. A duration of 0 will teleport the actors permanently.")]
|
||||
@@ -37,7 +37,9 @@ namespace OpenRA.Mods.Cnc.Scripting
|
||||
kv.Key.WrappedClrType().Name, kv.Value.WrappedClrType().Name));
|
||||
}
|
||||
|
||||
var cs = actor.TraitOrDefault<Chronoshiftable>();
|
||||
var cs = actor.TraitsImplementing<Chronoshiftable>()
|
||||
.FirstEnabledTraitOrDefault();
|
||||
|
||||
if (cs != null && cs.CanChronoshiftTo(actor, cell))
|
||||
cs.Teleport(actor, cell, duration, killCargo, Self);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
@@ -38,7 +37,7 @@ namespace OpenRA.Mods.Cnc.Scripting
|
||||
if (infiltrates == null)
|
||||
throw new LuaException("{0} tried to infiltrate invalid target {1}!".F(Self, target));
|
||||
|
||||
Self.QueueActivity(new Infiltrate(Self, target, infiltrates));
|
||||
Self.QueueActivity(new Infiltrate(Self, Target.FromActor(target), infiltrates));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,12 @@
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* Information, see COPYING.
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Cnc.Effects;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Traits;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.SpriteLoaders
|
||||
{
|
||||
@@ -182,8 +183,9 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
|
||||
return tiles;
|
||||
}
|
||||
|
||||
public bool TryParseSprite(Stream s, out ISpriteFrame[] frames)
|
||||
public bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata)
|
||||
{
|
||||
metadata = null;
|
||||
if (!IsTmpTS(s))
|
||||
{
|
||||
frames = null;
|
||||
|
||||
@@ -9,53 +9,71 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Dogs use this attack model.")]
|
||||
class AttackLeapInfo : AttackFrontalInfo
|
||||
[Desc("Move onto the target then execute the attack.")]
|
||||
public class AttackLeapInfo : AttackFrontalInfo, Requires<MobileInfo>
|
||||
{
|
||||
[Desc("Leap speed (in units/tick).")]
|
||||
[Desc("Leap speed (in WDist units/tick).")]
|
||||
public readonly WDist Speed = new WDist(426);
|
||||
|
||||
public readonly WAngle Angle = WAngle.FromDegrees(20);
|
||||
|
||||
[Desc("Types of damage that this trait causes. Leave empty for no damage types.")]
|
||||
public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
|
||||
[Desc("Conditions that last from start of the leap until the attack.")]
|
||||
[GrantedConditionReference]
|
||||
public readonly string LeapCondition = "attacking";
|
||||
|
||||
public override object Create(ActorInitializer init) { return new AttackLeap(init.Self, this); }
|
||||
}
|
||||
|
||||
class AttackLeap : AttackFrontal
|
||||
public class AttackLeap : AttackFrontal
|
||||
{
|
||||
readonly AttackLeapInfo info;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int leapToken = ConditionManager.InvalidConditionToken;
|
||||
|
||||
public AttackLeap(Actor self, AttackLeapInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public override void DoAttack(Actor self, Target target, IEnumerable<Armament> armaments = null)
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
if (target.Type != TargetType.Actor || !CanAttack(self, target))
|
||||
return;
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
var a = ChooseArmamentsForTarget(target, true).FirstOrDefault();
|
||||
if (a == null)
|
||||
return;
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (target.Type != TargetType.Actor)
|
||||
return false;
|
||||
|
||||
if (!target.IsInRange(self.CenterPosition, a.MaxRange()))
|
||||
return;
|
||||
if (self.Location == target.Actor.Location && HasAnyValidWeapons(target))
|
||||
return true;
|
||||
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Leap(self, target.Actor, a, info.Speed, info.Angle, info.DamageTypes));
|
||||
return base.CanAttack(self, target);
|
||||
}
|
||||
|
||||
public void GrantLeapCondition(Actor self)
|
||||
{
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.LeapCondition))
|
||||
leapToken = conditionManager.GrantCondition(self, info.LeapCondition);
|
||||
}
|
||||
|
||||
public void RevokeLeapCondition(Actor self)
|
||||
{
|
||||
if (leapToken != ConditionManager.InvalidConditionToken)
|
||||
leapToken = conditionManager.RevokeCondition(self, leapToken);
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack)
|
||||
{
|
||||
return new LeapAttack(self, newTarget, allowMove, this, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public override object Create(ActorInitializer init) { return new AttackPopupTurreted(init, this); }
|
||||
}
|
||||
|
||||
class AttackPopupTurreted : AttackTurreted, INotifyBuildComplete, INotifyIdle, IDamageModifier
|
||||
class AttackPopupTurreted : AttackTurreted, INotifyIdle, IDamageModifier
|
||||
{
|
||||
enum PopupState { Open, Rotating, Transitioning, Closed }
|
||||
|
||||
@@ -64,9 +64,22 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
skippedMakeAnimation = init.Contains<SkipMakeAnimsInit>();
|
||||
}
|
||||
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
base.Created(self);
|
||||
|
||||
// Map placed actors are created in the closed state
|
||||
if (skippedMakeAnimation)
|
||||
{
|
||||
state = PopupState.Closed;
|
||||
wsb.PlayCustomAnimationRepeating(self, info.ClosedIdleSequence);
|
||||
turret.DesiredFacing = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (state == PopupState.Transitioning || !building.BuildComplete)
|
||||
if (state == PopupState.Transitioning)
|
||||
return false;
|
||||
|
||||
if (!base.CanAttack(self, target))
|
||||
@@ -106,16 +119,6 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyBuildComplete.BuildingComplete(Actor self)
|
||||
{
|
||||
if (skippedMakeAnimation)
|
||||
{
|
||||
state = PopupState.Closed;
|
||||
wsb.PlayCustomAnimationRepeating(self, info.ClosedIdleSequence);
|
||||
turret.DesiredFacing = null;
|
||||
}
|
||||
}
|
||||
|
||||
int IDamageModifier.GetDamageModifier(Actor attacker, Damage damage)
|
||||
{
|
||||
return state == PopupState.Closed ? info.ClosedDamageMultiplier : 100;
|
||||
|
||||
@@ -59,10 +59,10 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
// Check that AttackTDGunboatTurreted hasn't cancelled the target by modifying attack.Target
|
||||
// Having both this and AttackTDGunboatTurreted modify that field is a horrible hack.
|
||||
if (hasTicked && attack.Target.Type == TargetType.Invalid)
|
||||
if (hasTicked && attack.requestedTarget.Type == TargetType.Invalid)
|
||||
return NextActivity;
|
||||
|
||||
attack.Target = target;
|
||||
attack.requestedTarget = target;
|
||||
hasTicked = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("Uses the \"Cloneable\" trait to determine whether or not we should clone a produced unit.")]
|
||||
public readonly HashSet<string> CloneableTypes = new HashSet<string>();
|
||||
public readonly BitSet<CloneableType> CloneableTypes = default(BitSet<CloneableType>);
|
||||
|
||||
public object Create(ActorInitializer init) { return new ClonesProducedUnits(init, this); }
|
||||
}
|
||||
@@ -30,17 +30,15 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public class ClonesProducedUnits : INotifyOtherProduction
|
||||
{
|
||||
readonly ClonesProducedUnitsInfo info;
|
||||
readonly Production production;
|
||||
readonly string faction;
|
||||
readonly Production[] productionTraits;
|
||||
|
||||
public ClonesProducedUnits(ActorInitializer init, ClonesProducedUnitsInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
production = init.Self.Trait<Production>();
|
||||
faction = init.Contains<FactionInit>() ? init.Get<FactionInit, string>() : init.Self.Owner.Faction.InternalName;
|
||||
productionTraits = init.Self.TraitsImplementing<Production>().ToArray();
|
||||
}
|
||||
|
||||
public void UnitProducedByOther(Actor self, Actor producer, Actor produced, string productionType)
|
||||
public void UnitProducedByOther(Actor self, Actor producer, Actor produced, string productionType, TypeDictionary init)
|
||||
{
|
||||
// No recursive cloning!
|
||||
if (producer.Owner != self.Owner || producer.Info.HasTraitInfo<ClonesProducedUnitsInfo>())
|
||||
@@ -50,13 +48,23 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
if (ci == null || !info.CloneableTypes.Overlaps(ci.Types))
|
||||
return;
|
||||
|
||||
var inits = new TypeDictionary
|
||||
{
|
||||
new OwnerInit(self.Owner),
|
||||
new FactionInit(BuildableInfo.GetInitialFaction(produced.Info, faction))
|
||||
};
|
||||
var factionInit = init.GetOrDefault<FactionInit>();
|
||||
|
||||
production.Produce(self, produced.Info, productionType, inits);
|
||||
// Stop as soon as one production trait successfully produced
|
||||
foreach (var p in productionTraits)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(productionType) && !p.Info.Produces.Contains(productionType))
|
||||
continue;
|
||||
|
||||
var inits = new TypeDictionary
|
||||
{
|
||||
new OwnerInit(self.Owner),
|
||||
factionInit ?? new FactionInit(BuildableInfo.GetInitialFaction(produced.Info, p.Faction))
|
||||
};
|
||||
|
||||
if (p.Produce(self, produced.Info, productionType, inits))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common;
|
||||
@@ -23,7 +22,9 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("Deliver the unit in production via skylift.")]
|
||||
public class ProductionAirdropInfo : ProductionInfo
|
||||
{
|
||||
[NotificationReference("Speech")]
|
||||
public readonly string ReadyAudio = "Reinforce";
|
||||
|
||||
[Desc("Cargo aircraft used for delivery. Must have the `Aircraft` trait.")]
|
||||
[ActorReference(typeof(AircraftInfo))] public readonly string ActorType = "c17";
|
||||
|
||||
@@ -32,18 +33,15 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
class ProductionAirdrop : Production
|
||||
{
|
||||
readonly ProductionAirdropInfo info;
|
||||
public ProductionAirdrop(ActorInitializer init, ProductionAirdropInfo info)
|
||||
: base(init, info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
: base(init, info) { }
|
||||
|
||||
public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits)
|
||||
{
|
||||
if (IsTraitDisabled || IsTraitPaused)
|
||||
return false;
|
||||
|
||||
var info = (ProductionAirdropInfo)Info;
|
||||
var owner = self.Owner;
|
||||
var aircraftInfo = self.World.Map.Rules.Actors[info.ActorType].TraitInfo<AircraftInfo>();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
@@ -18,12 +19,13 @@ using OpenRA.Traits;
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Can be teleported via Chronoshift power.")]
|
||||
public class ChronoshiftableInfo : ITraitInfo
|
||||
public class ChronoshiftableInfo : ConditionalTraitInfo
|
||||
{
|
||||
[Desc("Should the actor die instead of being teleported?")]
|
||||
public readonly bool ExplodeInstead = false;
|
||||
|
||||
[Desc("Types of damage that this trait causes to self when 'ExplodeInstead' is true. Leave empty for no damage types.")]
|
||||
[Desc("Types of damage that this trait causes to self when 'ExplodeInstead' is true",
|
||||
"or the return-to-origin is blocked. Leave empty for no damage types.")]
|
||||
public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
|
||||
|
||||
public readonly string ChronoshiftSound = "chrono2.aud";
|
||||
@@ -34,12 +36,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("The color the bar of the 'return-to-origin' logic has.")]
|
||||
public readonly Color TimeBarColor = Color.White;
|
||||
|
||||
public object Create(ActorInitializer init) { return new Chronoshiftable(init, this); }
|
||||
public override object Create(ActorInitializer init) { return new Chronoshiftable(init, this); }
|
||||
}
|
||||
|
||||
public class Chronoshiftable : ITick, ISync, ISelectionBar, IDeathActorInitModifier, ITransformActorInitModifier, INotifyCreated
|
||||
public class Chronoshiftable : ConditionalTrait<ChronoshiftableInfo>, ITick, ISync, ISelectionBar,
|
||||
IDeathActorInitModifier, ITransformActorInitModifier, INotifyCreated
|
||||
{
|
||||
readonly ChronoshiftableInfo info;
|
||||
readonly Actor self;
|
||||
Actor chronosphere;
|
||||
bool killCargo;
|
||||
@@ -51,8 +53,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Sync] public int ReturnTicks = 0;
|
||||
|
||||
public Chronoshiftable(ActorInitializer init, ChronoshiftableInfo info)
|
||||
: base(info)
|
||||
{
|
||||
this.info = info;
|
||||
self = init.Self;
|
||||
|
||||
if (init.Contains<ChronoshiftReturnInit>())
|
||||
@@ -70,14 +72,29 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
if (!info.ReturnToOrigin || ReturnTicks <= 0)
|
||||
if (IsTraitDisabled || !Info.ReturnToOrigin || ReturnTicks <= 0)
|
||||
return;
|
||||
|
||||
// Return to original location
|
||||
if (--ReturnTicks == 0)
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Teleport(chronosphere, Origin, null, killCargo, true, info.ChronoshiftSound));
|
||||
|
||||
// The Move activity is not immediately cancelled, which, combined
|
||||
// with Activity.Cancel discarding NextActivity without checking the
|
||||
// IsInterruptable flag, means that a well timed order can cancel the
|
||||
// Teleport activity queued below - an exploit / cheat of the return mechanic.
|
||||
// The Teleport activity queued below is guaranteed to either complete
|
||||
// (force-resetting the actor to the middle of the target cell) or kill
|
||||
// the actor. It is therefore safe to force-erase the Move activity to
|
||||
// work around the cancellation bug.
|
||||
// HACK: this is manipulating private internal actor state
|
||||
if (self.CurrentActivity is Move)
|
||||
typeof(Actor).GetProperty("CurrentActivity").SetValue(self, null);
|
||||
|
||||
// The actor is killed using Info.DamageTypes if the teleport fails
|
||||
self.QueueActivity(new Teleport(chronosphere, Origin, null, true, killCargo, Info.ChronoshiftSound,
|
||||
false, true, Info.DamageTypes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,19 +107,22 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public virtual bool CanChronoshiftTo(Actor self, CPos targetLocation)
|
||||
{
|
||||
// TODO: Allow enemy units to be chronoshifted into bad terrain to kill them
|
||||
return iPositionable != null && iPositionable.CanEnterCell(targetLocation);
|
||||
return !IsTraitDisabled && iPositionable != null && iPositionable.CanEnterCell(targetLocation);
|
||||
}
|
||||
|
||||
public virtual bool Teleport(Actor self, CPos targetLocation, int duration, bool killCargo, Actor chronosphere)
|
||||
{
|
||||
if (IsTraitDisabled)
|
||||
return false;
|
||||
|
||||
// Some things appear chronoshiftable, but instead they just die.
|
||||
if (info.ExplodeInstead)
|
||||
if (Info.ExplodeInstead)
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
// Damage is inflicted by the chronosphere
|
||||
if (!self.Disposed)
|
||||
self.Kill(chronosphere, info.DamageTypes);
|
||||
self.Kill(chronosphere, Info.DamageTypes);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -122,7 +142,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
// Set up the teleport
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Teleport(chronosphere, targetLocation, null, killCargo, true, info.ChronoshiftSound));
|
||||
self.QueueActivity(new Teleport(chronosphere, targetLocation, null, killCargo, true, Info.ChronoshiftSound));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -130,7 +150,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
// Show the remaining time as a bar
|
||||
float ISelectionBar.GetValue()
|
||||
{
|
||||
if (!info.ReturnToOrigin)
|
||||
if (IsTraitDisabled || !Info.ReturnToOrigin)
|
||||
return 0f;
|
||||
|
||||
// Otherwise an empty bar is rendered all the time
|
||||
@@ -140,12 +160,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
return (float)ReturnTicks / duration;
|
||||
}
|
||||
|
||||
Color ISelectionBar.GetColor() { return info.TimeBarColor; }
|
||||
Color ISelectionBar.GetColor() { return Info.TimeBarColor; }
|
||||
bool ISelectionBar.DisplayWhenEmpty { get { return false; } }
|
||||
|
||||
void ModifyActorInit(TypeDictionary init)
|
||||
{
|
||||
if (!info.ReturnToOrigin || ReturnTicks <= 0)
|
||||
if (IsTraitDisabled || !Info.ReturnToOrigin || ReturnTicks <= 0)
|
||||
return;
|
||||
|
||||
init.Add(new ChronoshiftOriginInit(Origin));
|
||||
|
||||
@@ -9,17 +9,20 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
// Type tag for CloneableTypes
|
||||
public class CloneableType { }
|
||||
|
||||
[Desc("Actors with the \"ClonesProducedUnits\" trait will produce a free duplicate of me.")]
|
||||
public class CloneableInfo : TraitInfo<Cloneable>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("This unit's cloneable type is:")]
|
||||
public readonly HashSet<string> Types = new HashSet<string>();
|
||||
public readonly BitSet<CloneableType> Types = default(BitSet<CloneableType>);
|
||||
}
|
||||
|
||||
public class Cloneable { }
|
||||
|
||||
@@ -12,19 +12,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Implements the special case handling for the Chronoshiftable return on a construction yard.",
|
||||
"Actors that are actively (un)deploying will be returned to the origin as the original actor.",
|
||||
"If ReturnOriginalActorOnCondition evaluates true and the actor is not being sold then OriginalActor will be returned to the origin.",
|
||||
"Otherwise, a vortex animation is played and damage is dealt each tick, ignoring modifiers.")]
|
||||
public class ConyardChronoReturnInfo : ITraitInfo, Requires<HealthInfo>, Requires<WithSpriteBodyInfo>
|
||||
public class ConyardChronoReturnInfo : IObservesVariablesInfo, Requires<HealthInfo>, Requires<WithSpriteBodyInfo>
|
||||
{
|
||||
[Desc("Sequence name with the baked-in vortex animation"), SequenceReference]
|
||||
public readonly string Sequence = "pdox";
|
||||
@@ -42,7 +42,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("Apply the damage using these damagetypes.")]
|
||||
public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
|
||||
|
||||
[Desc("Actor to transform into when the timer expires during (un)deploy."), ActorReference]
|
||||
[ConsumedConditionReference]
|
||||
[Desc("Boolean expression defining the condition under which to teleport a replacement actor instead of triggering the vortex.")]
|
||||
public readonly BooleanExpression ReturnOriginalActorOnCondition = null;
|
||||
|
||||
[ActorReference(typeof(MobileInfo))]
|
||||
[Desc("Replacement actor to create when ReturnOriginalActorOnCondition evaluates true.")]
|
||||
public readonly string OriginalActor = "mcv";
|
||||
|
||||
[Desc("Facing of the returned actor.")]
|
||||
@@ -56,8 +61,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public object Create(ActorInitializer init) { return new ConyardChronoReturn(init, this); }
|
||||
}
|
||||
|
||||
public class ConyardChronoReturn : INotifyCreated, ITick, ISync, ISelectionBar, IDeathActorInitModifier,
|
||||
ITransformActorInitModifier, INotifyBuildComplete, INotifySold, INotifyTransform
|
||||
public class ConyardChronoReturn : INotifyCreated, ITick, ISync, IObservesVariables, ISelectionBar, INotifySold,
|
||||
IDeathActorInitModifier, ITransformActorInitModifier
|
||||
{
|
||||
readonly ConyardChronoReturnInfo info;
|
||||
readonly WithSpriteBody wsb;
|
||||
@@ -70,7 +75,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
Actor chronosphere;
|
||||
int duration;
|
||||
bool buildComplete;
|
||||
bool returnOriginal;
|
||||
bool selling;
|
||||
|
||||
[Sync]
|
||||
@@ -110,6 +115,17 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
}
|
||||
|
||||
IEnumerable<VariableObserver> IObservesVariables.GetVariableObservers()
|
||||
{
|
||||
if (info.ReturnOriginalActorOnCondition != null)
|
||||
yield return new VariableObserver(ReplacementConditionChanged, info.ReturnOriginalActorOnCondition.Variables);
|
||||
}
|
||||
|
||||
void ReplacementConditionChanged(Actor self, IReadOnlyDictionary<string, int> conditions)
|
||||
{
|
||||
returnOriginal = info.ReturnOriginalActorOnCondition.Evaluate(conditions);
|
||||
}
|
||||
|
||||
void TriggerVortex()
|
||||
{
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.Condition) && conditionToken == ConditionManager.InvalidConditionToken)
|
||||
@@ -177,7 +193,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
nt.AfterTransform(a);
|
||||
|
||||
if (selected)
|
||||
self.World.Selection.Add(self.World, a);
|
||||
self.World.Selection.Add(a);
|
||||
|
||||
if (controlgroup.HasValue)
|
||||
self.World.Selection.AddToControlGroup(a, controlgroup.Value);
|
||||
@@ -188,13 +204,16 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
if (self.WillDispose)
|
||||
return;
|
||||
|
||||
if (triggered)
|
||||
health.InflictDamage(self, chronosphere, new Damage(info.Damage, info.DamageTypes), true);
|
||||
|
||||
if (returnTicks <= 0 || --returnTicks > 0)
|
||||
return;
|
||||
|
||||
if (!buildComplete && !selling)
|
||||
if (returnOriginal && !selling)
|
||||
ReturnToOrigin();
|
||||
else
|
||||
TriggerVortex();
|
||||
@@ -228,26 +247,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
void IDeathActorInitModifier.ModifyDeathActorInit(Actor self, TypeDictionary init) { ModifyActorInit(init); }
|
||||
void ITransformActorInitModifier.ModifyTransformActorInit(Actor self, TypeDictionary init) { ModifyActorInit(init); }
|
||||
|
||||
void INotifyBuildComplete.BuildingComplete(Actor self)
|
||||
{
|
||||
buildComplete = true;
|
||||
}
|
||||
|
||||
void INotifySold.Sold(Actor self) { }
|
||||
void INotifySold.Selling(Actor self)
|
||||
{
|
||||
buildComplete = false;
|
||||
selling = true;
|
||||
}
|
||||
|
||||
void INotifyTransform.BeforeTransform(Actor self)
|
||||
{
|
||||
buildComplete = false;
|
||||
}
|
||||
|
||||
void INotifyTransform.OnTransform(Actor self) { }
|
||||
void INotifyTransform.AfterTransform(Actor self) { }
|
||||
|
||||
// Show the remaining time as a bar
|
||||
float ISelectionBar.GetValue()
|
||||
{
|
||||
|
||||
@@ -15,7 +15,6 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
|
||||
37
OpenRA.Mods.Cnc/Traits/EdibleByLeap.cs
Normal file
37
OpenRA.Mods.Cnc/Traits/EdibleByLeap.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Allows this actor to be the target of an attack leap.")]
|
||||
public class EdibleByLeapInfo : TraitInfo<EdibleByLeap> { }
|
||||
|
||||
public class EdibleByLeap
|
||||
{
|
||||
Actor leaper;
|
||||
|
||||
public bool CanLeap(Actor targeter)
|
||||
{
|
||||
return leaper == null || leaper.IsDead || leaper == targeter;
|
||||
}
|
||||
|
||||
public bool GetLeapAtBy(Actor targeter)
|
||||
{
|
||||
if (leaper != null && !leaper.IsDead && leaper != targeter)
|
||||
return false;
|
||||
|
||||
leaper = targeter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,9 @@
|
||||
* Copyright 2007-2018 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.
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
static readonly FrozenActorAction Remove = (fufubg, fal, gps, fa) =>
|
||||
{
|
||||
// Removes the frozen actor. Once done, we no longer need to track GPS updates.
|
||||
fa.Invalidate();
|
||||
fal.Remove(fa);
|
||||
gps.UnregisterForOnGpsRefreshed(fufubg.self, fufubg);
|
||||
};
|
||||
@@ -49,7 +50,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public readonly GpsWatcher GpsWatcher;
|
||||
public Traits(Player player, FrozenUnderFogUpdatedByGps frozenUnderFogUpdatedByGps)
|
||||
{
|
||||
FrozenActorLayer = player.PlayerActor.TraitOrDefault<FrozenActorLayer>();
|
||||
FrozenActorLayer = player.FrozenActorLayer;
|
||||
GpsWatcher = player.PlayerActor.TraitOrDefault<GpsWatcher>();
|
||||
GpsWatcher.RegisterForOnGpsRefreshed(frozenUnderFogUpdatedByGps.self, frozenUnderFogUpdatedByGps);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.Effects;
|
||||
using OpenRA.Traits;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
@@ -18,7 +17,7 @@ using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("This structure can be infiltrated causing funds to be stolen.")]
|
||||
[Desc("Funds are transferred from the owner to the infiltrator.")]
|
||||
class InfiltrateForCashInfo : ITraitInfo
|
||||
{
|
||||
public readonly BitSet<TargetableType> Types;
|
||||
@@ -33,6 +32,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("Maximum amount of funds which will be stolen.")]
|
||||
public readonly int Maximum = int.MaxValue;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the victim will hear when they get robbed.")]
|
||||
public readonly string Notification = null;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Transform into a different actor type.")]
|
||||
class InfiltrateForTransformInfo : ITraitInfo
|
||||
{
|
||||
[ActorReference, FieldLoader.Require]
|
||||
public readonly string IntoActor = null;
|
||||
|
||||
public readonly int ForceHealthPercentage = 0;
|
||||
|
||||
public readonly bool SkipMakeAnims = true;
|
||||
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForTransform(init, this); }
|
||||
}
|
||||
|
||||
class InfiltrateForTransform : INotifyInfiltrated
|
||||
{
|
||||
readonly InfiltrateForTransformInfo info;
|
||||
readonly string faction;
|
||||
|
||||
public InfiltrateForTransform(ActorInitializer init, InfiltrateForTransformInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
faction = init.Contains<FactionInit>() ? init.Get<FactionInit, string>() : init.Self.Owner.Faction.InternalName;
|
||||
}
|
||||
|
||||
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet<TargetableType> types)
|
||||
{
|
||||
if (!info.Types.Overlaps(types))
|
||||
return;
|
||||
|
||||
var transform = new Transform(self, info.IntoActor)
|
||||
{
|
||||
ForceHealthPercentage = info.ForceHealthPercentage,
|
||||
Faction = faction,
|
||||
SkipMakeAnims = info.SkipMakeAnims
|
||||
};
|
||||
|
||||
var facing = self.TraitOrDefault<IFacing>();
|
||||
if (facing != null)
|
||||
transform.Facing = facing.Facing;
|
||||
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
@@ -24,23 +23,27 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
public class InfiltratesInfo : ConditionalTraitInfo
|
||||
{
|
||||
public readonly BitSet<TargetableType> Types;
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
[VoiceReference] public readonly string Voice = "Action";
|
||||
[VoiceReference]
|
||||
public readonly string Voice = "Action";
|
||||
|
||||
[Desc("What diplomatic stances can be infiltrated by this actor.")]
|
||||
public readonly Stance ValidStances = Stance.Neutral | Stance.Enemy;
|
||||
|
||||
[Desc("Behaviour when entering the structure.",
|
||||
[Desc("Behaviour when entering the target.",
|
||||
"Possible values are Exit, Suicide, Dispose.")]
|
||||
public readonly EnterBehaviour EnterBehaviour = EnterBehaviour.Dispose;
|
||||
|
||||
[Desc("Notification to play when a building is infiltrated.")]
|
||||
public readonly string Notification = "BuildingInfiltrated";
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Notification to play when a target is infiltrated.")]
|
||||
public readonly string Notification = null;
|
||||
|
||||
[Desc("Experience to grant to the infiltrating player.")]
|
||||
public readonly int PlayerExperience = 0;
|
||||
|
||||
public readonly string EnterCursor = "enter";
|
||||
|
||||
public override object Create(ActorInitializer init) { return new Infiltrates(this); }
|
||||
}
|
||||
|
||||
@@ -89,21 +92,34 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
? Info.Voice : null;
|
||||
}
|
||||
|
||||
public bool CanInfiltrateTarget(Actor self, Target target)
|
||||
{
|
||||
switch (target.Type)
|
||||
{
|
||||
case TargetType.Actor:
|
||||
return Info.Types.Overlaps(target.Actor.GetEnabledTargetTypes()) &&
|
||||
Info.ValidStances.HasStance(self.Owner.Stances[target.Actor.Owner]);
|
||||
case TargetType.FrozenActor:
|
||||
return target.FrozenActor.IsValid && Info.Types.Overlaps(target.FrozenActor.TargetTypes) &&
|
||||
Info.ValidStances.HasStance(self.Owner.Stances[target.FrozenActor.Owner]);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString != "Infiltrate" || !IsValidOrder(self, order) || IsTraitDisabled)
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor
|
||||
|| !Info.Types.Overlaps(target.Actor.GetAllTargetTypes()))
|
||||
if (!CanInfiltrateTarget(self, order.Target))
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
self.QueueActivity(new Infiltrate(self, target.Actor, this));
|
||||
self.SetTargetLine(order.Target, Color.Red);
|
||||
self.QueueActivity(new Infiltrate(self, order.Target, this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +128,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
readonly InfiltratesInfo info;
|
||||
|
||||
public InfiltrationOrderTargeter(InfiltratesInfo info)
|
||||
: base("Infiltrate", 7, "enter", true, false)
|
||||
: base("Infiltrate", 7, info.EnterCursor, true, true)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Cnc.Activities;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
@@ -50,6 +49,10 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
[VoiceReference] public readonly string Voice = "Action";
|
||||
|
||||
[GrantedConditionReference]
|
||||
[Desc("The condition to grant to self while deployed.")]
|
||||
public readonly string DeployedCondition = null;
|
||||
|
||||
public WeaponInfo ThumpDamageWeaponInfo { get; private set; }
|
||||
public WeaponInfo DetonationWeaponInfo { get; private set; }
|
||||
|
||||
@@ -75,12 +78,13 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
}
|
||||
}
|
||||
|
||||
class MadTank : IIssueOrder, IResolveOrder, IOrderVoice, ITick, IPreventsTeleport, IIssueDeployOrder
|
||||
class MadTank : INotifyCreated, IIssueOrder, IResolveOrder, IOrderVoice, ITick, IIssueDeployOrder
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly MadTankInfo info;
|
||||
readonly WithFacingSpriteBody wfsb;
|
||||
readonly ScreenShaker screenShaker;
|
||||
ConditionManager conditionManager;
|
||||
bool deployed;
|
||||
int tick;
|
||||
|
||||
@@ -92,6 +96,11 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
screenShaker = self.World.WorldActor.Trait<ScreenShaker>();
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
if (!deployed)
|
||||
@@ -127,9 +136,9 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
return new Order(order.OrderID, self, target, queued);
|
||||
}
|
||||
|
||||
Order IIssueDeployOrder.IssueDeployOrder(Actor self)
|
||||
Order IIssueDeployOrder.IssueDeployOrder(Actor self, bool queued)
|
||||
{
|
||||
return new Order("Detonate", self, false);
|
||||
return new Order("Detonate", self, queued);
|
||||
}
|
||||
|
||||
bool IIssueDeployOrder.CanIssueDeployOrder(Actor self) { return true; }
|
||||
@@ -165,13 +174,14 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
driverMobile.Nudge(driver, driver, true);
|
||||
}
|
||||
|
||||
public bool PreventsTeleport(Actor self) { return deployed; }
|
||||
|
||||
void StartDetonationSequence()
|
||||
{
|
||||
if (deployed)
|
||||
return;
|
||||
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.DeployedCondition))
|
||||
conditionManager.GrantCondition(self, info.DeployedCondition);
|
||||
|
||||
self.World.AddFrameEndTask(w => EjectDriver());
|
||||
if (info.ThumpSequence != null)
|
||||
wfsb.PlayCustomAnimationRepeating(self, info.ThumpSequence);
|
||||
@@ -187,20 +197,18 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
if (order.OrderString == "DetonateAttack")
|
||||
{
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
self.QueueActivity(new MoveAdjacentTo(self, target));
|
||||
self.SetTargetLine(order.Target, Color.Red);
|
||||
self.QueueActivity(new MoveAdjacentTo(self, order.Target, targetLineColor: Color.Red));
|
||||
self.QueueActivity(new CallFunc(StartDetonationSequence));
|
||||
}
|
||||
else if (order.OrderString == "Detonate")
|
||||
{
|
||||
self.CancelActivity();
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.QueueActivity(new CallFunc(StartDetonationSequence));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user