mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
1035 Commits
5.1.0
...
develop-7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8e41c0bc1 | ||
|
|
9bd99f9cde | ||
|
|
d2cee713d8 | ||
|
|
1e0ef288be | ||
|
|
12a0186246 | ||
|
|
2f1a9d3dfd | ||
|
|
7c690227d6 | ||
|
|
8172461db4 | ||
|
|
87ed02deb0 | ||
|
|
59676fb0fe | ||
|
|
7cfd6381fb | ||
|
|
9f7aa7b75b | ||
|
|
5f5422b579 | ||
|
|
16ad43f260 | ||
|
|
9cd1a06651 | ||
|
|
2bf1ba6d12 | ||
|
|
e705a2ddbf | ||
|
|
60dd8f3245 | ||
|
|
0ecc53ef27 | ||
|
|
2f715a1427 | ||
|
|
e28ffe97ac | ||
|
|
54bd4189bd | ||
|
|
a762970469 | ||
|
|
c5ebde3cd3 | ||
|
|
38ce82f3f9 | ||
|
|
16ef182a8d | ||
|
|
8ac582b9a8 | ||
|
|
11d77c0fe6 | ||
|
|
c1eab6715b | ||
|
|
4be77472e7 | ||
|
|
af65254db5 | ||
|
|
b523b03e3c | ||
|
|
aae53ffc63 | ||
|
|
c6916d8da2 | ||
|
|
8ae64f57b5 | ||
|
|
b132543b7e | ||
|
|
1836b0bd98 | ||
|
|
29a05fa0e7 | ||
|
|
da1f84a8f5 | ||
|
|
5d22d4f43c | ||
|
|
6adde1cb3e | ||
|
|
81a00981eb | ||
|
|
ed17fc97d9 | ||
|
|
75ee0de23f | ||
|
|
a4d7c66621 | ||
|
|
998864797d | ||
|
|
5cca4eb051 | ||
|
|
d5c335c698 | ||
|
|
7fea15ee07 | ||
|
|
a31296cbc0 | ||
|
|
fe60757891 | ||
|
|
7614017828 | ||
|
|
61879aeafa | ||
|
|
f5b9052257 | ||
|
|
b27d2be0d1 | ||
|
|
e1a1eca5da | ||
|
|
c695db0f98 | ||
|
|
ff20f16109 | ||
|
|
8af3dc6965 | ||
|
|
e6cf253e45 | ||
|
|
8dc3fca290 | ||
|
|
0d18b57074 | ||
|
|
b745e7c8ec | ||
|
|
800ca598ef | ||
|
|
212813e31d | ||
|
|
571fe21d57 | ||
|
|
cd952312b7 | ||
|
|
23982fdac6 | ||
|
|
8724b458a8 | ||
|
|
ef64899701 | ||
|
|
51e4dc5c22 | ||
|
|
ac5edeb936 | ||
|
|
f7364d04ed | ||
|
|
483bf73213 | ||
|
|
0b87f90d4f | ||
|
|
202fe1109b | ||
|
|
5a8863b07e | ||
|
|
889e1f5da2 | ||
|
|
418d78f99b | ||
|
|
a4b5bc198c | ||
|
|
fcb2f21d36 | ||
|
|
15abf3d6fe | ||
|
|
a8b7aca4fb | ||
|
|
799dbfa99c | ||
|
|
eb31a0f038 | ||
|
|
78e2da9196 | ||
|
|
ca96a232f0 | ||
|
|
03671bbfb0 | ||
|
|
97c4168d13 | ||
|
|
e0edfa68b1 | ||
|
|
97c238a4bb | ||
|
|
a6c5b03212 | ||
|
|
5b148a74aa | ||
|
|
33242048e7 | ||
|
|
4f7932ad8a | ||
|
|
8158cc7112 | ||
|
|
50d7a3e46f | ||
|
|
b6b9a22668 | ||
|
|
bd2ec12e0f | ||
|
|
752a90cddb | ||
|
|
004594639e | ||
|
|
6943adf6df | ||
|
|
5ad57d2878 | ||
|
|
acb2d01d92 | ||
|
|
15fa73a514 | ||
|
|
7f94b34277 | ||
|
|
4d2ed64a70 | ||
|
|
e444761059 | ||
|
|
c9e4b5eba4 | ||
|
|
c14c7cc73d | ||
|
|
5e459c2390 | ||
|
|
4ee6de2c84 | ||
|
|
a7d2eca209 | ||
|
|
57a4a7c282 | ||
|
|
b4c5236d8b | ||
|
|
de2a779715 | ||
|
|
352c2ddc56 | ||
|
|
76e6aff9d7 | ||
|
|
b4c02767ac | ||
|
|
aa2a888ed0 | ||
|
|
b50d82feff | ||
|
|
cce9592ac8 | ||
|
|
b69def652e | ||
|
|
6df83485e1 | ||
|
|
e297fcbff8 | ||
|
|
2f2ebff674 | ||
|
|
06b74c4ca6 | ||
|
|
7ddfc5e5ad | ||
|
|
f86709ebd0 | ||
|
|
7266de42f5 | ||
|
|
47831d43b5 | ||
|
|
cf47dd82d7 | ||
|
|
081c97583b | ||
|
|
77f1706cbb | ||
|
|
664efa3ace | ||
|
|
b6059f692e | ||
|
|
4bf8f25d31 | ||
|
|
e2c6d6788c | ||
|
|
1c20bc3966 | ||
|
|
c31d76ec83 | ||
|
|
ada8f9f8ee | ||
|
|
1b72598803 | ||
|
|
4bc8bf52e7 | ||
|
|
0d257a2c3b | ||
|
|
0ba602d3aa | ||
|
|
38d18ba767 | ||
|
|
94b8aa7213 | ||
|
|
1ac36d03da | ||
|
|
dca256364a | ||
|
|
652e7d8d7b | ||
|
|
42e9a6294b | ||
|
|
f3d2952579 | ||
|
|
ee1d4cd3e4 | ||
|
|
4e067eaaa8 | ||
|
|
4a0975b21b | ||
|
|
dd9ad2f0be | ||
|
|
d1fe267072 | ||
|
|
28ba100864 | ||
|
|
99ea06c0d5 | ||
|
|
fa070b2126 | ||
|
|
5405632434 | ||
|
|
ca2cec5d7d | ||
|
|
8150176fc6 | ||
|
|
5c72e0754a | ||
|
|
af2e195f90 | ||
|
|
8523c11357 | ||
|
|
b860d72c2d | ||
|
|
bcb7d059c0 | ||
|
|
bc0dacf974 | ||
|
|
2d9b0177ec | ||
|
|
2fed84c676 | ||
|
|
a73c06223d | ||
|
|
e129c02109 | ||
|
|
67dae80b76 | ||
|
|
088b69a6ef | ||
|
|
db64f37a95 | ||
|
|
af6c42f49b | ||
|
|
4503170075 | ||
|
|
c07f343d0e | ||
|
|
725f6c55a5 | ||
|
|
364742a98b | ||
|
|
7b35a749e2 | ||
|
|
23ac510d26 | ||
|
|
ba10298dbc | ||
|
|
c6a8aeac1d | ||
|
|
b41ef0ab13 | ||
|
|
c33a0d5deb | ||
|
|
70b9d4c174 | ||
|
|
1462bedd97 | ||
|
|
937bacacb7 | ||
|
|
e8824e5d03 | ||
|
|
e030cfebb8 | ||
|
|
eea98b01f6 | ||
|
|
f8c1d291ed | ||
|
|
0df268f331 | ||
|
|
be2ad226f4 | ||
|
|
a4df23361e | ||
|
|
0f34946127 | ||
|
|
575470ae1b | ||
|
|
31c59e7380 | ||
|
|
4b542b70ae | ||
|
|
9cb641bddf | ||
|
|
7a8b3591cd | ||
|
|
fd2ba6b2b2 | ||
|
|
ac6cc39616 | ||
|
|
0be9e1985a | ||
|
|
a1af4e563a | ||
|
|
7167e84a8f | ||
|
|
7eeb84de47 | ||
|
|
45aabf369b | ||
|
|
e396a21791 | ||
|
|
c6635a4885 | ||
|
|
306971230b | ||
|
|
e0c13846a7 | ||
|
|
4aa42e6573 | ||
|
|
24a04fb8c6 | ||
|
|
321de8d4ec | ||
|
|
5db82f733f | ||
|
|
d65fbf299c | ||
|
|
7c2bb3bd85 | ||
|
|
f9903f1e19 | ||
|
|
4a4935f165 | ||
|
|
09f92cc5e4 | ||
|
|
887e5997c2 | ||
|
|
f88a50dd07 | ||
|
|
3f12a5ae3d | ||
|
|
f2946817bf | ||
|
|
935a9b0631 | ||
|
|
c0dc411102 | ||
|
|
55037626a4 | ||
|
|
fc3e72bacf | ||
|
|
0fd0f0e7c0 | ||
|
|
22503d4e95 | ||
|
|
de9236e93a | ||
|
|
66523301aa | ||
|
|
9a81121ac1 | ||
|
|
a245ba80c3 | ||
|
|
9a1860fc5e | ||
|
|
f3f5ab70ea | ||
|
|
6ce7638fdc | ||
|
|
7f916d55e7 | ||
|
|
43ea019091 | ||
|
|
6025cad716 | ||
|
|
9365aea724 | ||
|
|
28859a8a9c | ||
|
|
304fd7ea80 | ||
|
|
5e345263a7 | ||
|
|
445ee25bbf | ||
|
|
20937815f8 | ||
|
|
3863b8ef40 | ||
|
|
d91ccaa70f | ||
|
|
7673ca5481 | ||
|
|
774a37a7d2 | ||
|
|
905094f63f | ||
|
|
fd5b7ba49d | ||
|
|
1b828b95b3 | ||
|
|
54546aaefb | ||
|
|
ded5fc8b1d | ||
|
|
68fc4f6950 | ||
|
|
5d07238ab8 | ||
|
|
5e7e5e2636 | ||
|
|
ca5c0055d1 | ||
|
|
e208df16b2 | ||
|
|
11632b0ef1 | ||
|
|
b0bc46f539 | ||
|
|
627ed45065 | ||
|
|
fc9ad5b519 | ||
|
|
40ddad1d9a | ||
|
|
eb997db703 | ||
|
|
e53dc5b80b | ||
|
|
ab64655f05 | ||
|
|
e1b530e4fc | ||
|
|
4414853e45 | ||
|
|
35adcd2c7f | ||
|
|
5ec487a832 | ||
|
|
0cc56edc95 | ||
|
|
3de8b7e022 | ||
|
|
e5946e59a8 | ||
|
|
0f48a48a4e | ||
|
|
1a255969a7 | ||
|
|
bf4728fded | ||
|
|
d5e91c7168 | ||
|
|
2ded922b5d | ||
|
|
193ab0ff63 | ||
|
|
9356449f45 | ||
|
|
670683b47f | ||
|
|
517212225e | ||
|
|
6315beb7cd | ||
|
|
c1653b7ee1 | ||
|
|
8ed0efe241 | ||
|
|
7b50894ca6 | ||
|
|
7dfd99a0b0 | ||
|
|
c78e6dc231 | ||
|
|
93f3e81cfc | ||
|
|
38f4b27d10 | ||
|
|
541ac6f8dd | ||
|
|
774eb48828 | ||
|
|
1eccedb74d | ||
|
|
bc6f953f76 | ||
|
|
261f939896 | ||
|
|
c5f0f1ef9f | ||
|
|
dc843a811e | ||
|
|
c8ada1ac46 | ||
|
|
d82ac8f355 | ||
|
|
7ab8683d72 | ||
|
|
d0d56aceb6 | ||
|
|
c33ba2c5af | ||
|
|
72e67d7b71 | ||
|
|
8dabac916b | ||
|
|
56da2dfd4c | ||
|
|
624ca3c308 | ||
|
|
123db516ad | ||
|
|
4531fc7f37 | ||
|
|
ea6662c38b | ||
|
|
9139f84c33 | ||
|
|
9e625b0e5e | ||
|
|
336df10da2 | ||
|
|
79e241730b | ||
|
|
251d329a71 | ||
|
|
8b384f184c | ||
|
|
d81ba04ba5 | ||
|
|
8f9270e9fe | ||
|
|
b4b9bbf476 | ||
|
|
a47cb865fb | ||
|
|
4f9719abc4 | ||
|
|
9785cf9fe6 | ||
|
|
91100a75db | ||
|
|
fdac9e99e1 | ||
|
|
3204466295 | ||
|
|
aac68436d9 | ||
|
|
085bf65d45 | ||
|
|
3e1312b53a | ||
|
|
60ab332235 | ||
|
|
16c1fd83bc | ||
|
|
455854f91d | ||
|
|
62f7a9f112 | ||
|
|
a1f2685629 | ||
|
|
3f2ec65c2c | ||
|
|
73b7be0606 | ||
|
|
73ee2ba4c0 | ||
|
|
3d4d9af3f4 | ||
|
|
41fb28ae80 | ||
|
|
5c18af4638 | ||
|
|
66a5878fc6 | ||
|
|
5b93149c7b | ||
|
|
83c084e476 | ||
|
|
60b92a5577 | ||
|
|
202dbb6259 | ||
|
|
75b19a5f06 | ||
|
|
ab6eebab43 | ||
|
|
b482dbb031 | ||
|
|
59af080bfb | ||
|
|
db7cd17c10 | ||
|
|
2f0a7e4f12 | ||
|
|
b63ecc59fb | ||
|
|
575cbf659c | ||
|
|
b50219ba0b | ||
|
|
e4d76b3b13 | ||
|
|
99d9a2e4b9 | ||
|
|
04a6782b48 | ||
|
|
cb3257e704 | ||
|
|
daf11c01c9 | ||
|
|
207d56c2e9 | ||
|
|
3629fa1b36 | ||
|
|
21f8550fc7 | ||
|
|
9bd0be20a6 | ||
|
|
d6b1c1409d | ||
|
|
eee0039add | ||
|
|
c9d49f6f40 | ||
|
|
0f5e35a2eb | ||
|
|
979851aac9 | ||
|
|
3a2eb182f9 | ||
|
|
2338c26392 | ||
|
|
c835cda5b6 | ||
|
|
6295f3fd71 | ||
|
|
84d0a40547 | ||
|
|
36ef1479a6 | ||
|
|
67dcc6e7f5 | ||
|
|
edd162c3d2 | ||
|
|
982656bd5a | ||
|
|
469d8b7b12 | ||
|
|
f1562a7b94 | ||
|
|
1e12f1cc80 | ||
|
|
123d8fcaa6 | ||
|
|
4b4738c58f | ||
|
|
037ff85396 | ||
|
|
e0160ac876 | ||
|
|
08abe36443 | ||
|
|
2461a66ad8 | ||
|
|
9ab3430cc4 | ||
|
|
a9348154af | ||
|
|
5621b4cbd4 | ||
|
|
028576f208 | ||
|
|
746bada0d4 | ||
|
|
c89327586d | ||
|
|
bbfe1657d6 | ||
|
|
c39a094d88 | ||
|
|
13f0dd8b01 | ||
|
|
ba7b3aa473 | ||
|
|
08d4fe92d1 | ||
|
|
09786c6d29 | ||
|
|
1150750c09 | ||
|
|
b9b1f51957 | ||
|
|
e53a487948 | ||
|
|
cd19f2ab21 | ||
|
|
124e2d5e10 | ||
|
|
bb2ceb9968 | ||
|
|
ddd203a79f | ||
|
|
017a673211 | ||
|
|
b011870c03 | ||
|
|
db4672f4af | ||
|
|
cc5c625a99 | ||
|
|
8480dadba8 | ||
|
|
fd88a6a22f | ||
|
|
7a230c90f0 | ||
|
|
f15c2ada1b | ||
|
|
fa7dbc587a | ||
|
|
5e40042ace | ||
|
|
df85d2627d | ||
|
|
229008577b | ||
|
|
a679c19af1 | ||
|
|
8b1cd5965a | ||
|
|
1881fecc83 | ||
|
|
d133809bc0 | ||
|
|
49001bb558 | ||
|
|
18f1048dc4 | ||
|
|
0afc4d2af6 | ||
|
|
0e62e50b1c | ||
|
|
07960bd65a | ||
|
|
647529f35f | ||
|
|
90e9e3ecd6 | ||
|
|
87f88f4c50 | ||
|
|
49fd9c0c36 | ||
|
|
7e4f81d541 | ||
|
|
eb3d2ef049 | ||
|
|
080782e011 | ||
|
|
10d0dd861c | ||
|
|
693afb7949 | ||
|
|
d16f6692b8 | ||
|
|
a74add96b7 | ||
|
|
643d1be6d7 | ||
|
|
1717bc98cb | ||
|
|
0c5e548892 | ||
|
|
f4ecfe6da9 | ||
|
|
71f68b3103 | ||
|
|
d8486568b7 | ||
|
|
24a6c5995b | ||
|
|
452848fd2a | ||
|
|
4f1e3da70a | ||
|
|
c630226e2d | ||
|
|
82939a446b | ||
|
|
f49833646d | ||
|
|
422e335328 | ||
|
|
c803a49134 | ||
|
|
236812cc81 | ||
|
|
a101527906 | ||
|
|
7a45391c22 | ||
|
|
4521053804 | ||
|
|
3dd0e4a66f | ||
|
|
9cd511da3d | ||
|
|
6bbe583e82 | ||
|
|
6519f48149 | ||
|
|
d02afdf22e | ||
|
|
be38969c40 | ||
|
|
7c2690ca54 | ||
|
|
36bd628378 | ||
|
|
38ff691eac | ||
|
|
582fcf8b19 | ||
|
|
60f772081c | ||
|
|
267da47f6e | ||
|
|
151cf17e35 | ||
|
|
6437700a61 | ||
|
|
07ac8957c8 | ||
|
|
2bd39bd9f5 | ||
|
|
2ecb3506b5 | ||
|
|
35fc43cda0 | ||
|
|
71311eb157 | ||
|
|
02c52f0801 | ||
|
|
ee7a0ade9e | ||
|
|
1bee29de83 | ||
|
|
d1c1977a9c | ||
|
|
16b03ec90e | ||
|
|
7162bf4d24 | ||
|
|
3fb31c9d78 | ||
|
|
52c0be63ea | ||
|
|
a54c217f88 | ||
|
|
622e5d65aa | ||
|
|
21199d9a24 | ||
|
|
51b9d80488 | ||
|
|
ccfd82e1c9 | ||
|
|
cbb81bbba7 | ||
|
|
e60cde892a | ||
|
|
7355162d80 | ||
|
|
9823f7b96f | ||
|
|
a20b95bb26 | ||
|
|
0fc2e8872d | ||
|
|
746eda70ee | ||
|
|
ec425501cd | ||
|
|
64e68706ad | ||
|
|
2f97d948b8 | ||
|
|
cc4237d076 | ||
|
|
13546d77e7 | ||
|
|
27dff95df5 | ||
|
|
5f071a6138 | ||
|
|
def611ef89 | ||
|
|
8c2c353071 | ||
|
|
d45bba19c8 | ||
|
|
9ab1dbab78 | ||
|
|
83af032bee | ||
|
|
2a5e12dfc5 | ||
|
|
e751b53241 | ||
|
|
61488627a4 | ||
|
|
da90a40bc4 | ||
|
|
d578e763c0 | ||
|
|
289545e777 | ||
|
|
3eafd0cb62 | ||
|
|
75cc8bfd50 | ||
|
|
76bc0fde33 | ||
|
|
7f05f6bc7d | ||
|
|
8f0d071afb | ||
|
|
30bdcfac29 | ||
|
|
8a1c0c041c | ||
|
|
ad7032064d | ||
|
|
c5ff8777be | ||
|
|
70e5c578ae | ||
|
|
6557864697 | ||
|
|
04cb53a9c8 | ||
|
|
5f1ae30f19 | ||
|
|
aa77cfe4b9 | ||
|
|
de148dbb61 | ||
|
|
0a7ded4052 | ||
|
|
c401ac7560 | ||
|
|
22c3d4ebc5 | ||
|
|
c5efc908de | ||
|
|
046c863768 | ||
|
|
3335cafa94 | ||
|
|
ee3fb6df09 | ||
|
|
d59653eed9 | ||
|
|
50b82f6383 | ||
|
|
72682e4db3 | ||
|
|
10e7ce6363 | ||
|
|
e874f47920 | ||
|
|
92992fc068 | ||
|
|
48075ace90 | ||
|
|
88a8caa023 | ||
|
|
f20d0effe9 | ||
|
|
7acc418489 | ||
|
|
47b6c2608c | ||
|
|
00dc8df0de | ||
|
|
244425381d | ||
|
|
a10e55cfd7 | ||
|
|
2d39fb496c | ||
|
|
9e3edd7208 | ||
|
|
015103f613 | ||
|
|
321bd4e874 | ||
|
|
799e01c7b3 | ||
|
|
e4f91fd3ba | ||
|
|
41d1ae099f | ||
|
|
2836a89f91 | ||
|
|
f04030858b | ||
|
|
5be92cd75e | ||
|
|
36d9dda500 | ||
|
|
3008d9a512 | ||
|
|
c437fa329c | ||
|
|
004bcce58e | ||
|
|
4664a7bbd4 | ||
|
|
fd7bd28381 | ||
|
|
5b090c20ec | ||
|
|
5569f49456 | ||
|
|
5cdfe62e2d | ||
|
|
138e48dc2d | ||
|
|
8f16f242b1 | ||
|
|
679dfc3441 | ||
|
|
4115ca6040 | ||
|
|
88ea647c3a | ||
|
|
69a5b4f227 | ||
|
|
aae314ae1d | ||
|
|
4b89220a7b | ||
|
|
759b934184 | ||
|
|
25d0dcd08e | ||
|
|
1d20e6277e | ||
|
|
9c9dc1c976 | ||
|
|
c6d1f31108 | ||
|
|
8362797381 | ||
|
|
1bb7e1bf47 | ||
|
|
23ba3215d4 | ||
|
|
ad8fef2fda | ||
|
|
2274cef68c | ||
|
|
bc76efaea6 | ||
|
|
de76276a4d | ||
|
|
4738a722a6 | ||
|
|
6de50d1515 | ||
|
|
088073b257 | ||
|
|
c9df8cc803 | ||
|
|
5f010bb94d | ||
|
|
f7f565477c | ||
|
|
6a6df8936e | ||
|
|
d30701c1bb | ||
|
|
5492a45b67 | ||
|
|
d2d62c350c | ||
|
|
9050e705ff | ||
|
|
7b0676025b | ||
|
|
8eb97136b0 | ||
|
|
e9c5cac20c | ||
|
|
1e19afe0e5 | ||
|
|
bbb08aa1db | ||
|
|
274f689f70 | ||
|
|
892bd9f069 | ||
|
|
d0fe058a24 | ||
|
|
14218f4d09 | ||
|
|
c7270e8654 | ||
|
|
d67d32610d | ||
|
|
e93639e1ab | ||
|
|
f0a3fd1e3a | ||
|
|
9a2fa50b0f | ||
|
|
4ace13c857 | ||
|
|
923549ef69 | ||
|
|
437fdd6d12 | ||
|
|
4014a4e250 | ||
|
|
46e220cecc | ||
|
|
78e901cbf7 | ||
|
|
123a44fefc | ||
|
|
b683246647 | ||
|
|
f72b2a21f7 | ||
|
|
3af3bd606c | ||
|
|
6c1e1e1e95 | ||
|
|
039ac9ec74 | ||
|
|
941a7d441c | ||
|
|
ad37dcce44 | ||
|
|
2c041081c9 | ||
|
|
8dddffb8b5 | ||
|
|
78c3da99f7 | ||
|
|
47d54fd295 | ||
|
|
bc39d5eaa8 | ||
|
|
29cfff70ee | ||
|
|
8c0be1099c | ||
|
|
e6a3bf9885 | ||
|
|
31bc5eb2aa | ||
|
|
be67d6dbc6 | ||
|
|
01f872c960 | ||
|
|
995ccadc5b | ||
|
|
c5918d5531 | ||
|
|
4a666705c4 | ||
|
|
f7ced1aea6 | ||
|
|
46694e458d | ||
|
|
c238e50e41 | ||
|
|
5aa358c27b | ||
|
|
1207b082dc | ||
|
|
da4bd8120b | ||
|
|
2c10d3f5b2 | ||
|
|
827c68bf75 | ||
|
|
769fe12159 | ||
|
|
fa8c0d9660 | ||
|
|
c5fd3df235 | ||
|
|
4993353184 | ||
|
|
7fe73ad2eb | ||
|
|
f63a107c11 | ||
|
|
e36c62b30e | ||
|
|
73a8ec02b2 | ||
|
|
453f6ac74a | ||
|
|
895a4eb0dc | ||
|
|
005090fbcd | ||
|
|
053a1287c9 | ||
|
|
f5955dafaf | ||
|
|
e95a9e0685 | ||
|
|
a6a44ef433 | ||
|
|
cf7c7d853f | ||
|
|
ca640ebabe | ||
|
|
baae65919f | ||
|
|
a70ab8cc1d | ||
|
|
f7f0cf942c | ||
|
|
54b9392d4b | ||
|
|
cf3ef5b403 | ||
|
|
8c63274f57 | ||
|
|
d7e62d0b0b | ||
|
|
4b4336391a | ||
|
|
6ee235545f | ||
|
|
15176223fa | ||
|
|
b08b91ca2e | ||
|
|
0afe1f69d4 | ||
|
|
6f21067ddb | ||
|
|
ccce801dc4 | ||
|
|
dc4762a03b | ||
|
|
30aebf2546 | ||
|
|
2310ef0f80 | ||
|
|
de284c2bf6 | ||
|
|
b7439cbd17 | ||
|
|
4053356e13 | ||
|
|
904602510d | ||
|
|
8165d3bd8c | ||
|
|
3c9acea31c | ||
|
|
17f2f007d2 | ||
|
|
7e213dbfbe | ||
|
|
fcb897a0e8 | ||
|
|
5230591dc2 | ||
|
|
605d8f057f | ||
|
|
738cf1f381 | ||
|
|
73fcfcec7b | ||
|
|
995e28cb32 | ||
|
|
2c6e8c414c | ||
|
|
b4edd5d841 | ||
|
|
35df036eb8 | ||
|
|
05fbdae54c | ||
|
|
b6457ae434 | ||
|
|
59f734dd07 | ||
|
|
88cd9e19c5 | ||
|
|
980d8f3092 | ||
|
|
811f46c289 | ||
|
|
6933470ce0 | ||
|
|
cba39df5da | ||
|
|
34111cfc67 | ||
|
|
fef123c2d4 | ||
|
|
a710ce5e1b | ||
|
|
13ca5352c7 | ||
|
|
92236a5bc3 | ||
|
|
4539e91fa9 | ||
|
|
c5c596dc2f | ||
|
|
d0ad554e14 | ||
|
|
3037b540d4 | ||
|
|
0b1365a04b | ||
|
|
ccb510fe47 | ||
|
|
89b987fc87 | ||
|
|
8d806def5b | ||
|
|
e5f4974e9a | ||
|
|
4dfc42528d | ||
|
|
79b471b41c | ||
|
|
f9f18dd38b | ||
|
|
d53fc46ffc | ||
|
|
781f8fb0e8 | ||
|
|
b39a44ae37 | ||
|
|
9a2c10a98f | ||
|
|
0bdb4ac894 | ||
|
|
510dcd762f | ||
|
|
64b01c471b | ||
|
|
1cd77a4a77 | ||
|
|
dba2699b7e | ||
|
|
78b080063e | ||
|
|
98c36c8b03 | ||
|
|
aac333e132 | ||
|
|
030675812e | ||
|
|
8e8bbe84f3 | ||
|
|
6d29bfdf65 | ||
|
|
625f36c780 | ||
|
|
6ff9208d46 | ||
|
|
406a64ae3f | ||
|
|
bd8aa0296b | ||
|
|
21ba1bea36 | ||
|
|
e51662526b | ||
|
|
c628695a4e | ||
|
|
155f9d4052 | ||
|
|
abadfef5a7 | ||
|
|
59e98b31df | ||
|
|
4e348dd99a | ||
|
|
0056747aee | ||
|
|
e3adcada52 | ||
|
|
02383763ec | ||
|
|
e7398af877 | ||
|
|
e1cdbed2e1 | ||
|
|
41158543cf | ||
|
|
05f3f3636b | ||
|
|
0e324115be | ||
|
|
a3a5c59327 | ||
|
|
762bc328e1 | ||
|
|
2585dcc130 | ||
|
|
bc41261009 | ||
|
|
a53812c0fb | ||
|
|
af4a718fc7 | ||
|
|
2efe4f6c80 | ||
|
|
332959128a | ||
|
|
d6e82d44fc | ||
|
|
45e76e12b6 | ||
|
|
ad0d3412fb | ||
|
|
6f8c30ec81 | ||
|
|
c88fa6d2af | ||
|
|
e4aedc9e83 | ||
|
|
b1356551c6 | ||
|
|
1c543666b5 | ||
|
|
95836a217c | ||
|
|
2ae820fb20 | ||
|
|
0e6a303c17 | ||
|
|
1a9930b93a | ||
|
|
bb72acd3ac | ||
|
|
9e2e4ffa74 | ||
|
|
ff12b37431 | ||
|
|
ac80c4adc1 | ||
|
|
3e08e0e8b6 | ||
|
|
c5ab0431a9 | ||
|
|
df7be8603b | ||
|
|
bafc9dc65e | ||
|
|
0a13839155 | ||
|
|
54e24dff39 | ||
|
|
2c17a9a52e | ||
|
|
1ae6503ceb | ||
|
|
f72f669d00 | ||
|
|
f10f792c4b | ||
|
|
344d4e31b7 | ||
|
|
60c8c80480 | ||
|
|
daf4704fe7 | ||
|
|
5febcdd4e4 | ||
|
|
d154069877 | ||
|
|
6baf36c587 | ||
|
|
9e6b1cf716 | ||
|
|
c16ca40894 | ||
|
|
b533633494 | ||
|
|
2af274dc77 | ||
|
|
5cd9af32fa | ||
|
|
4c3509a455 | ||
|
|
77d29e314c | ||
|
|
09457d8aab | ||
|
|
2168143fea | ||
|
|
cba68549d8 | ||
|
|
ab6f44cb6f | ||
|
|
9b20a6d053 | ||
|
|
5cd2b91e28 | ||
|
|
9bdb81019b | ||
|
|
ddfe4c00b1 | ||
|
|
4f64329f25 | ||
|
|
b4742ad54c | ||
|
|
36f74ae0a9 | ||
|
|
758feab413 | ||
|
|
52ed6f3f94 | ||
|
|
c1cb32de21 | ||
|
|
e3ee988225 | ||
|
|
4e030c4a3a | ||
|
|
4f73c47dcb | ||
|
|
21f7912458 | ||
|
|
350f08be2f | ||
|
|
1df31b2496 | ||
|
|
05ae78a671 | ||
|
|
6ebda41922 | ||
|
|
1adafac35f | ||
|
|
d488bcffbd | ||
|
|
f153e75f03 | ||
|
|
51961294dd | ||
|
|
9f7f391609 | ||
|
|
33ed127cba | ||
|
|
8a66bf2e09 | ||
|
|
2e901f3586 | ||
|
|
85e7b1762d | ||
|
|
7c4d1e2f60 | ||
|
|
5d0fa0fbb3 | ||
|
|
011d8a4e12 | ||
|
|
079f19a66e | ||
|
|
7a19a7d696 | ||
|
|
c0890b2347 | ||
|
|
61c1d12a86 | ||
|
|
dc0562b3be | ||
|
|
05d69ba003 | ||
|
|
ffedd2e1ad | ||
|
|
3932e28417 | ||
|
|
33a75b5450 | ||
|
|
a024be6b1d | ||
|
|
3987f26689 | ||
|
|
ac5d20ff82 | ||
|
|
9d14333cc0 | ||
|
|
a70a951192 | ||
|
|
3afc6ba24b | ||
|
|
3231d008cc | ||
|
|
205929bfc9 | ||
|
|
8128b2ed17 | ||
|
|
32dd0f543a | ||
|
|
e80851b0a1 | ||
|
|
2499276b2a | ||
|
|
f95795d547 | ||
|
|
b3a3eb414a | ||
|
|
0300b77ff5 | ||
|
|
93a0db3112 | ||
|
|
b312242cb8 | ||
|
|
5d291846d5 | ||
|
|
7387c2ed8f | ||
|
|
bf034e18eb | ||
|
|
a97a4b2c15 | ||
|
|
4b99ae957e | ||
|
|
cd97565cce | ||
|
|
0106e1c64a | ||
|
|
420779fb4e | ||
|
|
11328ea241 | ||
|
|
ecd2f2b6e5 | ||
|
|
0036dca773 | ||
|
|
cefc36a6a9 | ||
|
|
194b4dfd6b | ||
|
|
d9b4342293 | ||
|
|
39152eab3c | ||
|
|
eb6c187180 | ||
|
|
808db05d23 | ||
|
|
795df1a93f | ||
|
|
54745e786e | ||
|
|
82dfeb8afa | ||
|
|
8df9f6989a | ||
|
|
19713e6159 | ||
|
|
f526681f3d | ||
|
|
77edeac990 | ||
|
|
2c0d7c8e55 | ||
|
|
5c0227b86f | ||
|
|
8e5d7d9f20 | ||
|
|
c844c364fa | ||
|
|
4139258508 | ||
|
|
2d07ef717c | ||
|
|
383d1b2c9c | ||
|
|
c31ace409f | ||
|
|
8dbd2e2561 | ||
|
|
abe76ea003 | ||
|
|
a013d27d17 | ||
|
|
641c21627e | ||
|
|
1bad0f045e | ||
|
|
209afd3adf | ||
|
|
deb7227ce9 | ||
|
|
abeebe9c8b | ||
|
|
81cbf807cb | ||
|
|
4528233830 | ||
|
|
5684570880 | ||
|
|
c0dfa77d76 | ||
|
|
d81c6a0426 | ||
|
|
656a98675e | ||
|
|
43d5dc0528 | ||
|
|
0c7d549e59 | ||
|
|
fc4022a76d | ||
|
|
de87b4be92 | ||
|
|
d2f7785f9f | ||
|
|
e4ba9a8b72 | ||
|
|
4803ae5f78 | ||
|
|
d739d830ab | ||
|
|
3626fa79b4 | ||
|
|
dea2c883a7 | ||
|
|
2193af1a95 | ||
|
|
88bc4fd852 | ||
|
|
515efd0598 | ||
|
|
fb10a8d28e | ||
|
|
ce4628b64f | ||
|
|
d9108a7ca6 | ||
|
|
8867aaeb6d | ||
|
|
490a4e8097 | ||
|
|
f9ed61d199 | ||
|
|
61015b127e | ||
|
|
e99c36a21f | ||
|
|
0608147cd9 | ||
|
|
c40f538782 | ||
|
|
3afaa90183 | ||
|
|
bdf2976a89 | ||
|
|
697e2c2b11 | ||
|
|
c643adf57b | ||
|
|
2c21644a2c | ||
|
|
d11d3b58f7 | ||
|
|
690705ff8f | ||
|
|
34100d1c76 | ||
|
|
676a25631b | ||
|
|
9e92c87351 | ||
|
|
2a75d14e0e | ||
|
|
f3bf9c0c3c | ||
|
|
acd63fdeac | ||
|
|
bc819d59f4 | ||
|
|
8c2e25339f | ||
|
|
ef73f712ec | ||
|
|
08d670a882 | ||
|
|
477d3fb197 | ||
|
|
4a7dae9cc2 | ||
|
|
69649d7496 | ||
|
|
12f420f50e | ||
|
|
66c8b96c9a | ||
|
|
a51b3b4621 | ||
|
|
d69ebc728b | ||
|
|
f13795b743 | ||
|
|
1dfc625f79 | ||
|
|
0c731e4856 | ||
|
|
9c0f72921f | ||
|
|
a94db0ec43 | ||
|
|
0ff00acf9d | ||
|
|
45b7b4b2cb | ||
|
|
59d178f99e | ||
|
|
c59c87c3e8 | ||
|
|
c21476b262 | ||
|
|
5b4d65db23 | ||
|
|
b351551568 | ||
|
|
ea868b7079 | ||
|
|
613008b56e | ||
|
|
2ea2ecec94 | ||
|
|
a3038e75cf | ||
|
|
5dcd4580c3 | ||
|
|
68b48ad610 | ||
|
|
532ac261ff | ||
|
|
94f65d8f70 | ||
|
|
46e5299c60 | ||
|
|
56e1eb33af | ||
|
|
753b301e88 | ||
|
|
2c14a140fe | ||
|
|
d31f0e22e3 | ||
|
|
30cfd8a769 | ||
|
|
d645b4fe73 | ||
|
|
68c794f608 | ||
|
|
475d18b701 | ||
|
|
e9634b7066 | ||
|
|
c60bba1166 | ||
|
|
35900c2350 | ||
|
|
e6fc817f49 | ||
|
|
7a18d160c8 | ||
|
|
a33104d7c4 | ||
|
|
7f57180da4 | ||
|
|
a23e7fe83d | ||
|
|
c854508381 | ||
|
|
d1cf8915e3 | ||
|
|
d9b5b87f2b | ||
|
|
5923ba21de | ||
|
|
8fe805bc68 | ||
|
|
1944a172a3 | ||
|
|
de0b267568 | ||
|
|
03430a4df5 | ||
|
|
87bf3110c8 | ||
|
|
748d80ff3b | ||
|
|
33f00fb811 | ||
|
|
ae99558f40 | ||
|
|
85cbffb845 | ||
|
|
70dbe7c9ca | ||
|
|
2d0b5023c9 | ||
|
|
92fdd0b80d | ||
|
|
dac3533654 | ||
|
|
2d93ac58fc | ||
|
|
79924a59bc | ||
|
|
545f974552 | ||
|
|
2699a38f7b | ||
|
|
13d52803d6 | ||
|
|
1a3b8d1dd6 | ||
|
|
b2cbf4b6f4 | ||
|
|
9839787b6d | ||
|
|
cd0c0f1b01 | ||
|
|
5db1b94ac4 | ||
|
|
c8f30b3289 | ||
|
|
88b4039e47 | ||
|
|
49033f67f3 | ||
|
|
410077467b | ||
|
|
14769c0350 | ||
|
|
b728fcc2d6 | ||
|
|
74291271e3 | ||
|
|
b0787d9a3f | ||
|
|
be69d17345 | ||
|
|
52b9656a1a |
8
.coveragerc
Normal file
8
.coveragerc
Normal file
@@ -0,0 +1,8 @@
|
||||
[report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
if TYPE_CHECKING:
|
||||
|
||||
[run]
|
||||
branch = True
|
||||
source = game,pydcs_extensions,qt_ui,resources/tools
|
||||
92
.gitattributes
vendored
Normal file
92
.gitattributes
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
* text=auto
|
||||
*.pxd text diff=python
|
||||
*.py text diff=python
|
||||
*.py3 text diff=python
|
||||
*.pyw text diff=python
|
||||
*.pyx text diff=python
|
||||
*.pyz text diff=python
|
||||
*.pyi text diff=python
|
||||
*.db binary
|
||||
*.p binary
|
||||
*.pkl binary
|
||||
*.pickle binary
|
||||
*.pyc binary export-ignore
|
||||
*.pyo binary export-ignore
|
||||
*.pyd binary
|
||||
unshipped_data/arcgis_maps/ filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# https://github.com/alexkaratarakis/gitattributes/blob/master/Common.gitattributes
|
||||
# Documents
|
||||
*.bibtex text diff=bibtex
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.md text diff=markdown
|
||||
*.mdx text diff=markdown
|
||||
*.tex text diff=tex
|
||||
*.adoc text
|
||||
*.textile text
|
||||
*.mustache text
|
||||
*.csv text
|
||||
*.tab text
|
||||
*.tsv text
|
||||
*.txt text
|
||||
*.sql text
|
||||
*.epub diff=astextplain
|
||||
|
||||
# Graphics
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.ico binary
|
||||
# SVG treated as text by default.
|
||||
*.svg text
|
||||
# If you want to treat it as binary,
|
||||
# use the following line instead.
|
||||
# *.svg binary
|
||||
*.eps binary
|
||||
|
||||
# Scripts
|
||||
*.bash text eol=lf
|
||||
*.fish text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.zsh text eol=lf
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
|
||||
# Serialisation
|
||||
*.json text
|
||||
*.toml text
|
||||
*.xml text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
|
||||
# Archives
|
||||
*.7z binary
|
||||
*.gz binary
|
||||
*.tar binary
|
||||
*.tgz binary
|
||||
*.zip binary
|
||||
|
||||
# Text files where line endings should be preserved
|
||||
*.patch -text
|
||||
|
||||
#
|
||||
# Exclude files from exporting
|
||||
#
|
||||
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.gitkeep export-ignore
|
||||
86
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
86
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
name: Bug report
|
||||
description: >
|
||||
Use for any bug that happens after campaign generation. If the New Game wizard
|
||||
failed, use the "New Game wizard failed" template instead.
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Before filing, please search the issue tracker to see if the issue has
|
||||
already been reported.
|
||||
- type: dropdown
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Affected versions
|
||||
multiple: true
|
||||
description: >
|
||||
Select all DCS Liberation versions in which you have observed this bug.
|
||||
You do not need to test all of them, but the information is useful if
|
||||
you have it.
|
||||
|
||||
|
||||
If you do not see your version listed here you are on an old release
|
||||
that is not supported, and the bug may already be fixed in a newer
|
||||
release. Check that the bug still exists in a newer release before
|
||||
filing.
|
||||
|
||||
|
||||
If the bug was found in a development build, select "Development build"
|
||||
and provide a link to the build in the field below.
|
||||
options:
|
||||
- 6.1.1
|
||||
- Development build
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Build information
|
||||
description:
|
||||
The build information from the Help -> Report an issue window.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: >
|
||||
Describe the bug. What went wrong? What did you expect to happen
|
||||
instead? What steps should we take to reproduce the error? If an error
|
||||
dialog was shown, include the full text.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Save game and other files
|
||||
description: >
|
||||
Attach any files needed to reproduce the bug here. **A save game is
|
||||
required.** We typically cannot help without a save game (the
|
||||
`.liberation` (or `.liberation.zip`, for 7.x) file found in
|
||||
`%USERPROFILE%/Saved Games/DCS/Liberation/Saves`), so most bugs filed
|
||||
without saved games will be closed without investigation.
|
||||
|
||||
|
||||
Other useful files to include are:
|
||||
|
||||
|
||||
The Liberation log file. The log file is located at `<Liberation install
|
||||
directory>/logs/liberation.log`. The log often includes data about
|
||||
non-fatal errors that could be the root cause of the problem.
|
||||
|
||||
|
||||
The `liberation_nextturn.miz` or a track file. This should always be
|
||||
included for bugs where the mission was generated incorrectly or where
|
||||
the in-game AI is misbehaving.
|
||||
|
||||
|
||||
The `state.json` file for the most recently completed turn, located at
|
||||
`<Liberation install directory>/state.json`. This file is essential for
|
||||
investigating any issues with end-of-turn results processing. **If you
|
||||
include this file, also include `last_turn.liberation`** (unless the
|
||||
save is from 7.x or newer, which includes that information in the save
|
||||
automatically).
|
||||
|
||||
|
||||
You can attach files to the bug by dragging and dropping the file into
|
||||
this text box. GitHub will not allow uploads of all file types, so
|
||||
attach a zip of the files if needed.
|
||||
validations:
|
||||
required: true
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,40 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before filing, please search the issue tracker to see if the issue has already been reported.
|
||||
|
||||
If reporting a DCS AI bug, check https://github.com/dcs-liberation/dcs_liberation#dcs-bugs.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Additional information**
|
||||
|
||||
We will usually need more information for debugging. Include as much of the following as you are able:
|
||||
|
||||
- DCS Liberation save file (the `.liberation` file you save from the DCS Liberation window). By default these are located in your DCS saved games directory (`%USERPROFILE%/Saved Games/DCS`).
|
||||
- The generated mission file (the `.miz` file that you load in DCS to play the turn). By default these are located in your missions directory (`%USERPROFILE%/Saved Games/DCS/Missions`).
|
||||
- A tacview track file, especially when demonstrating an issue with AI behavior. By default these are located in your Tacview tracks directory (`%USERPROFILE%/Documents/Tacview`).
|
||||
- The state.json file from the finished mission when the problem is related to results processing. By default these are located in your Liberation install directory.
|
||||
|
||||
**Version information (please complete the following information):**
|
||||
- DCS Liberation [e.g. 2.3.1]:
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
18
.github/ISSUE_TEMPLATE/config.yml
vendored
18
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1 +1,19 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: FAQ
|
||||
url: https://discord.gg/PXHA6AXw
|
||||
about: Check to see if your issue is in the FAQ.
|
||||
- name: Manual
|
||||
url: https://github.com/dcs-liberation/dcs_liberation/wiki/
|
||||
- name: Feature blocking DCS AI bugs
|
||||
url: https://github.com/dcs-liberation/dcs_liberation#dcs-bugs
|
||||
about: >
|
||||
A list of known DCS bugs that prevent us from improving AI behavior. Check
|
||||
the list before filing AI bugs here to see if it's something we know about
|
||||
but cannot fix.
|
||||
- name: DCS bugs
|
||||
url: https://forums.eagle.ru/forum/119-dcs-world-27/
|
||||
about: >
|
||||
DCS bugs should be reported against DCS, not here. Occasionally we can add
|
||||
workarounds for DCS bugs. Use the "Bug report" template if you can suggest
|
||||
a workaround.
|
||||
|
||||
113
.github/ISSUE_TEMPLATE/new-game-bug.yml
vendored
Normal file
113
.github/ISSUE_TEMPLATE/new-game-bug.yml
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: New Game wizard failed
|
||||
description: >
|
||||
Use for bugs that prevent the "New Game" wizard from completing successfully.
|
||||
If the wizard completes without issue, use the normal bug report template.
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Before filing, please search the issue tracker to see if the issue has
|
||||
already been reported.
|
||||
|
||||
|
||||
If the bug is not related to campaign generation (the campaign was
|
||||
created successfully and as expected), use the normal bug report
|
||||
template instead, as this template will not include the information we
|
||||
need. We are unable to investigate incomplete bug reports, so they will
|
||||
be closed and you will be asked to refile. If you're unsure, use your
|
||||
best guess. Needing to refile is not the end of the world :)
|
||||
- type: dropdown
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Affected versions
|
||||
multiple: true
|
||||
description: >
|
||||
Select all DCS Liberation versions in which you have observed this bug.
|
||||
You do not need to test all of them, but the information is useful if
|
||||
you have it.
|
||||
|
||||
|
||||
If you do not see your version listed here you are on an old release
|
||||
that is not supported, and the bug may already be fixed in a newer
|
||||
release. Check that the bug still exists in a newer release before
|
||||
filing.
|
||||
|
||||
|
||||
If the bug was found in a development build, select "Development build"
|
||||
and provide a link to the build in the field below.
|
||||
options:
|
||||
- 6.1.1
|
||||
- Development build
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Build information
|
||||
description:
|
||||
The build information from the Help -> Report an issue window.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Campaign name
|
||||
description: >
|
||||
The name of the campaign you selected. If the bug only occurs with a
|
||||
custom campaign (or modifications to a stock campaign), upload the
|
||||
campaign file as an attachment to the bug description field.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Blue faction
|
||||
description: >
|
||||
The name of the blue faction you selected. If the bug only occurs with a
|
||||
custom faction (or modifications to a stock faction), upload the faction
|
||||
file as an attachment to the bug description field.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Red faction
|
||||
description: >
|
||||
The name of the red faction you selected. If the bug only occurs with a
|
||||
custom faction (or modifications to a stock faction), upload the faction
|
||||
file as an attachment to the bug description field.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Modifications to default settings
|
||||
description: >
|
||||
Describe any modifications you made to the default campaign generation
|
||||
settings.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: >
|
||||
Describe the bug. What went wrong? If an error dialog was shown, include
|
||||
the full text.
|
||||
|
||||
|
||||
Attach any relevant files such as custom campaign files or factions
|
||||
here. You can attach files to the bug by dragging and dropping the file
|
||||
into this text box. GitHub will not allow uploads of all file types, so
|
||||
attach a zip of the files if needed.
|
||||
|
||||
|
||||
If possible, also include the save game. If the bug prevented the game
|
||||
from being generated at all this will not be possible, but if the bug is
|
||||
that the wizard generated something incorrectly, the save game will help
|
||||
us see what went wrong.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Log file
|
||||
description: >
|
||||
Attach the Liberation log file. The log file is located at `<Liberation
|
||||
install directory>/logs/liberation.log`.
|
||||
|
||||
|
||||
You can attach files to the bug by dragging and dropping the file into
|
||||
this text box.
|
||||
validations:
|
||||
required: true
|
||||
22
.github/actions/build-app/action.yaml
vendored
Normal file
22
.github/actions/build-app/action.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Build Liberation package
|
||||
description: Assembles the full Liberation application.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Build client
|
||||
shell: powershell
|
||||
run: |
|
||||
cd client
|
||||
npm run build
|
||||
|
||||
- name: Build binaries
|
||||
shell: powershell
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
$env:PYTHONPATH=".;./pydcs"
|
||||
pyinstaller pyinstaller.spec
|
||||
|
||||
- name: Install changelog
|
||||
shell: powershell
|
||||
run: |
|
||||
Copy-Item .\changelog.md .\dist
|
||||
16
.github/actions/mypy/action.yaml
vendored
Normal file
16
.github/actions/mypy/action.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: mypy
|
||||
description: Type checks Python code.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: mypy game
|
||||
shell: powershell
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
mypy game
|
||||
|
||||
- name: mypy tests
|
||||
shell: powershell
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
mypy tests
|
||||
17
.github/actions/setup-liberation-js/action.yaml
vendored
Normal file
17
.github/actions/setup-liberation-js/action.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Liberation JS set-up
|
||||
description: Sets up the Liberation Javascript environment.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16"
|
||||
cache: npm
|
||||
cache-dependency-path: client/package-lock.json
|
||||
|
||||
- name: npm ci
|
||||
shell: powershell
|
||||
run: |
|
||||
cd client
|
||||
npm ci
|
||||
21
.github/actions/setup-liberation-python/action.yaml
vendored
Normal file
21
.github/actions/setup-liberation-python/action.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Liberation Python set-up
|
||||
description: Sets up the Liberation Python environment.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
|
||||
- name: Install environment
|
||||
shell: powershell
|
||||
run: |
|
||||
python -m venv ./venv
|
||||
|
||||
- name: Install dependencies
|
||||
shell: powershell
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
python -m pip install -r requirements.txt
|
||||
24
.github/pull_request_template.md
vendored
Normal file
24
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Pull requests should be made against the `develop` branch. Any backports
|
||||
necessary will be handled by the development team.
|
||||
|
||||
Pull requests should be focused on one task. Multiple bug fixes should be
|
||||
multiple PRs. We cannot merge half a PR, and combined PRs are much more
|
||||
difficult to review. PRs that do not adhere to this will have their review
|
||||
delayed.
|
||||
|
||||
Prefer rebase to merge, and squash commits as needed to preserve a readable
|
||||
commit history. This project maintains linear history in the develop branch, so
|
||||
we will either rebase or squash your PR when merging. It is much easier for us
|
||||
if your branch already has a readable commit history (ensure that your commit
|
||||
subject lines are clear enough to identify the patch in the git log). An
|
||||
exception to this is made for large PRs that are likely to require multiple
|
||||
rounds of review; in that case it's easier if you **don't** do this (GitHub
|
||||
does not preserve the history of old commits, so we cannot filter a PR for only
|
||||
new changes if a branch is force pushed) and we will squash it when merging.
|
||||
|
||||
New features and bug fixes are usually worth mentioning in the changelog.
|
||||
Exceptions are fixes for bugs that never shipped (were only present in a canary
|
||||
build), and changes with no intended user observable behavior, such as a
|
||||
refactor. If you're comfortable writing the note yourself, add it to
|
||||
`changelog.md` in the root of the project in the section for the upcoming
|
||||
release.
|
||||
13
.github/workflows/black.yml
vendored
13
.github/workflows/black.yml
vendored
@@ -1,13 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
args: ". --check"
|
||||
69
.github/workflows/build.yml
vendored
69
.github/workflows/build.yml
vendored
@@ -3,56 +3,39 @@ name: Build
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
uses: ./.github/workflows/lint.yml
|
||||
|
||||
test:
|
||||
uses: ./.github/workflows/test.yml
|
||||
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Set up Python environment
|
||||
uses: ./.github/actions/setup-liberation-python
|
||||
|
||||
- name: Install environment
|
||||
run: |
|
||||
python -m venv ./venv
|
||||
- name: Set up JS environment
|
||||
uses: ./.github/actions/setup-liberation-js
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
python -m pip install -r requirements.txt
|
||||
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
|
||||
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
|
||||
- name: Set build number
|
||||
run: |
|
||||
[IO.File]::WriteAllLines($pwd.path + "\resources\buildnumber", $env:GITHUB_RUN_NUMBER)
|
||||
[IO.File]::WriteAllLines($pwd.path + "\resources\gitsha", $env:GITHUB_SHA)
|
||||
|
||||
- name: mypy game
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
mypy game
|
||||
- name: Build app
|
||||
uses: ./.github/actions/build-app
|
||||
|
||||
- name: mypy gen
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
mypy gen
|
||||
- name: Create archive
|
||||
run:
|
||||
Compress-Archive -Path .\dist\dcs_liberation\ -DestinationPath
|
||||
dist\dcs_liberation.zip
|
||||
|
||||
- name: mypy tests
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
mypy tests
|
||||
|
||||
- name: update build number
|
||||
run: |
|
||||
[IO.File]::WriteAllLines($pwd.path + "\resources\buildnumber", $env:GITHUB_RUN_NUMBER)
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
$env:PYTHONPATH=".;./pydcs"
|
||||
pyinstaller pyinstaller.spec
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dcs_liberation
|
||||
path: dist/
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dcs_liberation
|
||||
path: dist/dcs_liberation.zip
|
||||
|
||||
30
.github/workflows/lint.yml
vendored
Normal file
30
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Python lint
|
||||
|
||||
on: workflow_call
|
||||
|
||||
jobs:
|
||||
black:
|
||||
name: Black
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
version: ~=22.12
|
||||
src: "."
|
||||
options: "--check"
|
||||
|
||||
mypy:
|
||||
name: Type checking
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Python environment
|
||||
uses: ./.github/actions/setup-liberation-python
|
||||
|
||||
- name: mypy
|
||||
uses: ./.github/actions/mypy
|
||||
150
.github/workflows/release.yml
vendored
150
.github/workflows/release.yml
vendored
@@ -2,107 +2,83 @@ name: Release Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ '*' ]
|
||||
tags: ["*"]
|
||||
|
||||
jobs:
|
||||
|
||||
lint:
|
||||
uses: ./.github/workflows/lint.yml
|
||||
|
||||
test:
|
||||
uses: ./.github/workflows/test.yml
|
||||
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Set up Python environment
|
||||
uses: ./.github/actions/setup-liberation-python
|
||||
|
||||
- name: Install environment
|
||||
run: |
|
||||
python -m venv ./venv
|
||||
- name: Set up JS environment
|
||||
uses: ./.github/actions/setup-liberation-js
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
python -m pip install -r requirements.txt
|
||||
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
|
||||
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
|
||||
- name: Finalize build
|
||||
run: |
|
||||
New-Item -ItemType file resources\final
|
||||
|
||||
- name: Finalize version
|
||||
run: |
|
||||
New-Item -ItemType file resources\final
|
||||
- name: Build app
|
||||
uses: ./.github/actions/build-app
|
||||
with:
|
||||
release: true
|
||||
|
||||
- name: mypy game
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
mypy game
|
||||
|
||||
- name: mypy gen
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
mypy gen
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
$env:PYTHONPATH=".;./pydcs"
|
||||
pyinstaller pyinstaller.spec
|
||||
|
||||
- name: Create Installer
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref }}
|
||||
run: |
|
||||
Copy-Item .\changelog.md .\dist
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dcs_liberation
|
||||
path: dist/
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dcs_liberation
|
||||
path: dist/
|
||||
|
||||
release:
|
||||
needs: [ build ]
|
||||
needs: [build]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: dcs_liberation
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: dcs_liberation
|
||||
|
||||
- name: "Get Version"
|
||||
id: version
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref }}
|
||||
run: |
|
||||
Get-ChildItem -Recurse -Depth 1
|
||||
$version = ($env:TAG_NAME -split "/") | Select-Object -Last 1
|
||||
$prerelease = ("2.1.1-alpha3" -match '[^\.\d]').ToString().ToLower()
|
||||
Write-Host $version
|
||||
Write-Host $prerelease
|
||||
Write-Output "::set-output name=number::$version"
|
||||
Write-Output "::set-output name=prerelease::$prerelease"
|
||||
$changelog = Get-Content .\changelog.md
|
||||
$last_change = ($changelog | Select-String -Pattern "^#\s" | Select-Object -Skip 1 -First 1).LineNumber - 2
|
||||
($changelog | Select-Object -First $last_change) -join "`n" | Out-File .\releasenotes.md
|
||||
Compress-Archive -Path .\dcs_liberation -DestinationPath "dcs_liberation.$version.zip" -Compression Optimal
|
||||
|
||||
- uses: actions/create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
body_path: releasenotes.md
|
||||
draft: false
|
||||
prerelease: ${{ steps.version.outputs.prerelease }}
|
||||
- name: "Get Version"
|
||||
id: version
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref }}
|
||||
run: |
|
||||
Get-ChildItem -Recurse -Depth 1
|
||||
$version = ($env:TAG_NAME -split "/") | Select-Object -Last 1
|
||||
$prerelease = ("2.1.1-alpha3" -match '[^\.\d]').ToString().ToLower()
|
||||
Write-Host $version
|
||||
Write-Host $prerelease
|
||||
Write-Output "::set-output name=number::$version"
|
||||
Write-Output "::set-output name=prerelease::$prerelease"
|
||||
$changelog = Get-Content .\changelog.md
|
||||
$last_change = ($changelog | Select-String -Pattern "^#\s" | Select-Object -Skip 1 -First 1).LineNumber - 2
|
||||
($changelog | Select-Object -First $last_change) -join "`n" | Out-File .\releasenotes.md
|
||||
Compress-Archive -Path .\dcs_liberation -DestinationPath "dcs_liberation.$version.zip" -Compression Optimal
|
||||
|
||||
- uses: actions/create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
body_path: releasenotes.md
|
||||
draft: false
|
||||
prerelease: ${{ steps.version.outputs.prerelease }}
|
||||
|
||||
- uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dcs_liberation.${{ steps.version.outputs.number }}.zip
|
||||
asset_name: dcs_liberation.${{ steps.version.outputs.number }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dcs_liberation.${{ steps.version.outputs.number }}.zip
|
||||
asset_name: dcs_liberation.${{ steps.version.outputs.number }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
57
.github/workflows/test.yml
vendored
57
.github/workflows/test.yml
vendored
@@ -1,33 +1,38 @@
|
||||
name: Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
name: Tests
|
||||
on: workflow_call
|
||||
jobs:
|
||||
|
||||
build:
|
||||
python-tests:
|
||||
name: Python tests
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Set up Python environment
|
||||
uses: ./.github/actions/setup-liberation-python
|
||||
|
||||
- name: Install environment
|
||||
run: |
|
||||
python -m venv ./venv
|
||||
- name: run tests
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
pytest --cov-report=xml tests
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
python -m pip install -r requirements.txt
|
||||
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
|
||||
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
- name: run tests
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
pytest tests
|
||||
ts-tests:
|
||||
name: Typescript tests
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JS environment
|
||||
uses: ./.github/actions/setup-liberation-js
|
||||
|
||||
- name: run tests
|
||||
run: |
|
||||
cd client
|
||||
npm test -- --coverage
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,14 +1,14 @@
|
||||
*.pyc
|
||||
__pycache__
|
||||
build/**
|
||||
# Sphinx
|
||||
docs/_build
|
||||
resources/payloads/*.lua
|
||||
venv
|
||||
logs.txt
|
||||
.DS_Store
|
||||
.vscode/settings.json
|
||||
dist/**
|
||||
a.py
|
||||
resources/tools/a.miz
|
||||
/.coverage
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
.env
|
||||
@@ -17,9 +17,9 @@ env/
|
||||
/kneeboards
|
||||
/liberation_preferences.json
|
||||
/state.json
|
||||
/serverconfig.env
|
||||
|
||||
/logs/
|
||||
|
||||
qt_ui/logs/liberation.log
|
||||
/resources/logging.yaml
|
||||
|
||||
*.psd
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 20.8b1
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3
|
||||
13
.readthedocs.yaml
Normal file
13
.readthedocs.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
39
.vscode/launch.json
vendored
39
.vscode/launch.json
vendored
@@ -15,6 +15,32 @@
|
||||
},
|
||||
"preLaunchTask": "Prepare Environment"
|
||||
},
|
||||
{
|
||||
"name": "Python: Debug",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "qt_ui\\main.py",
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"PYTHONPATH": ".;./pydcs",
|
||||
"CORS_ALLOW_DEBUG_SERVER": "true"
|
||||
},
|
||||
"args": ["--dev"],
|
||||
"preLaunchTask": "Prepare Environment"
|
||||
},
|
||||
{
|
||||
"name": "Node: Development Server",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceRoot}\\client",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run", "start"
|
||||
],
|
||||
"env": {
|
||||
"BROWSER": "none"
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Python: Make Release",
|
||||
"type": "python",
|
||||
@@ -25,6 +51,17 @@
|
||||
"PYTHONPATH": ".;./pydcs"
|
||||
},
|
||||
"preLaunchTask": "Prepare Environment"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Fix Layout orientation",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "resources\\tools\\fix_layout_orientation.py",
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"PYTHONPATH": ".;./pydcs"
|
||||
},
|
||||
"args": ["resources/layouts/anti_air/S-300_Site.miz"]
|
||||
},
|
||||
]
|
||||
}
|
||||
11
README.md
11
README.md
@@ -1,4 +1,6 @@
|
||||

|
||||
[](https://shdwp.github.io/ukraine/)
|
||||
|
||||
(Github Readme Banner and Splash screen Artwork by Andriy Dankovych, CC BY-SA 4.0)
|
||||
|
||||
[](https://patreon.com/khopa)
|
||||
|
||||
@@ -12,7 +14,12 @@
|
||||
|
||||
## About DCS Liberation
|
||||
DCS Liberation is a [DCS World](https://www.digitalcombatsimulator.com/en/products/world/) turn based single-player or co-op dynamic campaign.
|
||||
It is an external program that generates full and complex DCS missions and manage a persistent combat environment.
|
||||
It is an external program that generates full and complex DCS missions and manage a persistent combat environment.
|
||||
|
||||
**Note that DCS Liberation does not support the stable release of DCS. We can
|
||||
only guarantee compatibility with either the open beta or the stable release,
|
||||
and more people play the open beta. DCS stable _might_ work sometimes, but it's
|
||||
untested, and we will be unable to fix any bugs unique to stable DCS.**
|
||||
|
||||

|
||||
|
||||
|
||||
200
changelog.md
200
changelog.md
@@ -1,3 +1,202 @@
|
||||
# 7.1.1
|
||||
|
||||
Saves from 7.1.0 are compatible with 7.1.1
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
## Fixes
|
||||
|
||||
# 7.1.0
|
||||
|
||||
Saves from 7.0.0 are compatible with 7.1.0
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for Normandy 2 airfields.
|
||||
* **[Factions]** Replaced Patriot STRs "EWRs" with AN/FPS-117 for blue factions 1980 or newer.
|
||||
* **[Mission Generation]** Added option to prevent scud and V2 sites from firing at the start of the mission.
|
||||
* **[Mission Generation]** Added settings for controlling number of tactical commander, observer, JTAC, and game master slots.
|
||||
* **[Mission Planning]** Per-flight TOT offsets can now be set in the flight details UI. This allows individual flights to be scheduled ahead of or behind the rest of the package.
|
||||
* **[New Game Wizard]** The air wing configuration dialog will check for and reject overfull airbases before continuing when the new squadron rules are used.
|
||||
* **[New Game Wizard]** Closing the air wing configuration dialog will now cancel and return to the new game wizard rather than reverting changes and continuing.
|
||||
* **[New Game Wizard]** A warning will be displayed next to the new squadron rules button if the campaign predates the new rules and will likely require user intervention before continuing.
|
||||
* **[UI]** Parking capacity of each squadron's base is now shown during air wing configuration to avoid overcrowding bases when beginning the game with full squadrons.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Mission Planning]** BAI is once again plannable against missile sites and coastal defense batteries.
|
||||
* **[UI]** Fixed formatting of departure time in flight details dialog.
|
||||
|
||||
# 7.0.0
|
||||
|
||||
Saves from 6.x are not compatible with 7.0.
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.8.5.40170.
|
||||
* **[Engine]** Saved games are now a zip file of save assets for easier bug reporting. The new extension is .liberation.zip. Drag and drop that file into bug reports.
|
||||
* **[Campaign]** Added options to limit squadron sizes and to begin all squadrons at maximum strength. Maximum squadron size is defined during air wing configuration with default values provided by the campaign.
|
||||
* **[Campaign]** Added handling for more DCS death events. This probably does not catch any deaths that weren't previously tracked, but it should record them sooner, which will improve results for game crashes or other early exits.
|
||||
* **[Campaign AI]** The campaign AI now prefers fulfilling missions with squadrons which have a matching primary task. Previously distance from target held a stronger influence than task preference. Primary tasks for squadrons are set by campaign designers but are user-configurable.
|
||||
* **[Flight Planning]** Package TOT and composition can be modified after advancing time in Liberation.
|
||||
* **[Mission Generation]** Units on the front line are now hidden on MFDs.
|
||||
* **[Mission Generation]** Preset radio channels will now be configured for both A-10C modules.
|
||||
* **[Mission Generation]** The A-10C II now uses separate radios for inter- and intra-flight comms (similar to other modern aircraft).
|
||||
* **[Mission Generation]** Wind speeds no longer follow a uniform distribution. Median wind speeds are now much lower and the standard deviation has been reduced considerably at altitude but increased somewhat at MSL.
|
||||
* **[Mission Generation]** Improved task generation for SEAD flights carrying TALDs.
|
||||
* **[Mission Generation]** Added task timeout for SEAD flights with TALDs to prevent AI from overflying the target.
|
||||
* **[Mission Generation]** Game state will automatically be checkpointed before fast-forwarding the mission, and restored on mission abort. This means that it's now possible to abort a mission and make changes without needing to manually re-load your game.
|
||||
* **[Modding]** Updated Community A-4E-C mod version support to 2.1.0 release.
|
||||
* **[Modding]** Add support for VSN F-4B and F-4C mod.
|
||||
* **[Modding]** Added support for AI C-47 mod.
|
||||
* **[Modding]** Custom factions can now be defined in YAML as well as JSON. JSON support may be removed in the future if having both formats causes confusion.
|
||||
* **[Modding]** Campaigns which require custom factions can now define those factions directly in the campaign YAML. See Operation Aliied Sword for an example.
|
||||
* **[Modding]** The `mission_types` field in squadron files has been removed. Squadron task capability is now determined by airframe, and the auto-assignable list has always been overridden by the campaign settings.
|
||||
* **[Modding]** Aircraft task capabilities and preferred aircraft for each task are now moddable in the aircraft unit yaml files. Each aircraft has a weight per task. Higher weights are given higher preference.
|
||||
* **[Modding]** Wind speed generation inputs are now moddable. See https://dcs-liberation.rtfd.io/en/latest/modding/weather.html.
|
||||
* **[New Game Wizard]** Choices for some options will be remembered for the next new game. Not all settings will be preserved, as many are campaign dependent.
|
||||
* **[New Game Wizard]** Lua plugins can now be set while creating a new game.
|
||||
* **[New Game Wizard]** Squadrons can be directly replaced with a preset during air wing configuration rather than needing to remove and create a new squadron.
|
||||
* **[New Game Wizard]** Squadron liveries can now be selected during air wing configuration.
|
||||
* **[Squadrons]** Squadron-specific mission capability lists no longer restrict players from assigning missions outside the squadron's preferences.
|
||||
* **[UI]** The orientation of objects like SAMs, EWRs, garrisons, and ships can now be manually adjusted.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Campaign]** Fixed a longstanding bug where oversized airlifts could corrupt a save with empty convoys.
|
||||
* **[Campaign]** Aircraft with built-in TGPs but without an external pod will no longer degrade automatic loadouts to iron bombs.
|
||||
* **[Engine]** Fixed crash in startup caused by a corrupted Liberation preferences file.
|
||||
* **[Flight Planning]** AEW&C missions are now plannable over FOBs and LHAs.
|
||||
* **[Flight Planning]** BAI is no longer plannable against buildings.
|
||||
* **[Modding]** Fixed an issue where Falklands campaigns created or edited with new versions of DCS could not be loaded.
|
||||
* **[Modding]** Fixed decoding of campaign yaml files to use UTF-8 rather than the system locale's default. It's now possible to use "Bf 109 K-4 Kurfürst" as a preferred aircraft type.
|
||||
* **[Mission Generation]** Planes will no longer spawn in helipads that are not also designated for fixed wing parking.
|
||||
* **[Mission Generation]** Potentially an issue where ground war planning game state could become corrupted, preventing mission generation.
|
||||
* **[Mission Generation]** Refueling tasks will now only be created for flights that have a tanker in their package.
|
||||
* **[Mission Generation]** Fixed missing Tanker task on recovery tanker missions.
|
||||
* **[UI]** Fixed error when resetting air wing configuration during game setup.
|
||||
* **[UI]** Fixed flight plan recreation when changing mission type with "Recreate as" flight options.
|
||||
* **[UI]** Fixed failure to launch UI when Liberation persistent preferences file was corrupt.
|
||||
|
||||
# 6.1.1
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Data]** Fixed unit ID for the KS-19 AAA. KS-19 would not previously generate correctly in missions. A new game is required for this fix to take effect.
|
||||
* **[Flight Planning]** Automatic flight planning will no longer accidentally plan a recovery tanker instead of a theater refueling package. This fixes a potential crash during mission generation when opfor plans a refueling task at a sunk carrier. You'll need to skip the current turn to force opfor to replan their flights to get the fix.
|
||||
* **[Mission Generation]** Using heliports (airports without any runways) will no longer cause mission generation to fail.
|
||||
* **[Mission Generation]** Prevent helicopters from spawning into collisions at FARPs when more than one flight uses the same FARP.
|
||||
|
||||
# 6.1.0
|
||||
|
||||
Saves from 6.0.0 are compatible with 6.1.0
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.8.1.34437, including Blackshark 3.
|
||||
* **[Factions]** Defaulted bluefor modern to use Georgian and Ukrainian liveries for Russian aircraft.
|
||||
* **[Factions]** Added Peru.
|
||||
* **[Flight Planning]** AEW&C and Refueling flights are now plannable on LHA carriers.
|
||||
* **[Flight Planning]** Refueling flights planned on aircraft carriers will act as a recovery tanker for the carrier.
|
||||
* **[Loadouts]** Adjusted F-15E loadouts.
|
||||
* **[Mission Generation]** The previous turn will now be saved as last_turn.liberation when submitting mission results. This is often essential for debugging bug reports. **Include this file in the bug report whenever it is available.**
|
||||
* **[Modding]** Added support for the HMS Ariadne, Achilles, and Castle class.
|
||||
* **[Modding]** Added HMS Invincible to the game data as a helicopter carrier.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Flight Planning]** Fixes CAS flights not having landing waypoints.
|
||||
* **[Mission Generation]** Airbase and FOB capture is no longer blocked by grounded aircraft / helicopters.
|
||||
* **[Squadrons]** Fixed the livery for the VF-33 F-14A squadron.
|
||||
* **[Theaters]** Fixed Channel campaigns not having data for land/sea/obstacle boundaries, causing front lines to extend into forests and water. Requires a new campaign to get the fix.
|
||||
* **[UI]** Fixed an issue where manual submit of mission results did not end the mission correctly.
|
||||
|
||||
# 6.0.0
|
||||
|
||||
Saves from 5.x are not compatible with 6.0.
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.8.0.33006.
|
||||
* **[Factions]** Updated the Faction file structure. Older custom faction files will not work correctly and have to be updated to the new structure.
|
||||
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
|
||||
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. This mission type will not be planned automatically.
|
||||
* **[Flight Planning]** Air to Ground flights now have ECM enabled on lock at the join point, and SEAD/DEAD also have ECM enabled on detection and lock at ingress.
|
||||
* **[Flight Planning]** AWACS flightplan changed from orbit to a racetrack to reduce data link disconnects which were caused by blind spots as a result of the bank angle.
|
||||
* **[Flight Planning]** Added a new helo mission type: AirAssault which can be used to load and transport infantry troops from a pickup zone or a carrier to an enemy CP to capture it.
|
||||
* **[Flight Planning]** Improved the Airlift mission type so that it now can be enforced within the unit transfer dialog and implemented CTLD support. This allows user to spawn sling loadable crates at the pickup location and fly transport flights.
|
||||
* **[Mission Generation]** Added an option to fast-forward mission generation until the point of first contact (WIP).
|
||||
* **[Mission Generation]** Added performance option to not cull IADS when culling would affect how mission is played at target area.
|
||||
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
|
||||
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
|
||||
* **[Mission Generation]** Added ice halos.
|
||||
* **[Mission Generation]** Adjusted wind speeds. Wind speeds at high altitude are generally higher now.
|
||||
* **[Mission Generation]** Added turbulence. Higher in Summer and Winter, also higher at day time than at nighttime.
|
||||
* **[Modding]** Updated UH-60L mod version support to 1.3.1
|
||||
* **[Modding]** Updated the High Digit SAMs implementation and added the HQ-2 as well as the upgraded SA-2 and SA-3 Launchers from the mod. Threat range circles will now also be displayed correctly.
|
||||
* **[Modding]** Theater information such as climate properties is now moddable.
|
||||
* **[Modding]** Allow campaign designers to define default values for the economy settings (starting budget and multiplier).
|
||||
* **[Modding]** Campaigns can now optionally define their start time by including a time in the `recommended_start_date` field. There is not currently a way to override the start time in the UI.
|
||||
* **[Plugins]** Allow full support of the SkynetIADS plugin with all advanced features (connection nodes, power sources, command centers) if campaign supports it.
|
||||
* **[Plugins]** Added support for the CTLD script by ciribob with many possible customization options and updated the JTAC Autolase to the CTLD included script.
|
||||
* **[UI]** Added options to the loadout editor for setting properties such as HMD choice.
|
||||
* **[UI]** Added separate images for the different carrier types.
|
||||
* **[UI]** Add Accept/Reset buttons to Air Wing Configurator screen.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Engine]** Fixed issue that prevented some weapon types like torpedoes from being recognized.
|
||||
* **[Flight Planning]** Fixed a miscalculation of waypoint TOTs that would require time travel.
|
||||
* **[Loadouts]** Improved the range of the F-16 CAS loadout by adding bags.
|
||||
* **[Mission Generation]** AAA ground units now spawn correctly at the frontline
|
||||
* **[Mission Generation]** Fixed SA-13 incorrectly created as SA-8 Loading Unit which will not be spawned in the generated mission.
|
||||
* **[Mission Generation]** Fixed adding additional mission types for a squadron causing error messages when the mission type is not supported by the aircraft type by default
|
||||
* **[Mission Generation]** Fixed an issue where SEAD/DEAD/BAI flights fired all missiles / bombs against a single unit in a group instead of targeting the whole group.
|
||||
* **[Mission Generation]** Fixed an issue which generated the helipads at FARPs incorrectly and placed the helicopters within each other.
|
||||
* **[Mission Generation]** Fixed an issue with SEAD missions flown by the AI when using the Skynet Plugin and anti-radiation missiles (ARM). The AI now correctly engages the SAM when it comes alive instead of diving into it.
|
||||
* **[Mission Generation]** Fixed generation issue that would cause AI helicopters to get stuck after taking off from a FARP.
|
||||
* **[Mission Generation]** Fixed mission scripting error caused by control points with apostrophes in their names, such as Tha'lah.
|
||||
* **[Modding]** Campaigns that used quad zones for scenery targets will no longer load. Only circular zones were ever supported, but an implementation quirk allowed them to load in a way that would misbehave. A "No white triggerzones found" message during campaign generation is the sign of a broken campaign.
|
||||
* **[Modding]** Loadouts with invalid weapons (typically new DCS weapons not yet available in Liberation) will be ignored rather than causing an error.
|
||||
* **[Squadrons]** Fixed issue in air wing configuration that would allow squadrons to be created with no home base if no base was available.
|
||||
* **[Squadrons]** Helicopter squadrons can no longer be assigned to FOBs that are not FARPs.
|
||||
* **[UI]** Add vanilla theme weather and time of day icons
|
||||
* **[UI]** Disable player slots for non-flyable aircraft.
|
||||
* **[UI]** Fixed and issue where the liberation main exe was still running after application close.
|
||||
|
||||
# 5.2.1
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Mission Generation]** Work around DCS 2.8 bug preventing the AI from leaving their hold point.
|
||||
|
||||
# 5.2.0
|
||||
|
||||
Saves from 5.1.0 are compatible with 5.2.0
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.7.11.21408, including the new Apache AH-64D and the Syria map extension
|
||||
* **[Mission Generation]** Improved FARP Helipad handling and creation (now includes windsocks)
|
||||
* **[Modding]** Add UH-60L mod support
|
||||
* **[Modding]** Updated Community A-4E-C mod version support to 2.0.0 release. Version 1.4.2 is no longer compatible, unless the mod default loadouts are deleted/modified.
|
||||
* **[Modding]** Updated JAS-39-C mod support for v1.8.0-beta
|
||||
* **[Campaign]** Peace Spring, Vectron's Claw, Vegas Nerve, Scenic Route 2 campaign update
|
||||
* **[Campaign]** Added Tripoint Hostility campaign by Fuzzle
|
||||
* **[Campaign]** Add 3 new campaigns from Sith1144
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Mission Generation]** Fixed incorrect SA-5 and NASAMS threat range when TR destroyed. It will not count as threat anymore when the TR is dead.
|
||||
* **[Mission Generation]** Fixed "Max Threat Range" error
|
||||
* **[Mission Generation]** Fix unculled zones not updating when needed
|
||||
* **[Mission Planner]** Now allows squadron transfers to control points where the number of free slots matches exactly the expected size of the transferring squadron next turn.
|
||||
* **[Data]** Removed Fw 190 A-8 and D-9 from Germany 1940 and 1942 faction list for historical accuracy.
|
||||
* **[Data]** Updated Loadouts for Tornado GR4, F-15E and F-16C
|
||||
* **[Data]** Corrected some unit data
|
||||
* **[UI]** Fixed various UI issues (for example Scaling and HighDPI)
|
||||
* **[UI]** Typhoon GR4 and IDS images
|
||||
|
||||
# 5.1.0
|
||||
|
||||
Saves from 5.0.0 are compatible with 5.1.0
|
||||
@@ -20,6 +219,7 @@ Saves from 5.0.0 are compatible with 5.1.0
|
||||
* **[Mission Generation]** Corrected Viggen FR22 & FR24 preset channels for the DCS 2.7.9 update
|
||||
* **[Mission Generation]** Fixed the SA-5 Generator to use the P-19 FlatFace SR as a Fallback radar if the faction does not have access to the TinShield SR.
|
||||
* **[UI]** Enable / Disable the settings, save and stats actions if no game is loaded to prevent an error as these functions can only be used on a valid game.
|
||||
* **[UI]** Added missing icons for Tornado GR4, and Tornado IDS.
|
||||
|
||||
# 5.0.0
|
||||
|
||||
|
||||
26
client/.gitignore
vendored
Normal file
26
client/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.vscode/settings.json
|
||||
.vscode/tasks.json
|
||||
10
client/.vscode/launch.json
vendored
Normal file
10
client/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"name": "http://localhost:3000",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
76
client/README.md
Normal file
76
client/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# DCS Liberation Client
|
||||
|
||||
This is a React app for the front-end of DCS Liberation. It is a work in
|
||||
progress that just barely implements the map. This is not useful for players
|
||||
yet.
|
||||
|
||||
For development, set the following environment variables when launching DCS
|
||||
Liberation (the Qt UI):
|
||||
|
||||
- `CORS_ALLOW_DEBUG_SERVER=true`
|
||||
|
||||
This will allow the front-end to make requests to the server, as long as the
|
||||
front-end is running on http://localhost:3000.
|
||||
|
||||
Then, run `npm start` to start the development server. Launch the Qt UI with
|
||||
`--new-map --dev` to connect the webview to the development server, or navigate
|
||||
to http://localhost:3000 in your browser.
|
||||
|
||||
## Regenerating the API stubs
|
||||
|
||||
The backend uses FastAPI which exposes `/openapi.json`. This is consumed by
|
||||
`@rtk-query/codegen-openapi` to automatically generate the API stubs in
|
||||
`src/api/liberationApi.ts`.
|
||||
|
||||
If you make a change to the API surface the typescript API will need to be
|
||||
regenerated. To do this, first launch Liberation (to start the backend) and run
|
||||
|
||||
```powershell
|
||||
npm run regenerate-api
|
||||
```
|
||||
|
||||
See https://redux-toolkit.js.org/rtk-query/usage/code-generation for more
|
||||
information.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.<br />
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br />
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br />
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br />
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br />
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
57
client/main.js
Normal file
57
client/main.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const path = require("path");
|
||||
|
||||
const { app, BrowserWindow } = require("electron");
|
||||
const isDev = require("electron-is-dev");
|
||||
const windowStateKeeper = require("electron-window-state");
|
||||
|
||||
function createWindow() {
|
||||
let mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1000,
|
||||
defaultHeight: 800,
|
||||
});
|
||||
|
||||
// Create the browser window.
|
||||
const win = new BrowserWindow({
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
height: mainWindowState.height,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
},
|
||||
});
|
||||
mainWindowState.manage(win);
|
||||
|
||||
// and load the index.html of the app.
|
||||
// win.loadFile("index.html");
|
||||
win.loadURL(
|
||||
isDev
|
||||
? "http://localhost:3000"
|
||||
: `file://${path.join(__dirname, "../build/index.html")}`
|
||||
);
|
||||
// Open the DevTools.
|
||||
if (isDev) {
|
||||
win.webContents.openDevTools({ mode: "detach" });
|
||||
}
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(createWindow);
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
12
client/openapi-config.ts
Normal file
12
client/openapi-config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ConfigFile } from "@rtk-query/codegen-openapi";
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: "http://[::1]:16880/openapi.json",
|
||||
apiFile: "./src/api/baseApi.ts",
|
||||
apiImport: "baseApi",
|
||||
outputFile: "./src/api/_liberationApi.ts",
|
||||
exportName: "_liberationApi",
|
||||
hooks: true,
|
||||
};
|
||||
|
||||
export default config;
|
||||
36432
client/package-lock.json
generated
Normal file
36432
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
86
client/package.json
Normal file
86
client/package.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"name": "liberation-client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"homepage": ".",
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.8.5",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/jest": "^29.1.2",
|
||||
"@types/node": "^18.8.3",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"axios": "^1.1.2",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"esri-leaflet": "^3.0.8",
|
||||
"leaflet": "^1.9.2",
|
||||
"leaflet-ruler": "^1.0.0",
|
||||
"milsymbol": "^2.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-esri-leaflet": "^2.0.1",
|
||||
"react-leaflet": "^4.1.0",
|
||||
"react-redux": "^8.0.4",
|
||||
"redux-logger": "^3.0.6",
|
||||
"typescript": "~4.8.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build && generate-license-file --input package.json --output build/NOTICE",
|
||||
"regenerate-api": "rtk-query-codegen-openapi ./openapi-config.ts",
|
||||
"lint": "eslint src",
|
||||
"prepare": "eslint src && license-checker --onlyAllow \"MIT;Apache-2.0;CC0-1.0;BSD-3-Clause;ISC;Custom: https://github.com/tmcw/jsonlint;BSD-2-Clause;Hippocratic-2.1;BSD*;WTFPL\" --excludePrivatePackages --production",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"electron": "wait-on tcp:3000 && electron ."
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"leaflet-ruler.d.ts"
|
||||
],
|
||||
"prettier": {
|
||||
"endOfLine": "auto"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rtk-query/codegen-openapi": "^1.0.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^3.3.0",
|
||||
"@types/leaflet": "^1.8.0",
|
||||
"@types/redux-logger": "^3.0.9",
|
||||
"@types/websocket": "^1.0.5",
|
||||
"electron": "^21.1.0",
|
||||
"electron-is-dev": "^2.0.0",
|
||||
"generate-license-file": "^2.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"wait-on": "^6.0.1"
|
||||
},
|
||||
"jest": {
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(@?react-leaflet|axios)/)"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "identity-obj-proxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
client/public/favicon.ico
Normal file
BIN
client/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
43
client/public/index.html
Normal file
43
client/public/index.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React Redux App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
client/public/logo192.png
Normal file
BIN
client/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
BIN
client/public/logo512.png
Normal file
BIN
client/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
25
client/public/manifest.json
Normal file
25
client/public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
client/public/robots.txt
Normal file
3
client/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
12
client/src/App.test.tsx
Normal file
12
client/src/App.test.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import App from "./App";
|
||||
import { store } from "./app/store";
|
||||
import { render } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
test("app renders", () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
});
|
||||
16
client/src/App.tsx
Normal file
16
client/src/App.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import LiberationMap from "./components/liberationmap";
|
||||
import useEventStream from "./hooks/useEventSteam";
|
||||
import useInitialGameState from "./hooks/useInitialGameState";
|
||||
|
||||
function App() {
|
||||
useInitialGameState();
|
||||
useEventStream();
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<LiberationMap />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
524
client/src/api/_liberationApi.ts
Normal file
524
client/src/api/_liberationApi.ts
Normal file
@@ -0,0 +1,524 @@
|
||||
import { baseApi as api } from "./baseApi";
|
||||
|
||||
const injectedRtkApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
listControlPoints: build.query<
|
||||
ListControlPointsApiResponse,
|
||||
ListControlPointsApiArg
|
||||
>({
|
||||
query: () => ({ url: `/control-points/` }),
|
||||
}),
|
||||
getControlPointById: build.query<
|
||||
GetControlPointByIdApiResponse,
|
||||
GetControlPointByIdApiArg
|
||||
>({
|
||||
query: (queryArg) => ({ url: `/control-points/${queryArg.cpId}` }),
|
||||
}),
|
||||
controlPointDestinationInRange: build.query<
|
||||
ControlPointDestinationInRangeApiResponse,
|
||||
ControlPointDestinationInRangeApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/control-points/${queryArg.cpId}/destination-in-range`,
|
||||
params: { lat: queryArg.lat, lng: queryArg.lng },
|
||||
}),
|
||||
}),
|
||||
setControlPointDestination: build.mutation<
|
||||
SetControlPointDestinationApiResponse,
|
||||
SetControlPointDestinationApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/control-points/${queryArg.cpId}/destination`,
|
||||
method: "PUT",
|
||||
body: queryArg.body,
|
||||
}),
|
||||
}),
|
||||
clearControlPointDestination: build.mutation<
|
||||
ClearControlPointDestinationApiResponse,
|
||||
ClearControlPointDestinationApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/control-points/${queryArg.cpId}/cancel-travel`,
|
||||
method: "PUT",
|
||||
}),
|
||||
}),
|
||||
getDebugHoldZones: build.query<
|
||||
GetDebugHoldZonesApiResponse,
|
||||
GetDebugHoldZonesApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/debug/waypoint-geometries/hold/${queryArg.flightId}`,
|
||||
}),
|
||||
}),
|
||||
getDebugIpZones: build.query<
|
||||
GetDebugIpZonesApiResponse,
|
||||
GetDebugIpZonesApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/debug/waypoint-geometries/ip/${queryArg.flightId}`,
|
||||
}),
|
||||
}),
|
||||
getDebugJoinZones: build.query<
|
||||
GetDebugJoinZonesApiResponse,
|
||||
GetDebugJoinZonesApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/debug/waypoint-geometries/join/${queryArg.flightId}`,
|
||||
}),
|
||||
}),
|
||||
listFlights: build.query<ListFlightsApiResponse, ListFlightsApiArg>({
|
||||
query: (queryArg) => ({
|
||||
url: `/flights/`,
|
||||
params: { with_waypoints: queryArg.withWaypoints },
|
||||
}),
|
||||
}),
|
||||
getFlightById: build.query<GetFlightByIdApiResponse, GetFlightByIdApiArg>({
|
||||
query: (queryArg) => ({
|
||||
url: `/flights/${queryArg.flightId}`,
|
||||
params: { with_waypoints: queryArg.withWaypoints },
|
||||
}),
|
||||
}),
|
||||
getCommitBoundaryForFlight: build.query<
|
||||
GetCommitBoundaryForFlightApiResponse,
|
||||
GetCommitBoundaryForFlightApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/flights/${queryArg.flightId}/commit-boundary`,
|
||||
}),
|
||||
}),
|
||||
listFrontLines: build.query<
|
||||
ListFrontLinesApiResponse,
|
||||
ListFrontLinesApiArg
|
||||
>({
|
||||
query: () => ({ url: `/front-lines/` }),
|
||||
}),
|
||||
getFrontLineById: build.query<
|
||||
GetFrontLineByIdApiResponse,
|
||||
GetFrontLineByIdApiArg
|
||||
>({
|
||||
query: (queryArg) => ({ url: `/front-lines/${queryArg.frontLineId}` }),
|
||||
}),
|
||||
getGameState: build.query<GetGameStateApiResponse, GetGameStateApiArg>({
|
||||
query: () => ({ url: `/game/` }),
|
||||
}),
|
||||
getTerrainZones: build.query<
|
||||
GetTerrainZonesApiResponse,
|
||||
GetTerrainZonesApiArg
|
||||
>({
|
||||
query: () => ({ url: `/map-zones/terrain` }),
|
||||
}),
|
||||
listUnculledZones: build.query<
|
||||
ListUnculledZonesApiResponse,
|
||||
ListUnculledZonesApiArg
|
||||
>({
|
||||
query: () => ({ url: `/map-zones/unculled` }),
|
||||
}),
|
||||
getThreatZones: build.query<
|
||||
GetThreatZonesApiResponse,
|
||||
GetThreatZonesApiArg
|
||||
>({
|
||||
query: () => ({ url: `/map-zones/threats` }),
|
||||
}),
|
||||
getNavmesh: build.query<GetNavmeshApiResponse, GetNavmeshApiArg>({
|
||||
query: (queryArg) => ({
|
||||
url: `/navmesh/`,
|
||||
params: { for_player: queryArg.forPlayer },
|
||||
}),
|
||||
}),
|
||||
openNewFrontLinePackageDialog: build.mutation<
|
||||
OpenNewFrontLinePackageDialogApiResponse,
|
||||
OpenNewFrontLinePackageDialogApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/qt/create-package/front-line/${queryArg.frontLineId}`,
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
openNewTgoPackageDialog: build.mutation<
|
||||
OpenNewTgoPackageDialogApiResponse,
|
||||
OpenNewTgoPackageDialogApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/qt/create-package/tgo/${queryArg.tgoId}`,
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
openTgoInfoDialog: build.mutation<
|
||||
OpenTgoInfoDialogApiResponse,
|
||||
OpenTgoInfoDialogApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/qt/info/tgo/${queryArg.tgoId}`,
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
openNewControlPointPackageDialog: build.mutation<
|
||||
OpenNewControlPointPackageDialogApiResponse,
|
||||
OpenNewControlPointPackageDialogApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/qt/create-package/control-point/${queryArg.cpId}`,
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
openControlPointInfoDialog: build.mutation<
|
||||
OpenControlPointInfoDialogApiResponse,
|
||||
OpenControlPointInfoDialogApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/qt/info/control-point/${queryArg.cpId}`,
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
listSupplyRoutes: build.query<
|
||||
ListSupplyRoutesApiResponse,
|
||||
ListSupplyRoutesApiArg
|
||||
>({
|
||||
query: () => ({ url: `/supply-routes/` }),
|
||||
}),
|
||||
listTgos: build.query<ListTgosApiResponse, ListTgosApiArg>({
|
||||
query: () => ({ url: `/tgos/` }),
|
||||
}),
|
||||
getTgoById: build.query<GetTgoByIdApiResponse, GetTgoByIdApiArg>({
|
||||
query: (queryArg) => ({ url: `/tgos/${queryArg.tgoId}` }),
|
||||
}),
|
||||
listAllWaypointsForFlight: build.query<
|
||||
ListAllWaypointsForFlightApiResponse,
|
||||
ListAllWaypointsForFlightApiArg
|
||||
>({
|
||||
query: (queryArg) => ({ url: `/waypoints/${queryArg.flightId}` }),
|
||||
}),
|
||||
setWaypointPosition: build.mutation<
|
||||
SetWaypointPositionApiResponse,
|
||||
SetWaypointPositionApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/waypoints/${queryArg.flightId}/${queryArg.waypointIdx}/position`,
|
||||
method: "POST",
|
||||
body: queryArg.leafletPoint,
|
||||
}),
|
||||
}),
|
||||
getIadsNetwork: build.query<
|
||||
GetIadsNetworkApiResponse,
|
||||
GetIadsNetworkApiArg
|
||||
>({
|
||||
query: () => ({ url: `/iads-network/` }),
|
||||
}),
|
||||
getIadsConnectionsForTgo: build.query<
|
||||
GetIadsConnectionsForTgoApiResponse,
|
||||
GetIadsConnectionsForTgoApiArg
|
||||
>({
|
||||
query: (queryArg) => ({ url: `/iads-network/for-tgo/${queryArg.tgoId}` }),
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
export { injectedRtkApi as _liberationApi };
|
||||
export type ListControlPointsApiResponse =
|
||||
/** status 200 Successful Response */ ControlPoint[];
|
||||
export type ListControlPointsApiArg = void;
|
||||
export type GetControlPointByIdApiResponse =
|
||||
/** status 200 Successful Response */ ControlPoint;
|
||||
export type GetControlPointByIdApiArg = {
|
||||
cpId: string;
|
||||
};
|
||||
export type ControlPointDestinationInRangeApiResponse =
|
||||
/** status 200 Successful Response */ boolean;
|
||||
export type ControlPointDestinationInRangeApiArg = {
|
||||
cpId: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
};
|
||||
export type SetControlPointDestinationApiResponse =
|
||||
/** status 204 Successful Response */ undefined;
|
||||
export type SetControlPointDestinationApiArg = {
|
||||
cpId: string;
|
||||
body: LatLng;
|
||||
};
|
||||
export type ClearControlPointDestinationApiResponse =
|
||||
/** status 204 Successful Response */ undefined;
|
||||
export type ClearControlPointDestinationApiArg = {
|
||||
cpId: string;
|
||||
};
|
||||
export type GetDebugHoldZonesApiResponse =
|
||||
/** status 200 Successful Response */ HoldZones;
|
||||
export type GetDebugHoldZonesApiArg = {
|
||||
flightId: string;
|
||||
};
|
||||
export type GetDebugIpZonesApiResponse =
|
||||
/** status 200 Successful Response */ IpZones;
|
||||
export type GetDebugIpZonesApiArg = {
|
||||
flightId: string;
|
||||
};
|
||||
export type GetDebugJoinZonesApiResponse =
|
||||
/** status 200 Successful Response */ JoinZones;
|
||||
export type GetDebugJoinZonesApiArg = {
|
||||
flightId: string;
|
||||
};
|
||||
export type ListFlightsApiResponse =
|
||||
/** status 200 Successful Response */ Flight[];
|
||||
export type ListFlightsApiArg = {
|
||||
withWaypoints?: boolean;
|
||||
};
|
||||
export type GetFlightByIdApiResponse =
|
||||
/** status 200 Successful Response */ Flight;
|
||||
export type GetFlightByIdApiArg = {
|
||||
flightId: string;
|
||||
withWaypoints?: boolean;
|
||||
};
|
||||
export type GetCommitBoundaryForFlightApiResponse =
|
||||
/** status 200 Successful Response */ LatLng[][];
|
||||
export type GetCommitBoundaryForFlightApiArg = {
|
||||
flightId: string;
|
||||
};
|
||||
export type ListFrontLinesApiResponse =
|
||||
/** status 200 Successful Response */ FrontLine[];
|
||||
export type ListFrontLinesApiArg = void;
|
||||
export type GetFrontLineByIdApiResponse =
|
||||
/** status 200 Successful Response */ FrontLine;
|
||||
export type GetFrontLineByIdApiArg = {
|
||||
frontLineId: string;
|
||||
};
|
||||
export type GetGameStateApiResponse =
|
||||
/** status 200 Successful Response */ Game;
|
||||
export type GetGameStateApiArg = void;
|
||||
export type GetTerrainZonesApiResponse =
|
||||
/** status 200 Successful Response */ MapZones;
|
||||
export type GetTerrainZonesApiArg = void;
|
||||
export type ListUnculledZonesApiResponse =
|
||||
/** status 200 Successful Response */ UnculledZone[];
|
||||
export type ListUnculledZonesApiArg = void;
|
||||
export type GetThreatZonesApiResponse =
|
||||
/** status 200 Successful Response */ ThreatZoneContainer;
|
||||
export type GetThreatZonesApiArg = void;
|
||||
export type GetNavmeshApiResponse =
|
||||
/** status 200 Successful Response */ NavMesh;
|
||||
export type GetNavmeshApiArg = {
|
||||
forPlayer: boolean;
|
||||
};
|
||||
export type OpenNewFrontLinePackageDialogApiResponse =
|
||||
/** status 200 Successful Response */ any;
|
||||
export type OpenNewFrontLinePackageDialogApiArg = {
|
||||
frontLineId: string;
|
||||
};
|
||||
export type OpenNewTgoPackageDialogApiResponse =
|
||||
/** status 200 Successful Response */ any;
|
||||
export type OpenNewTgoPackageDialogApiArg = {
|
||||
tgoId: string;
|
||||
};
|
||||
export type OpenTgoInfoDialogApiResponse =
|
||||
/** status 200 Successful Response */ any;
|
||||
export type OpenTgoInfoDialogApiArg = {
|
||||
tgoId: string;
|
||||
};
|
||||
export type OpenNewControlPointPackageDialogApiResponse =
|
||||
/** status 200 Successful Response */ any;
|
||||
export type OpenNewControlPointPackageDialogApiArg = {
|
||||
cpId: string;
|
||||
};
|
||||
export type OpenControlPointInfoDialogApiResponse =
|
||||
/** status 200 Successful Response */ any;
|
||||
export type OpenControlPointInfoDialogApiArg = {
|
||||
cpId: string;
|
||||
};
|
||||
export type ListSupplyRoutesApiResponse =
|
||||
/** status 200 Successful Response */ SupplyRoute[];
|
||||
export type ListSupplyRoutesApiArg = void;
|
||||
export type ListTgosApiResponse = /** status 200 Successful Response */ Tgo[];
|
||||
export type ListTgosApiArg = void;
|
||||
export type GetTgoByIdApiResponse = /** status 200 Successful Response */ Tgo;
|
||||
export type GetTgoByIdApiArg = {
|
||||
tgoId: string;
|
||||
};
|
||||
export type ListAllWaypointsForFlightApiResponse =
|
||||
/** status 200 Successful Response */ Waypoint[];
|
||||
export type ListAllWaypointsForFlightApiArg = {
|
||||
flightId: string;
|
||||
};
|
||||
export type SetWaypointPositionApiResponse =
|
||||
/** status 204 Successful Response */ undefined;
|
||||
export type SetWaypointPositionApiArg = {
|
||||
flightId: string;
|
||||
waypointIdx: number;
|
||||
leafletPoint: LatLng;
|
||||
};
|
||||
export type GetIadsNetworkApiResponse =
|
||||
/** status 200 Successful Response */ IadsNetwork;
|
||||
export type GetIadsNetworkApiArg = void;
|
||||
export type GetIadsConnectionsForTgoApiResponse =
|
||||
/** status 200 Successful Response */ IadsConnection[];
|
||||
export type GetIadsConnectionsForTgoApiArg = {
|
||||
tgoId: string;
|
||||
};
|
||||
export type LatLng = {
|
||||
lat: number;
|
||||
lng: number;
|
||||
};
|
||||
export type ControlPoint = {
|
||||
id: string;
|
||||
name: string;
|
||||
blue: boolean;
|
||||
position: LatLng;
|
||||
mobile: boolean;
|
||||
destination?: LatLng;
|
||||
sidc: string;
|
||||
};
|
||||
export type ValidationError = {
|
||||
loc: (string | number)[];
|
||||
msg: string;
|
||||
type: string;
|
||||
};
|
||||
export type HttpValidationError = {
|
||||
detail?: ValidationError[];
|
||||
};
|
||||
export type HoldZones = {
|
||||
homeBubble: LatLng[][];
|
||||
targetBubble: LatLng[][];
|
||||
joinBubble: LatLng[][];
|
||||
excludedZones: LatLng[][][];
|
||||
permissibleZones: LatLng[][][];
|
||||
preferredLines: LatLng[][];
|
||||
};
|
||||
export type IpZones = {
|
||||
homeBubble: LatLng[][];
|
||||
ipBubble: LatLng[][];
|
||||
permissibleZone: LatLng[][];
|
||||
safeZones: LatLng[][][];
|
||||
};
|
||||
export type JoinZones = {
|
||||
homeBubble: LatLng[][];
|
||||
targetBubble: LatLng[][];
|
||||
ipBubble: LatLng[][];
|
||||
excludedZones: LatLng[][][];
|
||||
permissibleZones: LatLng[][][];
|
||||
preferredLines: LatLng[][];
|
||||
};
|
||||
export type Waypoint = {
|
||||
name: string;
|
||||
position: LatLng;
|
||||
altitude_ft: number;
|
||||
altitude_reference: string;
|
||||
is_movable: boolean;
|
||||
should_mark: boolean;
|
||||
include_in_path: boolean;
|
||||
timing: string;
|
||||
};
|
||||
export type Flight = {
|
||||
id: string;
|
||||
blue: boolean;
|
||||
position?: LatLng;
|
||||
sidc: string;
|
||||
waypoints?: Waypoint[];
|
||||
};
|
||||
export type FrontLine = {
|
||||
id: string;
|
||||
extents: LatLng[];
|
||||
};
|
||||
export type Tgo = {
|
||||
id: string;
|
||||
name: string;
|
||||
control_point_name: string;
|
||||
category: string;
|
||||
blue: boolean;
|
||||
position: LatLng;
|
||||
units: string[];
|
||||
threat_ranges: number[];
|
||||
detection_ranges: number[];
|
||||
dead: boolean;
|
||||
sidc: string;
|
||||
};
|
||||
export type SupplyRoute = {
|
||||
id: string;
|
||||
points: LatLng[];
|
||||
front_active: boolean;
|
||||
is_sea: boolean;
|
||||
blue: boolean;
|
||||
active_transports: string[];
|
||||
};
|
||||
export type IadsConnection = {
|
||||
id: string;
|
||||
points: LatLng[];
|
||||
node: string;
|
||||
connected: string;
|
||||
active: boolean;
|
||||
blue: boolean;
|
||||
is_power: boolean;
|
||||
};
|
||||
export type IadsNetwork = {
|
||||
advanced: boolean;
|
||||
connections: IadsConnection[];
|
||||
};
|
||||
export type ThreatZones = {
|
||||
full: LatLng[][][];
|
||||
aircraft: LatLng[][][];
|
||||
air_defenses: LatLng[][][];
|
||||
radar_sams: LatLng[][][];
|
||||
};
|
||||
export type ThreatZoneContainer = {
|
||||
blue: ThreatZones;
|
||||
red: ThreatZones;
|
||||
};
|
||||
export type NavMeshPoly = {
|
||||
poly: LatLng[][];
|
||||
threatened: boolean;
|
||||
};
|
||||
export type NavMesh = {
|
||||
polys: NavMeshPoly[];
|
||||
};
|
||||
export type NavMeshes = {
|
||||
blue: NavMesh;
|
||||
red: NavMesh;
|
||||
};
|
||||
export type UnculledZone = {
|
||||
position: LatLng;
|
||||
radius: number;
|
||||
};
|
||||
export type Game = {
|
||||
control_points: ControlPoint[];
|
||||
tgos: Tgo[];
|
||||
supply_routes: SupplyRoute[];
|
||||
front_lines: FrontLine[];
|
||||
flights: Flight[];
|
||||
iads_network: IadsNetwork;
|
||||
threat_zones: ThreatZoneContainer;
|
||||
navmeshes: NavMeshes;
|
||||
map_center?: LatLng;
|
||||
unculled_zones: UnculledZone[];
|
||||
};
|
||||
export type MapZones = {
|
||||
inclusion: LatLng[][][];
|
||||
exclusion: LatLng[][][];
|
||||
sea: LatLng[][][];
|
||||
};
|
||||
export const {
|
||||
useListControlPointsQuery,
|
||||
useGetControlPointByIdQuery,
|
||||
useControlPointDestinationInRangeQuery,
|
||||
useSetControlPointDestinationMutation,
|
||||
useClearControlPointDestinationMutation,
|
||||
useGetDebugHoldZonesQuery,
|
||||
useGetDebugIpZonesQuery,
|
||||
useGetDebugJoinZonesQuery,
|
||||
useListFlightsQuery,
|
||||
useGetFlightByIdQuery,
|
||||
useGetCommitBoundaryForFlightQuery,
|
||||
useListFrontLinesQuery,
|
||||
useGetFrontLineByIdQuery,
|
||||
useGetGameStateQuery,
|
||||
useGetTerrainZonesQuery,
|
||||
useListUnculledZonesQuery,
|
||||
useGetThreatZonesQuery,
|
||||
useGetNavmeshQuery,
|
||||
useOpenNewFrontLinePackageDialogMutation,
|
||||
useOpenNewTgoPackageDialogMutation,
|
||||
useOpenTgoInfoDialogMutation,
|
||||
useOpenNewControlPointPackageDialogMutation,
|
||||
useOpenControlPointInfoDialogMutation,
|
||||
useListSupplyRoutesQuery,
|
||||
useListTgosQuery,
|
||||
useGetTgoByIdQuery,
|
||||
useListAllWaypointsForFlightQuery,
|
||||
useSetWaypointPositionMutation,
|
||||
useGetIadsNetworkQuery,
|
||||
useGetIadsConnectionsForTgoQuery,
|
||||
} = injectedRtkApi;
|
||||
5
client/src/api/actions.ts
Normal file
5
client/src/api/actions.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Game } from "./liberationApi";
|
||||
import { createAction } from "@reduxjs/toolkit";
|
||||
|
||||
export const gameLoaded = createAction<Game>("game/loaded");
|
||||
export const gameUnloaded = createAction("game/unloaded");
|
||||
15
client/src/api/backend.ts
Normal file
15
client/src/api/backend.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import axios from "axios";
|
||||
|
||||
const backendAddr =
|
||||
new URL(window.location.toString()).searchParams.get("server") ??
|
||||
"[::1]:16880";
|
||||
|
||||
export const HTTP_URL = `http://${backendAddr}/`;
|
||||
|
||||
export const backend = axios.create({
|
||||
baseURL: HTTP_URL,
|
||||
});
|
||||
|
||||
export const WEBSOCKET_URL = `ws://${backendAddr}/eventstream`;
|
||||
|
||||
export default backend;
|
||||
7
client/src/api/baseApi.ts
Normal file
7
client/src/api/baseApi.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { HTTP_URL } from "./backend";
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
export const baseApi = createApi({
|
||||
baseQuery: fetchBaseQuery({ baseUrl: HTTP_URL }),
|
||||
endpoints: () => ({}),
|
||||
});
|
||||
8
client/src/api/combat.ts
Normal file
8
client/src/api/combat.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { LatLng } from "leaflet";
|
||||
|
||||
export default interface Combat {
|
||||
id: string;
|
||||
flight_position: LatLng | null;
|
||||
target_positions: LatLng[] | null;
|
||||
footprint: LatLng[][] | null;
|
||||
}
|
||||
49
client/src/api/combatSlice.ts
Normal file
49
client/src/api/combatSlice.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import Combat from "./combat";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
interface CombatState {
|
||||
combat: { [key: string]: Combat };
|
||||
}
|
||||
|
||||
const initialState: CombatState = {
|
||||
combat: {},
|
||||
};
|
||||
|
||||
export const combatSlice = createSlice({
|
||||
name: "combat",
|
||||
initialState,
|
||||
reducers: {
|
||||
newCombats: (state, action: PayloadAction<Combat[]>) => {
|
||||
for (const combat of action.payload) {
|
||||
state.combat[combat.id] = combat;
|
||||
}
|
||||
},
|
||||
updateCombats: (state, action: PayloadAction<Combat[]>) => {
|
||||
for (const combat of action.payload) {
|
||||
state.combat[combat.id] = combat;
|
||||
}
|
||||
},
|
||||
endCombats: (state, action: PayloadAction<string[]>) => {
|
||||
for (const cID of action.payload) {
|
||||
delete state.combat[cID];
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.combat = {};
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.combat = {};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { newCombats, updateCombats, endCombats } =
|
||||
combatSlice.actions;
|
||||
|
||||
export const selectCombat = (state: RootState) => state.combat;
|
||||
|
||||
export default combatSlice.reducer;
|
||||
44
client/src/api/controlPointsSlice.ts
Normal file
44
client/src/api/controlPointsSlice.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { ControlPoint } from "./liberationApi";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
interface ControlPointsState {
|
||||
controlPoints: { [key: string]: ControlPoint };
|
||||
}
|
||||
|
||||
const initialState: ControlPointsState = {
|
||||
controlPoints: {},
|
||||
};
|
||||
|
||||
export const controlPointsSlice = createSlice({
|
||||
name: "controlPoints",
|
||||
initialState,
|
||||
reducers: {
|
||||
updateControlPoint: (state, action: PayloadAction<ControlPoint[]>) => {
|
||||
for (const cp of action.payload) {
|
||||
state.controlPoints[cp.id] = cp;
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.controlPoints = action.payload.control_points.reduce(
|
||||
(acc: { [key: string]: ControlPoint }, curr) => {
|
||||
acc[curr.id] = curr;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.controlPoints = {};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateControlPoint } = controlPointsSlice.actions;
|
||||
|
||||
export const selectControlPoints = (state: RootState) => state.controlPoints;
|
||||
|
||||
export default controlPointsSlice.reducer;
|
||||
149
client/src/api/eventstream.tsx
Normal file
149
client/src/api/eventstream.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import { AppDispatch } from "../app/store";
|
||||
import { gameUnloaded } from "./actions";
|
||||
import Combat from "./combat";
|
||||
import { endCombats, newCombats, updateCombats } from "./combatSlice";
|
||||
import { updateControlPoint } from "./controlPointsSlice";
|
||||
import {
|
||||
deselectFlight,
|
||||
registerFlights,
|
||||
selectFlight,
|
||||
unregisterFlights,
|
||||
updateFlights,
|
||||
updateFlightPositions,
|
||||
} from "./flightsSlice";
|
||||
import {
|
||||
deleteFrontLine,
|
||||
updateFrontLine,
|
||||
} from "./frontLinesSlice";
|
||||
import reloadGameState from "./gamestate";
|
||||
import {
|
||||
ControlPoint,
|
||||
Flight,
|
||||
FrontLine,
|
||||
IadsConnection,
|
||||
NavMesh,
|
||||
Tgo,
|
||||
ThreatZones,
|
||||
UnculledZone,
|
||||
} from "./liberationApi";
|
||||
import { navMeshUpdated } from "./navMeshSlice";
|
||||
import { updateTgo } from "./tgosSlice";
|
||||
import { threatZonesUpdated } from "./threatZonesSlice";
|
||||
import { unculledZonesUpdated } from "./unculledZonesSlice";
|
||||
import { LatLng } from "leaflet";
|
||||
import { updateIadsConnection, removeIadsConnection } from "./iadsNetworkSlice";
|
||||
|
||||
interface GameUpdateEvents {
|
||||
updated_flight_positions: { [id: string]: LatLng };
|
||||
new_combats: Combat[];
|
||||
updated_combats: Combat[];
|
||||
ended_combats: string[];
|
||||
navmesh_updates: {blue: boolean, mesh: NavMesh}[];
|
||||
updated_unculled_zones: UnculledZone[];
|
||||
threat_zones_updated: {blue: boolean, zones: ThreatZones}[];
|
||||
new_flights: Flight[];
|
||||
updated_flights: Flight[];
|
||||
deleted_flights: string[];
|
||||
selected_flight: string | null;
|
||||
deselected_flight: boolean;
|
||||
updated_front_lines: FrontLine[];
|
||||
deleted_front_lines: string[];
|
||||
updated_tgos: Tgo[];
|
||||
updated_control_points: ControlPoint[];
|
||||
updated_iads: IadsConnection[];
|
||||
deleted_iads: string[];
|
||||
reset_on_map_center: LatLng | null;
|
||||
game_unloaded: boolean;
|
||||
new_turn: boolean;
|
||||
}
|
||||
|
||||
export const handleStreamedEvents = (
|
||||
dispatch: AppDispatch,
|
||||
events: GameUpdateEvents
|
||||
) => {
|
||||
if (Object.keys(events.updated_flight_positions).length) {
|
||||
dispatch(
|
||||
updateFlightPositions(Object.entries(events.updated_flight_positions))
|
||||
);
|
||||
}
|
||||
|
||||
if (events.new_combats.length > 0) {
|
||||
dispatch(newCombats(events.new_combats));
|
||||
}
|
||||
|
||||
if (events.updated_combats.length > 0) {
|
||||
dispatch(updateCombats(events.updated_combats));
|
||||
}
|
||||
|
||||
if (events.ended_combats.length > 0) {
|
||||
dispatch(endCombats(events.ended_combats));
|
||||
}
|
||||
|
||||
if (Object.keys(events.navmesh_updates).length > 0) {
|
||||
dispatch(navMeshUpdated(events.navmesh_updates));
|
||||
}
|
||||
|
||||
if (events.updated_unculled_zones.length > 0) {
|
||||
dispatch(unculledZonesUpdated(events.updated_unculled_zones));
|
||||
}
|
||||
|
||||
if (Object.keys(events.threat_zones_updated).length > 0) {
|
||||
dispatch(threatZonesUpdated(events.threat_zones_updated));
|
||||
}
|
||||
|
||||
if (events.new_flights.length > 0) {
|
||||
dispatch(registerFlights(events.new_flights));
|
||||
}
|
||||
|
||||
if (events.updated_flights.length > 0) {
|
||||
dispatch(updateFlights(events.updated_flights));
|
||||
}
|
||||
|
||||
if (events.deleted_flights.length > 0) {
|
||||
dispatch(unregisterFlights(events.deleted_flights));
|
||||
}
|
||||
|
||||
if (events.deselected_flight) {
|
||||
dispatch(deselectFlight());
|
||||
}
|
||||
|
||||
if (events.selected_flight != null) {
|
||||
dispatch(selectFlight(events.selected_flight));
|
||||
}
|
||||
|
||||
if (events.updated_front_lines.length > 0) {
|
||||
dispatch(updateFrontLine(events.updated_front_lines));
|
||||
}
|
||||
|
||||
if (events.deleted_front_lines.length > 0) {
|
||||
dispatch(deleteFrontLine(events.deleted_front_lines));
|
||||
}
|
||||
|
||||
if (events.updated_tgos.length > 0) {
|
||||
dispatch(updateTgo(events.updated_tgos));
|
||||
}
|
||||
|
||||
if (events.updated_control_points.length > 0) {
|
||||
dispatch(updateControlPoint(events.updated_control_points));
|
||||
}
|
||||
|
||||
if (events.deleted_iads.length > 0) {
|
||||
dispatch(removeIadsConnection(events.deleted_iads));
|
||||
}
|
||||
|
||||
if (events.updated_iads.length > 0) {
|
||||
dispatch(updateIadsConnection(events.updated_iads));
|
||||
}
|
||||
|
||||
if (events.reset_on_map_center != null) {
|
||||
reloadGameState(dispatch);
|
||||
}
|
||||
|
||||
if (events.game_unloaded) {
|
||||
dispatch(gameUnloaded());
|
||||
}
|
||||
|
||||
if (events.new_turn) {
|
||||
reloadGameState(dispatch, true);
|
||||
}
|
||||
};
|
||||
89
client/src/api/flightsSlice.ts
Normal file
89
client/src/api/flightsSlice.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { Flight } from "./liberationApi";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { LatLng } from "leaflet";
|
||||
|
||||
interface FlightsState {
|
||||
flights: { [id: string]: Flight };
|
||||
selected: string | null;
|
||||
}
|
||||
|
||||
const initialState: FlightsState = {
|
||||
flights: {},
|
||||
selected: null,
|
||||
};
|
||||
|
||||
export const flightsSlice = createSlice({
|
||||
name: "flights",
|
||||
initialState,
|
||||
reducers: {
|
||||
registerFlights: (state, action: PayloadAction<Flight[]>) => {
|
||||
for (const flight of action.payload) {
|
||||
if (flight.id in state.flights) {
|
||||
console.log(`Overriding flight with ID: ${flight.id}`);
|
||||
}
|
||||
state.flights[flight.id] = flight;
|
||||
}
|
||||
},
|
||||
unregisterFlights: (state, action: PayloadAction<string[]>) => {
|
||||
for (const id of action.payload) {
|
||||
delete state.flights[id];
|
||||
}
|
||||
},
|
||||
updateFlights: (state, action: PayloadAction<Flight[]>) => {
|
||||
for (const flight of action.payload) {
|
||||
state.flights[flight.id] = flight;
|
||||
}
|
||||
},
|
||||
deselectFlight: (state) => {
|
||||
state.selected = null;
|
||||
},
|
||||
selectFlight: (state, action: PayloadAction<string>) => {
|
||||
state.selected = action.payload;
|
||||
},
|
||||
updateFlightPositions: (
|
||||
state,
|
||||
action: PayloadAction<[string, LatLng][]>
|
||||
) => {
|
||||
for (const [id, position] of action.payload) {
|
||||
state.flights[id].position = position;
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.selected = null;
|
||||
state.flights = action.payload.flights.reduce(
|
||||
(acc: { [key: string]: Flight }, curr) => {
|
||||
acc[curr.id] = curr;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.selected = null;
|
||||
state.flights = {};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
registerFlights,
|
||||
unregisterFlights,
|
||||
updateFlights,
|
||||
deselectFlight,
|
||||
selectFlight,
|
||||
updateFlightPositions,
|
||||
} = flightsSlice.actions;
|
||||
|
||||
export const selectFlights = (state: RootState) => state.flights;
|
||||
export const selectSelectedFlightId = (state: RootState) =>
|
||||
state.flights.selected;
|
||||
export const selectSelectedFlight = (state: RootState) => {
|
||||
const id = state.flights.selected;
|
||||
return id ? state.flights.flights[id] : null;
|
||||
};
|
||||
|
||||
export default flightsSlice.reducer;
|
||||
50
client/src/api/frontLinesSlice.ts
Normal file
50
client/src/api/frontLinesSlice.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { FrontLine } from "./liberationApi";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
interface FrontLinesState {
|
||||
fronts: { [key: string]: FrontLine };
|
||||
}
|
||||
|
||||
const initialState: FrontLinesState = {
|
||||
fronts: {},
|
||||
};
|
||||
|
||||
export const frontLinesSlice = createSlice({
|
||||
name: "frontLines",
|
||||
initialState,
|
||||
reducers: {
|
||||
updateFrontLine: (state, action: PayloadAction<FrontLine[]>) => {
|
||||
for (const front of action.payload) {
|
||||
state.fronts[front.id] = front;
|
||||
}
|
||||
},
|
||||
deleteFrontLine: (state, action: PayloadAction<string[]>) => {
|
||||
for (const uid of action.payload) {
|
||||
delete state.fronts[uid];
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.fronts = action.payload.front_lines.reduce(
|
||||
(acc: { [key: string]: FrontLine }, curr) => {
|
||||
acc[curr.id] = curr;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.fronts = {};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateFrontLine, deleteFrontLine } =
|
||||
frontLinesSlice.actions;
|
||||
|
||||
export const selectFrontLines = (state: RootState) => state.frontLines;
|
||||
|
||||
export default frontLinesSlice.reducer;
|
||||
24
client/src/api/gamestate.ts
Normal file
24
client/src/api/gamestate.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { AppDispatch } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import backend from "./backend";
|
||||
import { Game } from "./liberationApi";
|
||||
|
||||
export default function reloadGameState(
|
||||
dispatch: AppDispatch,
|
||||
ignoreRecenter: boolean = false
|
||||
) {
|
||||
backend
|
||||
.get("/game")
|
||||
.catch((error) => console.log(`Error fetching game state: ${error}`))
|
||||
.then((response) => {
|
||||
if (response == null || response.data == null) {
|
||||
dispatch(gameUnloaded());
|
||||
return;
|
||||
}
|
||||
const game = response.data as Game;
|
||||
if (ignoreRecenter) {
|
||||
game.map_center = undefined;
|
||||
}
|
||||
dispatch(gameLoaded(game));
|
||||
});
|
||||
}
|
||||
49
client/src/api/iadsNetworkSlice.ts
Normal file
49
client/src/api/iadsNetworkSlice.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { IadsConnection} from "./_liberationApi";
|
||||
|
||||
interface IadsNetworkState {
|
||||
connections: {[key: string]: IadsConnection}
|
||||
}
|
||||
|
||||
const initialState: IadsNetworkState = {
|
||||
connections: {},
|
||||
};
|
||||
|
||||
export const IadsNetworkSlice = createSlice({
|
||||
name: "iadsNetwork",
|
||||
initialState,
|
||||
reducers: {
|
||||
updateIadsConnection: (state, action: PayloadAction<IadsConnection[]>) => {
|
||||
for (const connection of action.payload) {
|
||||
state.connections[connection.id] = connection
|
||||
}
|
||||
},
|
||||
removeIadsConnection: (state, action: PayloadAction<string[]>) => {
|
||||
for (const cID of action.payload) {
|
||||
delete state.connections[cID];
|
||||
}
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.connections = action.payload.iads_network.connections.reduce(
|
||||
(acc: { [key: string]: IadsConnection }, curr) => {
|
||||
acc[curr.id] = curr;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.connections = {};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateIadsConnection, removeIadsConnection } = IadsNetworkSlice.actions;
|
||||
|
||||
export const selectIadsNetwork = (state: RootState) => state.iadsNetwork;
|
||||
|
||||
export default IadsNetworkSlice.reducer;
|
||||
71
client/src/api/liberationApi.ts
Normal file
71
client/src/api/liberationApi.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { _liberationApi } from "./_liberationApi";
|
||||
|
||||
// See https://redux-toolkit.js.org/rtk-query/usage/automated-refetching for an
|
||||
// explanation of tag behavior.
|
||||
|
||||
export enum Tags {
|
||||
FLIGHT_PLAN = "FlightPlan",
|
||||
}
|
||||
|
||||
const LIST_ID = "LIST";
|
||||
|
||||
function providesList<R extends { id: string | number }[], T extends string>(
|
||||
resultsWithIds: R | undefined,
|
||||
tagType: T
|
||||
) {
|
||||
return resultsWithIds
|
||||
? [
|
||||
{ type: tagType, id: LIST_ID },
|
||||
...resultsWithIds.map(({ id }) => ({ type: tagType, id })),
|
||||
]
|
||||
: [{ type: tagType, id: LIST_ID }];
|
||||
}
|
||||
|
||||
export const liberationApi = _liberationApi.enhanceEndpoints({
|
||||
addTagTypes: Object.values(Tags),
|
||||
endpoints: {
|
||||
// /debug/waypoint-geometries
|
||||
getDebugHoldZones: {
|
||||
providesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
getDebugIpZones: {
|
||||
providesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
getDebugJoinZones: {
|
||||
providesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
// /flights/
|
||||
getCommitBoundaryForFlight: {
|
||||
providesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
getFlightById: {
|
||||
providesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
listFlights: {
|
||||
providesTags: (result) => providesList(result, Tags.FLIGHT_PLAN),
|
||||
},
|
||||
// /waypoints/
|
||||
listAllWaypointsForFlight: {
|
||||
providesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
setWaypointPosition: {
|
||||
invalidatesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export * from "./_liberationApi";
|
||||
32
client/src/api/mapSlice.ts
Normal file
32
client/src/api/mapSlice.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { LatLngLiteral } from "leaflet";
|
||||
|
||||
interface MapState {
|
||||
center: LatLngLiteral;
|
||||
}
|
||||
|
||||
const initialState: MapState = {
|
||||
center: { lat: 0, lng: 0 },
|
||||
};
|
||||
|
||||
const mapSlice = createSlice({
|
||||
name: "map",
|
||||
initialState: initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
if (action.payload.map_center != null) {
|
||||
state.center = action.payload.map_center;
|
||||
}
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.center = { lat: 0, lng: 0 };
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const selectMapCenter = (state: RootState) => state.map.center;
|
||||
|
||||
export default mapSlice.reducer;
|
||||
53
client/src/api/navMeshSlice.ts
Normal file
53
client/src/api/navMeshSlice.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { NavMesh, NavMeshPoly } from "./liberationApi";
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface NavMeshState {
|
||||
blue: NavMeshPoly[];
|
||||
red: NavMeshPoly[];
|
||||
}
|
||||
|
||||
const initialState: NavMeshState = {
|
||||
blue: [],
|
||||
red: [],
|
||||
};
|
||||
|
||||
export interface INavMeshUpdate {
|
||||
blue: boolean;
|
||||
mesh: NavMesh;
|
||||
}
|
||||
|
||||
const navMeshSlice = createSlice({
|
||||
name: "navmesh",
|
||||
initialState: initialState,
|
||||
reducers: {
|
||||
updated: (state, action: PayloadAction<INavMeshUpdate[]>) => {
|
||||
for (const [blue, navmesh] of Object.entries(action.payload)) {
|
||||
const data = {blue: (blue === "true"), mesh: navmesh} as unknown as INavMeshUpdate
|
||||
const polys = data.mesh.polys;
|
||||
if (data.blue) {
|
||||
state.blue = polys;
|
||||
} else {
|
||||
state.red = polys;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.blue = action.payload.navmeshes.blue.polys;
|
||||
state.red = action.payload.navmeshes.red.polys;
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.blue = [];
|
||||
state.red = [];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { updated: navMeshUpdated } = navMeshSlice.actions;
|
||||
|
||||
export const selectNavMeshes = (state: RootState) => state.navmeshes;
|
||||
|
||||
export default navMeshSlice.reducer;
|
||||
30
client/src/api/supplyRoutesSlice.ts
Normal file
30
client/src/api/supplyRoutesSlice.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { SupplyRoute } from "./liberationApi";
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
interface SupplyRoutesState {
|
||||
routes: SupplyRoute[];
|
||||
}
|
||||
|
||||
const initialState: SupplyRoutesState = {
|
||||
routes: [],
|
||||
};
|
||||
|
||||
export const supplyRoutesSlice = createSlice({
|
||||
name: "supplyRoutes",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.routes = action.payload.supply_routes;
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.routes = [];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const selectSupplyRoutes = (state: RootState) => state.supplyRoutes;
|
||||
|
||||
export default supplyRoutesSlice.reducer;
|
||||
44
client/src/api/tgosSlice.ts
Normal file
44
client/src/api/tgosSlice.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { Tgo } from "./liberationApi";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
interface TgosState {
|
||||
tgos: { [key: string]: Tgo };
|
||||
}
|
||||
|
||||
const initialState: TgosState = {
|
||||
tgos: {},
|
||||
};
|
||||
|
||||
export const tgosSlice = createSlice({
|
||||
name: "tgos",
|
||||
initialState,
|
||||
reducers: {
|
||||
updateTgo: (state, action: PayloadAction<Tgo[]>) => {
|
||||
for (const tgo of action.payload) {
|
||||
state.tgos[tgo.id] = tgo;
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.tgos = action.payload.tgos.reduce(
|
||||
(acc: { [key: string]: Tgo }, curr) => {
|
||||
acc[curr.id] = curr;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.tgos = {};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateTgo } = tgosSlice.actions;
|
||||
|
||||
export const selectTgos = (state: RootState) => state.tgos;
|
||||
|
||||
export default tgosSlice.reducer;
|
||||
61
client/src/api/threatZonesSlice.ts
Normal file
61
client/src/api/threatZonesSlice.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { ThreatZoneContainer, ThreatZones } from "./liberationApi";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
interface ThreatZonesState {
|
||||
zones: ThreatZoneContainer;
|
||||
}
|
||||
|
||||
const initialState: ThreatZonesState = {
|
||||
zones: {
|
||||
blue: {
|
||||
full: [],
|
||||
aircraft: [],
|
||||
air_defenses: [],
|
||||
radar_sams: [],
|
||||
},
|
||||
red: {
|
||||
full: [],
|
||||
aircraft: [],
|
||||
air_defenses: [],
|
||||
radar_sams: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export interface IThreatZoneUpdate {
|
||||
blue: boolean;
|
||||
zones: ThreatZones;
|
||||
}
|
||||
|
||||
export const threatZonesSlice = createSlice({
|
||||
name: "threatZonesState",
|
||||
initialState,
|
||||
reducers: {
|
||||
updated: (state, action: PayloadAction<IThreatZoneUpdate[]>) => {
|
||||
for (const [blue, zones] of Object.entries(action.payload)) {
|
||||
const data = {blue: (blue === "true"), zones: zones} as unknown as IThreatZoneUpdate
|
||||
if (data.blue) {
|
||||
state.zones.blue = data.zones;
|
||||
} else {
|
||||
state.zones.red = data.zones;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.zones = action.payload.threat_zones;
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.zones = initialState.zones;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { updated: threatZonesUpdated } = threatZonesSlice.actions;
|
||||
|
||||
export const selectThreatZones = (state: RootState) => state.threatZones;
|
||||
|
||||
export default threatZonesSlice.reducer;
|
||||
36
client/src/api/unculledZonesSlice.ts
Normal file
36
client/src/api/unculledZonesSlice.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { RootState } from "../app/store";
|
||||
import { gameLoaded, gameUnloaded } from "./actions";
|
||||
import { UnculledZone } from "./liberationApi";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
interface UnculledZonesState {
|
||||
zones: UnculledZone[];
|
||||
}
|
||||
|
||||
const initialState: UnculledZonesState = {
|
||||
zones: [],
|
||||
};
|
||||
|
||||
export const unculledZonesSlice = createSlice({
|
||||
name: "unculledZonesState",
|
||||
initialState,
|
||||
reducers: {
|
||||
updated: (state, action: PayloadAction<UnculledZone[]>) => {
|
||||
state.zones = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(gameLoaded, (state, action) => {
|
||||
state.zones = action.payload.unculled_zones;
|
||||
});
|
||||
builder.addCase(gameUnloaded, (state) => {
|
||||
state.zones = initialState.zones;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { updated: unculledZonesUpdated } = unculledZonesSlice.actions;
|
||||
|
||||
export const selectUnculledZones = (state: RootState) => state.unculledZones;
|
||||
|
||||
export default unculledZonesSlice.reducer;
|
||||
6
client/src/app/hooks.ts
Normal file
6
client/src/app/hooks.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { RootState, AppDispatch } from "./store";
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||
|
||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
41
client/src/app/store.ts
Normal file
41
client/src/app/store.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { baseApi } from "../api/baseApi";
|
||||
import combatReducer from "../api/combatSlice";
|
||||
import controlPointsReducer from "../api/controlPointsSlice";
|
||||
import flightsReducer from "../api/flightsSlice";
|
||||
import frontLinesReducer from "../api/frontLinesSlice";
|
||||
import mapReducer from "../api/mapSlice";
|
||||
import navMeshReducer from "../api/navMeshSlice";
|
||||
import supplyRoutesReducer from "../api/supplyRoutesSlice";
|
||||
import tgosReducer from "../api/tgosSlice";
|
||||
import iadsNetworkReducer from "../api/iadsNetworkSlice";
|
||||
import threatZonesReducer from "../api/threatZonesSlice";
|
||||
import unculledZonesReducer from "../api/unculledZonesSlice";
|
||||
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
combat: combatReducer,
|
||||
controlPoints: controlPointsReducer,
|
||||
flights: flightsReducer,
|
||||
frontLines: frontLinesReducer,
|
||||
map: mapReducer,
|
||||
navmeshes: navMeshReducer,
|
||||
supplyRoutes: supplyRoutesReducer,
|
||||
iadsNetwork: iadsNetworkReducer,
|
||||
tgos: tgosReducer,
|
||||
threatZones: threatZonesReducer,
|
||||
[baseApi.reducerPath]: baseApi.reducer,
|
||||
unculledZones: unculledZonesReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(baseApi.middleware),
|
||||
});
|
||||
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppThunk<ReturnType = void> = ThunkAction<
|
||||
ReturnType,
|
||||
RootState,
|
||||
unknown,
|
||||
Action<string>
|
||||
>;
|
||||
53
client/src/components/aircraft/Aircraft.test.tsx
Normal file
53
client/src/components/aircraft/Aircraft.test.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import Aircraft from "./Aircraft";
|
||||
import { render } from "@testing-library/react";
|
||||
import { Icon } from "leaflet";
|
||||
|
||||
const mockMarker = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
Marker: (props: any) => {
|
||||
mockMarker(props);
|
||||
},
|
||||
}));
|
||||
|
||||
test("grounded aircraft do not render", async () => {
|
||||
const { container } = render(
|
||||
<Aircraft
|
||||
flight={{
|
||||
id: "",
|
||||
blue: true,
|
||||
position: undefined,
|
||||
sidc: "",
|
||||
waypoints: [],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
test("in-flight aircraft render", async () => {
|
||||
render(
|
||||
<Aircraft
|
||||
flight={{
|
||||
id: "",
|
||||
blue: true,
|
||||
position: {
|
||||
lat: 10,
|
||||
lng: 20,
|
||||
},
|
||||
sidc: "foobar",
|
||||
waypoints: [],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(mockMarker).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
position: {
|
||||
lat: 10,
|
||||
lng: 20,
|
||||
},
|
||||
icon: expect.any(Icon),
|
||||
})
|
||||
);
|
||||
});
|
||||
32
client/src/components/aircraft/Aircraft.tsx
Normal file
32
client/src/components/aircraft/Aircraft.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Flight } from "../../api/liberationApi";
|
||||
import { Icon, Point } from "leaflet";
|
||||
import { Symbol } from "milsymbol";
|
||||
import { Marker } from "react-leaflet";
|
||||
|
||||
function iconForFlight(flight: Flight) {
|
||||
const symbol = new Symbol(flight.sidc, {
|
||||
size: 20,
|
||||
});
|
||||
|
||||
return new Icon({
|
||||
iconUrl: symbol.toDataURL(),
|
||||
iconAnchor: new Point(symbol.getAnchor().x, symbol.getAnchor().y),
|
||||
});
|
||||
}
|
||||
|
||||
interface AircraftProps {
|
||||
flight: Flight;
|
||||
}
|
||||
|
||||
export default function Aircraft(props: AircraftProps) {
|
||||
if (!props.flight.position) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Marker
|
||||
position={props.flight.position}
|
||||
icon={iconForFlight(props.flight)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
1
client/src/components/aircraft/index.ts
Normal file
1
client/src/components/aircraft/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./Aircraft";
|
||||
15
client/src/components/aircraftlayer/AircraftLayer.tsx
Normal file
15
client/src/components/aircraftlayer/AircraftLayer.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { selectFlights } from "../../api/flightsSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import Aircraft from "../aircraft";
|
||||
import { LayerGroup } from "react-leaflet";
|
||||
|
||||
export default function AircraftLayer() {
|
||||
const flights = useAppSelector(selectFlights).flights;
|
||||
return (
|
||||
<LayerGroup>
|
||||
{Object.values(flights).map((flight) => {
|
||||
return <Aircraft key={flight.id} flight={flight} />;
|
||||
})}
|
||||
</LayerGroup>
|
||||
);
|
||||
}
|
||||
1
client/src/components/aircraftlayer/index.ts
Normal file
1
client/src/components/aircraftlayer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./AircraftLayer";
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Tgo } from "../../api/liberationApi";
|
||||
import { selectTgos } from "../../api/tgosSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import { Circle, LayerGroup } from "react-leaflet";
|
||||
|
||||
interface TgoRangeCirclesProps {
|
||||
tgo: Tgo;
|
||||
blue: boolean;
|
||||
detection?: boolean;
|
||||
}
|
||||
|
||||
function colorFor(blue: boolean, detection: boolean) {
|
||||
if (blue) {
|
||||
return detection ? "#bb89ff" : "#0084ff";
|
||||
}
|
||||
return detection ? "#eee17b" : "#c85050";
|
||||
}
|
||||
|
||||
const TgoRangeCircles = (props: TgoRangeCirclesProps) => {
|
||||
const radii = props.detection
|
||||
? props.tgo.detection_ranges
|
||||
: props.tgo.threat_ranges;
|
||||
const color = colorFor(props.blue, props.detection === true);
|
||||
const weight = props.detection ? 1 : 2;
|
||||
|
||||
return (
|
||||
<>
|
||||
{radii.map((radius, idx) => {
|
||||
return (
|
||||
<Circle
|
||||
key={idx}
|
||||
center={props.tgo.position}
|
||||
radius={radius}
|
||||
color={color}
|
||||
fill={false}
|
||||
weight={weight}
|
||||
interactive={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface AirDefenseRangeLayerProps {
|
||||
blue: boolean;
|
||||
detection?: boolean;
|
||||
}
|
||||
|
||||
export const AirDefenseRangeLayer = (props: AirDefenseRangeLayerProps) => {
|
||||
const tgos = Object.values(useAppSelector(selectTgos).tgos);
|
||||
var tgosForSide = tgos.filter((tgo) => tgo.blue === props.blue);
|
||||
|
||||
return (
|
||||
<LayerGroup>
|
||||
{tgosForSide.map((tgo) => {
|
||||
return (
|
||||
<TgoRangeCircles key={tgo.id} tgo={tgo} {...props}></TgoRangeCircles>
|
||||
);
|
||||
})}
|
||||
</LayerGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default AirDefenseRangeLayer;
|
||||
1
client/src/components/airdefenserangelayer/index.ts
Normal file
1
client/src/components/airdefenserangelayer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./AirDefenseRangeLayer";
|
||||
53
client/src/components/combat/Combat.tsx
Normal file
53
client/src/components/combat/Combat.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import CombatModel from "../../api/combat";
|
||||
import { LatLng } from "leaflet";
|
||||
import { Polygon, Polyline } from "react-leaflet";
|
||||
|
||||
interface CombatProps {
|
||||
combat: CombatModel;
|
||||
}
|
||||
|
||||
function CombatFootprint(props: CombatProps) {
|
||||
if (!props.combat.footprint) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Polygon
|
||||
positions={props.combat.footprint}
|
||||
color="#c85050"
|
||||
interactive={false}
|
||||
fillOpacity={0.2}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CombatLines(props: CombatProps) {
|
||||
if (!props.combat.flight_position || !props.combat.target_positions) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const flightPosition: LatLng = props.combat.flight_position;
|
||||
return (
|
||||
<>
|
||||
{props.combat.target_positions.map((position, idx) => {
|
||||
return (
|
||||
<Polyline
|
||||
key={idx}
|
||||
positions={[flightPosition, position]}
|
||||
color="#c85050"
|
||||
interactive={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Combat(props: CombatProps) {
|
||||
return (
|
||||
<>
|
||||
<CombatFootprint {...props} />
|
||||
<CombatLines {...props} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
client/src/components/combat/index.ts
Normal file
1
client/src/components/combat/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./Combat";
|
||||
16
client/src/components/combatlayer/CombatLayer.tsx
Normal file
16
client/src/components/combatlayer/CombatLayer.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { selectCombat } from "../../api/combatSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import Combat from "../combat/Combat";
|
||||
import { LayerGroup } from "react-leaflet";
|
||||
|
||||
export default function CombatLayer() {
|
||||
const combats = useAppSelector(selectCombat);
|
||||
return (
|
||||
<LayerGroup>
|
||||
{Object.values(combats.combat).map((combat) => {
|
||||
return <Combat key={combat.id} combat={combat} />;
|
||||
})}
|
||||
(
|
||||
</LayerGroup>
|
||||
);
|
||||
}
|
||||
1
client/src/components/combatlayer/index.ts
Normal file
1
client/src/components/combatlayer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./CombatLayer";
|
||||
15
client/src/components/controlpoints/ControlPoint.tsx
Normal file
15
client/src/components/controlpoints/ControlPoint.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ControlPoint as ControlPointModel } from "../../api/liberationApi";
|
||||
import { MobileControlPoint } from "./MobileControlPoint";
|
||||
import { StaticControlPoint } from "./StaticControlPoint";
|
||||
|
||||
interface ControlPointProps {
|
||||
controlPoint: ControlPointModel;
|
||||
}
|
||||
|
||||
export default function ControlPoint(props: ControlPointProps) {
|
||||
if (props.controlPoint.mobile) {
|
||||
return <MobileControlPoint {...props} />;
|
||||
} else {
|
||||
return <StaticControlPoint {...props} />;
|
||||
}
|
||||
}
|
||||
22
client/src/components/controlpoints/EventHandlers.ts
Normal file
22
client/src/components/controlpoints/EventHandlers.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ControlPoint } from "../../api/_liberationApi";
|
||||
import backend from "../../api/backend";
|
||||
|
||||
function openInfoDialog(controlPoint: ControlPoint) {
|
||||
backend.post(`/qt/info/control-point/${controlPoint.id}`);
|
||||
}
|
||||
|
||||
function openNewPackageDialog(controlPoint: ControlPoint) {
|
||||
backend.post(`/qt/create-package/control-point/${controlPoint.id}`);
|
||||
}
|
||||
|
||||
export const makeLocationMarkerEventHandlers = (controlPoint: ControlPoint) => {
|
||||
return {
|
||||
click: () => {
|
||||
openInfoDialog(controlPoint);
|
||||
},
|
||||
|
||||
contextmenu: () => {
|
||||
openNewPackageDialog(controlPoint);
|
||||
},
|
||||
};
|
||||
};
|
||||
15
client/src/components/controlpoints/Icons.tsx
Normal file
15
client/src/components/controlpoints/Icons.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ControlPoint } from "../../api/_liberationApi";
|
||||
import { Icon, Point } from "leaflet";
|
||||
import { Symbol } from "milsymbol";
|
||||
|
||||
export const iconForControlPoint = (cp: ControlPoint) => {
|
||||
const symbol = new Symbol(cp.sidc, {
|
||||
size: 24,
|
||||
colorMode: "Dark",
|
||||
});
|
||||
|
||||
return new Icon({
|
||||
iconUrl: symbol.toDataURL(),
|
||||
iconAnchor: new Point(symbol.getAnchor().x, symbol.getAnchor().y),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
interface LocationTooltipTextProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const LocationTooltipText = (props: LocationTooltipTextProps) => {
|
||||
return <h3 style={{ margin: 0 }}>{props.name}</h3>;
|
||||
};
|
||||
|
||||
export default LocationTooltipText;
|
||||
228
client/src/components/controlpoints/MobileControlPoint.tsx
Normal file
228
client/src/components/controlpoints/MobileControlPoint.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import { ControlPoint } from "../../api/_liberationApi";
|
||||
import backend from "../../api/backend";
|
||||
import {
|
||||
useClearControlPointDestinationMutation,
|
||||
useSetControlPointDestinationMutation,
|
||||
} from "../../api/liberationApi";
|
||||
import { makeLocationMarkerEventHandlers } from "./EventHandlers";
|
||||
import { iconForControlPoint } from "./Icons";
|
||||
import LocationTooltipText from "./LocationTooltipText";
|
||||
import { MovementPath, MovementPathHandle } from "./MovementPath";
|
||||
import { StaticControlPoint } from "./StaticControlPoint";
|
||||
import { LatLng, Marker as LMarker, LatLngLiteral } from "leaflet";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { Marker, Tooltip } from "react-leaflet";
|
||||
|
||||
function metersToNauticalMiles(meters: number) {
|
||||
return meters * 0.000539957;
|
||||
}
|
||||
|
||||
function formatLatLng(latLng: LatLng) {
|
||||
const lat = latLng.lat.toFixed(2);
|
||||
const lng = latLng.lng.toFixed(2);
|
||||
const ns = latLng.lat >= 0 ? "N" : "S";
|
||||
const ew = latLng.lng >= 0 ? "E" : "W";
|
||||
return `${lat}°${ns} ${lng}°${ew}`;
|
||||
}
|
||||
|
||||
function destinationTooltipText(
|
||||
cp: ControlPoint,
|
||||
destinationish: LatLngLiteral,
|
||||
inRange: boolean
|
||||
) {
|
||||
const destination = new LatLng(destinationish.lat, destinationish.lng);
|
||||
const distance = metersToNauticalMiles(
|
||||
destination.distanceTo(cp.position)
|
||||
).toFixed(1);
|
||||
if (!inRange) {
|
||||
return `Out of range (${distance}nm away)`;
|
||||
}
|
||||
const dest = formatLatLng(destination);
|
||||
return `${cp.name} moving ${distance}nm to ${dest} next turn`;
|
||||
}
|
||||
|
||||
interface PrimaryMarkerProps {
|
||||
controlPoint: ControlPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* The primary control point marker. For non-mobile control points, this has
|
||||
* fairly simple behavior: it's a marker in a fixed location that can manage
|
||||
* units and can have missions planned against it.
|
||||
*
|
||||
* For mobile control points, this is a draggable marker. If the control point
|
||||
* has a destination (either because it was dragged after render, or because it
|
||||
* had a destination in the game that was loaded), the unit management and
|
||||
* mission planning behaviors are delegated to SecondaryMarker, and the primary
|
||||
* marker becomes only a destination marker. It can be dragged to change the
|
||||
* destination, and can be right clicked to cancel movement.
|
||||
*/
|
||||
function PrimaryMarker(props: PrimaryMarkerProps) {
|
||||
// We can't use normal state to update the marker tooltip or the line points
|
||||
// because if we set any state in the drag event it will re-render this
|
||||
// component and all children, interrupting dragging. Instead, keep refs to
|
||||
// the objects and mutate them directly.
|
||||
//
|
||||
// For the same reason, the path is owned by this component, because updating
|
||||
// sibling state would be messy. Lifting the state into the parent would still
|
||||
// cause this component to redraw.
|
||||
const markerRef = useRef<LMarker | null>(null);
|
||||
const pathRef = useRef<MovementPathHandle | null>(null);
|
||||
|
||||
const [hasDestination, setHasDestination] = useState<boolean>(
|
||||
props.controlPoint.destination != null
|
||||
);
|
||||
const [position, setPosition] = useState<LatLngLiteral>(
|
||||
props.controlPoint.destination
|
||||
? props.controlPoint.destination
|
||||
: props.controlPoint.position
|
||||
);
|
||||
|
||||
const setDestination = useCallback((destination: LatLng) => {
|
||||
setPosition(destination);
|
||||
setHasDestination(true);
|
||||
}, []);
|
||||
|
||||
const resetDestination = useCallback(() => {
|
||||
setPosition(props.controlPoint.position);
|
||||
setHasDestination(false);
|
||||
}, [props]);
|
||||
|
||||
const [putDestination, { isLoading }] =
|
||||
useSetControlPointDestinationMutation();
|
||||
const [cancelTravel] = useClearControlPointDestinationMutation();
|
||||
|
||||
useEffect(() => {
|
||||
markerRef.current?.setTooltipContent(
|
||||
props.controlPoint.destination
|
||||
? destinationTooltipText(
|
||||
props.controlPoint,
|
||||
props.controlPoint.destination,
|
||||
true
|
||||
)
|
||||
: ReactDOMServer.renderToString(
|
||||
<LocationTooltipText name={props.controlPoint.name} />
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const locationClickHandlers = makeLocationMarkerEventHandlers(
|
||||
props.controlPoint
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Marker
|
||||
position={position}
|
||||
icon={iconForControlPoint(props.controlPoint)}
|
||||
draggable={!isLoading}
|
||||
autoPan
|
||||
// We might draw other markers on top of the CP. The tooltips from the
|
||||
// other markers are helpful so we want to keep them, but make sure the CP
|
||||
// is always the clickable thing.
|
||||
zIndexOffset={1000}
|
||||
opacity={props.controlPoint.destination ? 0.5 : 1}
|
||||
ref={(ref) => {
|
||||
if (ref != null) {
|
||||
markerRef.current = ref;
|
||||
}
|
||||
}}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
if (!hasDestination) {
|
||||
locationClickHandlers.click();
|
||||
}
|
||||
},
|
||||
contextmenu: () => {
|
||||
if (props.controlPoint.destination) {
|
||||
cancelTravel({ cpId: props.controlPoint.id }).then(() => {
|
||||
resetDestination();
|
||||
});
|
||||
} else {
|
||||
locationClickHandlers.contextmenu();
|
||||
}
|
||||
},
|
||||
drag: (event) => {
|
||||
const destination = event.target.getLatLng();
|
||||
backend
|
||||
.get(
|
||||
`/control-points/${props.controlPoint.id}/destination-in-range?lat=${destination.lat}&lng=${destination.lng}`
|
||||
)
|
||||
.then((inRange) => {
|
||||
markerRef.current?.setTooltipContent(
|
||||
destinationTooltipText(
|
||||
props.controlPoint,
|
||||
destination,
|
||||
inRange.data
|
||||
)
|
||||
);
|
||||
});
|
||||
pathRef.current?.setDestination(destination);
|
||||
},
|
||||
dragend: async (event) => {
|
||||
const currentPosition = new LatLng(position.lat, position.lng);
|
||||
const destination = event.target.getLatLng();
|
||||
setDestination(destination);
|
||||
try {
|
||||
await putDestination({
|
||||
cpId: props.controlPoint.id,
|
||||
body: { lat: destination.lat, lng: destination.lng },
|
||||
}).unwrap();
|
||||
} catch (error) {
|
||||
console.error("setDestination failed", error);
|
||||
setDestination(currentPosition);
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tooltip />
|
||||
</Marker>
|
||||
<MovementPath
|
||||
source={props.controlPoint.position}
|
||||
destination={position}
|
||||
ref={pathRef}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface SecondaryMarkerProps {
|
||||
controlPoint: ControlPoint;
|
||||
destination: LatLngLiteral | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The secondary marker for a control point. The secondary marker will only be
|
||||
* shown when the control point has a destination set. For mobile control
|
||||
* points, the primary marker is draggable, and the secondary marker will be
|
||||
* shown at the current location iff the control point has been dragged. The
|
||||
* secondary marker is also the marker that has the normal control point
|
||||
* interaction options (mission planning and unit management).
|
||||
*/
|
||||
function SecondaryMarker(props: SecondaryMarkerProps) {
|
||||
if (!props.destination) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return <StaticControlPoint controlPoint={props.controlPoint} />;
|
||||
}
|
||||
|
||||
interface MobileControlPointProps {
|
||||
controlPoint: ControlPoint;
|
||||
}
|
||||
|
||||
export const MobileControlPoint = (props: MobileControlPointProps) => {
|
||||
return (
|
||||
<>
|
||||
<PrimaryMarker
|
||||
controlPoint={props.controlPoint}
|
||||
key={props.controlPoint.destination ? 0 : 1}
|
||||
/>
|
||||
<SecondaryMarker
|
||||
controlPoint={props.controlPoint}
|
||||
destination={props.controlPoint.destination}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
35
client/src/components/controlpoints/MovementPath.tsx
Normal file
35
client/src/components/controlpoints/MovementPath.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { LatLngLiteral, Polyline as LPolyline } from "leaflet";
|
||||
import { forwardRef, useImperativeHandle, useRef } from "react";
|
||||
import { Polyline } from "react-leaflet";
|
||||
|
||||
interface MovementPathProps {
|
||||
source: LatLngLiteral;
|
||||
destination: LatLngLiteral;
|
||||
}
|
||||
|
||||
export interface MovementPathHandle {
|
||||
setDestination: (destination: LatLngLiteral) => void;
|
||||
}
|
||||
|
||||
export const MovementPath = forwardRef<MovementPathHandle, MovementPathProps>(
|
||||
(props: MovementPathProps, ref) => {
|
||||
const lineRef = useRef<LPolyline | null>(null);
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
setDestination: (destination: LatLngLiteral) => {
|
||||
lineRef.current?.setLatLngs([props.source, destination]);
|
||||
},
|
||||
}),
|
||||
[props]
|
||||
);
|
||||
return (
|
||||
<Polyline
|
||||
positions={[props.source, props.destination]}
|
||||
weight={1}
|
||||
color="#80BA80"
|
||||
ref={lineRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
27
client/src/components/controlpoints/StaticControlPoint.tsx
Normal file
27
client/src/components/controlpoints/StaticControlPoint.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ControlPoint } from "../../api/_liberationApi";
|
||||
import { makeLocationMarkerEventHandlers } from "./EventHandlers";
|
||||
import { iconForControlPoint } from "./Icons";
|
||||
import LocationTooltipText from "./LocationTooltipText";
|
||||
import { Marker, Tooltip } from "react-leaflet";
|
||||
|
||||
interface StaticControlPointProps {
|
||||
controlPoint: ControlPoint;
|
||||
}
|
||||
|
||||
export const StaticControlPoint = (props: StaticControlPointProps) => {
|
||||
return (
|
||||
<Marker
|
||||
position={props.controlPoint.position}
|
||||
icon={iconForControlPoint(props.controlPoint)}
|
||||
// We might draw other markers on top of the CP. The tooltips from the
|
||||
// other markers are helpful so we want to keep them, but make sure the CP
|
||||
// is always the clickable thing.
|
||||
zIndexOffset={1000}
|
||||
eventHandlers={makeLocationMarkerEventHandlers(props.controlPoint)}
|
||||
>
|
||||
<Tooltip>
|
||||
<LocationTooltipText name={props.controlPoint.name} />
|
||||
</Tooltip>
|
||||
</Marker>
|
||||
);
|
||||
};
|
||||
1
client/src/components/controlpoints/index.ts
Normal file
1
client/src/components/controlpoints/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./ControlPoint";
|
||||
@@ -0,0 +1,17 @@
|
||||
import { selectControlPoints } from "../../api/controlPointsSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import ControlPoint from "../controlpoints";
|
||||
import { LayerGroup } from "react-leaflet";
|
||||
|
||||
export default function ControlPointsLayer() {
|
||||
const controlPoints = useAppSelector(selectControlPoints);
|
||||
return (
|
||||
<LayerGroup>
|
||||
{Object.values(controlPoints.controlPoints).map((controlPoint) => {
|
||||
return (
|
||||
<ControlPoint key={controlPoint.id} controlPoint={controlPoint} />
|
||||
);
|
||||
})}
|
||||
</LayerGroup>
|
||||
);
|
||||
}
|
||||
1
client/src/components/controlpointslayer/index.ts
Normal file
1
client/src/components/controlpointslayer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./ControlPointsLayer";
|
||||
@@ -0,0 +1,47 @@
|
||||
import { UnculledZone } from "../../api/liberationApi";
|
||||
import { selectUnculledZones } from "../../api/unculledZonesSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import { LayerGroup, LayersControl, Circle } from "react-leaflet";
|
||||
|
||||
interface CullingExclusionCirclesProps {
|
||||
zones: UnculledZone[];
|
||||
}
|
||||
|
||||
const CullingExclusionCircles = (props: CullingExclusionCirclesProps) => {
|
||||
return (
|
||||
<>
|
||||
<LayerGroup>
|
||||
{props.zones.map((zone, idx) => {
|
||||
return (
|
||||
<Circle
|
||||
key={idx}
|
||||
center={zone.position}
|
||||
radius={zone.radius}
|
||||
color="#b4ff8c"
|
||||
fill={false}
|
||||
interactive={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</LayerGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default function CullingExclusionZones() {
|
||||
const data = useAppSelector(selectUnculledZones).zones;
|
||||
var cez = <></>;
|
||||
|
||||
if (!data) {
|
||||
console.log("Empty response when loading culling exclusion zones");
|
||||
} else {
|
||||
cez = (
|
||||
<CullingExclusionCircles zones={data}></CullingExclusionCircles>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<LayersControl.Overlay name="Culling exclusion zones">
|
||||
{cez}
|
||||
</LayersControl.Overlay>
|
||||
);
|
||||
}
|
||||
1
client/src/components/cullingexclusionzones/index.ts
Normal file
1
client/src/components/cullingexclusionzones/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./CullingExclusionZones";
|
||||
120
client/src/components/flightplan/FlightPlan.tsx
Normal file
120
client/src/components/flightplan/FlightPlan.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { Flight } from "../../api/liberationApi";
|
||||
import { useGetCommitBoundaryForFlightQuery } from "../../api/liberationApi";
|
||||
import WaypointMarker from "../waypointmarker";
|
||||
import { ReactElement } from "react";
|
||||
import { Polyline } from "react-leaflet";
|
||||
|
||||
const BLUE_PATH = "#0084ff";
|
||||
const RED_PATH = "#c85050";
|
||||
const SELECTED_PATH = "#ffff00";
|
||||
|
||||
interface FlightPlanProps {
|
||||
flight: Flight;
|
||||
selected: boolean;
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
const pathColor = (props: FlightPlanProps) => {
|
||||
if (props.selected && props.highlight) {
|
||||
return SELECTED_PATH;
|
||||
} else if (props.flight.blue) {
|
||||
return BLUE_PATH;
|
||||
} else {
|
||||
return RED_PATH;
|
||||
}
|
||||
};
|
||||
|
||||
function FlightPlanPath(props: FlightPlanProps) {
|
||||
const color = pathColor(props);
|
||||
const waypoints = props.flight.waypoints;
|
||||
if (waypoints == null) {
|
||||
return <></>;
|
||||
}
|
||||
const points = waypoints
|
||||
.filter((waypoint) => waypoint.include_in_path)
|
||||
.map((waypoint) => waypoint.position);
|
||||
return (
|
||||
<Polyline
|
||||
positions={points}
|
||||
pathOptions={{ color: color, interactive: false }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const WaypointMarkers = (props: FlightPlanProps) => {
|
||||
if (!props.selected || props.flight.waypoints == null) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
var markers: ReactElement[] = [];
|
||||
props.flight.waypoints?.forEach((p, idx) => {
|
||||
if (p.should_mark) {
|
||||
markers.push(
|
||||
<WaypointMarker
|
||||
key={idx}
|
||||
number={idx}
|
||||
waypoint={p}
|
||||
flight={props.flight}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return <>{markers}</>;
|
||||
};
|
||||
|
||||
interface CommitBoundaryProps {
|
||||
flightId: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
function CommitBoundary(props: CommitBoundaryProps) {
|
||||
const { data, error, isLoading } = useGetCommitBoundaryForFlightQuery(
|
||||
{
|
||||
flightId: props.flightId,
|
||||
},
|
||||
// RTK Query doesn't seem to allow us to invalidate the cache from anything
|
||||
// but a mutation, but this data can be invalidated by events from the
|
||||
// websocket. Just disable the cache for this.
|
||||
//
|
||||
// This isn't perfect. It won't redraw until the component remounts. There
|
||||
// doesn't appear to be a better way.
|
||||
{ refetchOnMountOrArgChange: true }
|
||||
);
|
||||
if (isLoading) {
|
||||
return <></>;
|
||||
}
|
||||
if (error) {
|
||||
console.error(`Error loading commit boundary for ${props.flightId}`, error);
|
||||
return <></>;
|
||||
}
|
||||
if (!data) {
|
||||
console.log(
|
||||
`Null response data when loading commit boundary for ${props.flightId}`
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<Polyline positions={data} color="#ffff00" weight={1} interactive={false} />
|
||||
);
|
||||
}
|
||||
|
||||
function CommitBoundaryIfSelected(props: CommitBoundaryProps) {
|
||||
if (!props.selected) {
|
||||
return <></>;
|
||||
}
|
||||
return <CommitBoundary {...props} />;
|
||||
}
|
||||
|
||||
export default function FlightPlan(props: FlightPlanProps) {
|
||||
return (
|
||||
<>
|
||||
<FlightPlanPath {...props} />
|
||||
<WaypointMarkers {...props} />
|
||||
<CommitBoundaryIfSelected
|
||||
flightId={props.flight.id}
|
||||
selected={props.selected}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
client/src/components/flightplan/index.ts
Normal file
1
client/src/components/flightplan/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./FlightPlan";
|
||||
67
client/src/components/flightplanslayer/FlightPlansLayer.tsx
Normal file
67
client/src/components/flightplanslayer/FlightPlansLayer.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { selectFlights, selectSelectedFlight } from "../../api/flightsSlice";
|
||||
import { Flight } from "../../api/liberationApi";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import FlightPlan from "../flightplan";
|
||||
import { LayerGroup } from "react-leaflet";
|
||||
|
||||
interface FlightPlansLayerProps {
|
||||
blue: boolean;
|
||||
selectedOnly?: true;
|
||||
}
|
||||
|
||||
function SelectedFlightPlan(props: FlightPlansLayerProps) {
|
||||
const flight = useAppSelector(selectSelectedFlight);
|
||||
if (!flight) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (!props.blue) {
|
||||
// We don't currently support playing as red, so nothing to draw.
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<FlightPlan
|
||||
key={flight.id}
|
||||
flight={flight}
|
||||
selected={true}
|
||||
highlight={!props.selectedOnly}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnselectedFlightPlans(props: FlightPlansLayerProps) {
|
||||
const flightData = useAppSelector(selectFlights);
|
||||
const isNotSelected = (flight: Flight) => {
|
||||
if (flightData.selected == null) {
|
||||
return true;
|
||||
}
|
||||
return flightData.selected !== flight.id;
|
||||
};
|
||||
|
||||
if (props.selectedOnly) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.values(flightData.flights)
|
||||
.filter(isNotSelected)
|
||||
.filter((flight) => props.blue === flight.blue)
|
||||
.map((flight) => {
|
||||
return (
|
||||
<FlightPlan key={flight.id} flight={flight} selected={false} />
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FlightPlansLayer(props: FlightPlansLayerProps) {
|
||||
return (
|
||||
<LayerGroup>
|
||||
<UnselectedFlightPlans {...props} />
|
||||
<SelectedFlightPlan {...props} />
|
||||
</LayerGroup>
|
||||
);
|
||||
}
|
||||
1
client/src/components/flightplanslayer/index.ts
Normal file
1
client/src/components/flightplanslayer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./FlightPlansLayer";
|
||||
27
client/src/components/frontline/FrontLine.tsx
Normal file
27
client/src/components/frontline/FrontLine.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
FrontLine as FrontLineModel,
|
||||
useOpenNewFrontLinePackageDialogMutation,
|
||||
} from "../../api/liberationApi";
|
||||
import { Polyline } from "react-leaflet";
|
||||
|
||||
interface FrontLineProps {
|
||||
front: FrontLineModel;
|
||||
}
|
||||
|
||||
function FrontLine(props: FrontLineProps) {
|
||||
const [openNewPackageDialog] = useOpenNewFrontLinePackageDialogMutation();
|
||||
return (
|
||||
<Polyline
|
||||
positions={props.front.extents}
|
||||
weight={16}
|
||||
color={"#fe7d0a"}
|
||||
eventHandlers={{
|
||||
contextmenu: () => {
|
||||
openNewPackageDialog({ frontLineId: props.front.id });
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default FrontLine;
|
||||
1
client/src/components/frontline/index.ts
Normal file
1
client/src/components/frontline/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./FrontLine";
|
||||
15
client/src/components/frontlineslayer/FrontLinesLayer.tsx
Normal file
15
client/src/components/frontlineslayer/FrontLinesLayer.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { selectFrontLines } from "../../api/frontLinesSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import FrontLine from "../frontline";
|
||||
import { LayerGroup } from "react-leaflet";
|
||||
|
||||
export default function SupplyRoutesLayer() {
|
||||
const fronts = useAppSelector(selectFrontLines).fronts;
|
||||
return (
|
||||
<LayerGroup>
|
||||
{Object.values(fronts).map((front, idx) => {
|
||||
return <FrontLine key={idx} front={front} />;
|
||||
})}
|
||||
</LayerGroup>
|
||||
);
|
||||
}
|
||||
1
client/src/components/frontlineslayer/index.ts
Normal file
1
client/src/components/frontlineslayer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./FrontLinesLayer";
|
||||
39
client/src/components/iadsnetwork/IadsNetwork.tsx
Normal file
39
client/src/components/iadsnetwork/IadsNetwork.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { IadsConnection as IadsConnectionModel } from "../../api/liberationApi";
|
||||
import { Polyline as LPolyline } from "leaflet";
|
||||
import { useRef } from "react";
|
||||
import { Polyline, Tooltip } from "react-leaflet";
|
||||
|
||||
interface IadsConnectionProps {
|
||||
iads_connection: IadsConnectionModel;
|
||||
}
|
||||
|
||||
function IadsConnectionTooltip(props: IadsConnectionProps) {
|
||||
var status = props.iads_connection.active ? "Active" : "Inactive";
|
||||
if (props.iads_connection.is_power) {
|
||||
return <Tooltip>Power Connection ({status})</Tooltip>;
|
||||
} else {
|
||||
return <Tooltip>Communication Connection ({status})</Tooltip>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default function IadsConnection(props: IadsConnectionProps) {
|
||||
const color = props.iads_connection.is_power ? "#FFD580" : "#87CEEB";
|
||||
const path = useRef<LPolyline | null>();
|
||||
const weight = 1
|
||||
var opacity = props.iads_connection.active ? 1.0 : 0.5
|
||||
var dashArray = props.iads_connection.active ? "" : "20"
|
||||
|
||||
return (
|
||||
<Polyline
|
||||
positions={props.iads_connection.points}
|
||||
color={color}
|
||||
weight={weight}
|
||||
opacity={opacity}
|
||||
dashArray={dashArray}
|
||||
ref={(ref) => (path.current = ref)}
|
||||
>
|
||||
<IadsConnectionTooltip {...props} />
|
||||
</Polyline>
|
||||
);
|
||||
}
|
||||
1
client/src/components/iadsnetwork/index.ts
Normal file
1
client/src/components/iadsnetwork/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./IadsNetwork";
|
||||
26
client/src/components/iadsnetworklayer/IadsNetworkLayer.tsx
Normal file
26
client/src/components/iadsnetworklayer/IadsNetworkLayer.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import { LayerGroup } from "react-leaflet";
|
||||
import IadsConnection from "../iadsnetwork/IadsNetwork";
|
||||
import { selectIadsNetwork } from "../../api/iadsNetworkSlice";
|
||||
|
||||
|
||||
interface IadsNetworkLayerProps {
|
||||
blue: boolean;
|
||||
}
|
||||
|
||||
export const IadsNetworkLayer = (props: IadsNetworkLayerProps) => {
|
||||
const connections = Object.values(useAppSelector(selectIadsNetwork).connections);
|
||||
var iadsConnectionsForSide = connections.filter((connection) => connection.blue === props.blue);
|
||||
|
||||
return (
|
||||
<LayerGroup>
|
||||
{iadsConnectionsForSide.map((connection) => {
|
||||
return (
|
||||
<IadsConnection key={connection.id} iads_connection={connection} />
|
||||
);
|
||||
})}
|
||||
</LayerGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default IadsNetworkLayer;
|
||||
1
client/src/components/iadsnetworklayer/index.ts
Normal file
1
client/src/components/iadsnetworklayer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./IadsNetworkLayer";
|
||||
7
client/src/components/liberationmap/LiberationMap.css
Normal file
7
client/src/components/liberationmap/LiberationMap.css
Normal file
@@ -0,0 +1,7 @@
|
||||
@import "~leaflet/dist/leaflet.css";
|
||||
|
||||
.leaflet-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
114
client/src/components/liberationmap/LiberationMap.tsx
Normal file
114
client/src/components/liberationmap/LiberationMap.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { selectMapCenter } from "../../api/mapSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import AircraftLayer from "../aircraftlayer";
|
||||
import AirDefenseRangeLayer from "../airdefenserangelayer";
|
||||
import CombatLayer from "../combatlayer";
|
||||
import ControlPointsLayer from "../controlpointslayer";
|
||||
import CullingExclusionZones from "../cullingexclusionzones/CullingExclusionZones";
|
||||
import FlightPlansLayer from "../flightplanslayer";
|
||||
import FrontLinesLayer from "../frontlineslayer";
|
||||
import Iadsnetworklayer from "../iadsnetworklayer";
|
||||
import NavMeshLayer from "../navmesh/NavMeshLayer";
|
||||
import LeafletRuler from "../ruler/Ruler";
|
||||
import SupplyRoutesLayer from "../supplyrouteslayer";
|
||||
import TerrainZonesLayers from "../terrainzones/TerrainZonesLayers";
|
||||
import TgosLayer from "../tgoslayer/TgosLayer";
|
||||
import { CoalitionThreatZones } from "../threatzones";
|
||||
import { WaypointDebugZonesControls } from "../waypointdebugzones/WaypointDebugZonesControls";
|
||||
import "./LiberationMap.css";
|
||||
import { Map } from "leaflet";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { BasemapLayer } from "react-esri-leaflet";
|
||||
import { LayersControl, MapContainer, ScaleControl } from "react-leaflet";
|
||||
|
||||
export default function LiberationMap() {
|
||||
const map = useRef<Map>(null);
|
||||
const mapCenter = useAppSelector(selectMapCenter);
|
||||
useEffect(() => {
|
||||
map.current?.setView(mapCenter, 8, { animate: true, duration: 1 });
|
||||
});
|
||||
return (
|
||||
<MapContainer zoom={8} zoomControl={false} ref={map}>
|
||||
<ScaleControl />
|
||||
<LeafletRuler />
|
||||
<LayersControl collapsed={false}>
|
||||
<LayersControl.BaseLayer name="Imagery Clarity" checked>
|
||||
<BasemapLayer name="ImageryClarity" />
|
||||
</LayersControl.BaseLayer>
|
||||
<LayersControl.BaseLayer name="Imagery Firefly">
|
||||
<BasemapLayer name="ImageryFirefly" />
|
||||
</LayersControl.BaseLayer>
|
||||
<LayersControl.BaseLayer name="Topographic">
|
||||
<BasemapLayer name="Topographic" />
|
||||
</LayersControl.BaseLayer>
|
||||
<LayersControl.Overlay name="Control points" checked>
|
||||
<ControlPointsLayer />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Aircraft" checked>
|
||||
<AircraftLayer />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Active combat" checked>
|
||||
<CombatLayer />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Air defenses" checked>
|
||||
<TgosLayer categories={["aa"]} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Factories" checked>
|
||||
<TgosLayer categories={["factory"]} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Ships" checked>
|
||||
<TgosLayer categories={["ship"]} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Other ground objects" checked>
|
||||
<TgosLayer categories={["aa", "factory", "ship"]} exclude />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Supply routes" checked>
|
||||
<SupplyRoutesLayer />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Front lines" checked>
|
||||
<FrontLinesLayer />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Enemy SAM threat range" checked>
|
||||
<AirDefenseRangeLayer blue={false} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Enemy SAM detection range">
|
||||
<AirDefenseRangeLayer blue={false} detection />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Enemy IADS Network">
|
||||
<Iadsnetworklayer blue={false} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Allied SAM threat range">
|
||||
<AirDefenseRangeLayer blue={true} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Allied SAM detection range">
|
||||
<AirDefenseRangeLayer blue={true} detection />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Allied IADS Network">
|
||||
<Iadsnetworklayer blue={true} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Selected blue flight plan">
|
||||
<FlightPlansLayer blue={true} selectedOnly />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="All blue flight plans" checked>
|
||||
<FlightPlansLayer blue={true} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="All red flight plans">
|
||||
<FlightPlansLayer blue={false} />
|
||||
</LayersControl.Overlay>
|
||||
</LayersControl>
|
||||
<LayersControl position="topleft">
|
||||
<CoalitionThreatZones blue={true} />
|
||||
<CoalitionThreatZones blue={false} />
|
||||
<LayersControl.Overlay name="Blue navmesh">
|
||||
<NavMeshLayer blue={true} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Red navmesh">
|
||||
<NavMeshLayer blue={false} />
|
||||
</LayersControl.Overlay>
|
||||
<TerrainZonesLayers />
|
||||
<CullingExclusionZones />
|
||||
<WaypointDebugZonesControls />
|
||||
</LayersControl>
|
||||
</MapContainer>
|
||||
);
|
||||
}
|
||||
1
client/src/components/liberationmap/index.ts
Normal file
1
client/src/components/liberationmap/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./LiberationMap";
|
||||
31
client/src/components/navmesh/NavMeshLayer.tsx
Normal file
31
client/src/components/navmesh/NavMeshLayer.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { selectNavMeshes } from "../../api/navMeshSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import { LayerGroup, Polygon } from "react-leaflet";
|
||||
|
||||
interface NavMeshLayerProps {
|
||||
blue: boolean;
|
||||
}
|
||||
|
||||
export default function NavMeshLayer(props: NavMeshLayerProps) {
|
||||
const meshes = useAppSelector(selectNavMeshes);
|
||||
const mesh = props.blue ? meshes.blue : meshes.red;
|
||||
return (
|
||||
<LayerGroup>
|
||||
{mesh.map((zone, idx) => {
|
||||
return (
|
||||
<Polygon
|
||||
key={idx}
|
||||
positions={zone.poly}
|
||||
color="#000000"
|
||||
weight={1}
|
||||
fill
|
||||
fillColor={zone.threatened ? "#ff0000" : "#00ff00"}
|
||||
fillOpacity={0.1}
|
||||
noClip
|
||||
interactive={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</LayerGroup>
|
||||
);
|
||||
}
|
||||
1
client/src/components/navmesh/index.ts
Normal file
1
client/src/components/navmesh/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./NavMeshLayer";
|
||||
9
client/src/components/ruler/Ruler.css
Normal file
9
client/src/components/ruler/Ruler.css
Normal file
@@ -0,0 +1,9 @@
|
||||
@import "~leaflet-ruler/src/leaflet-ruler.css";
|
||||
|
||||
.result-tooltip{
|
||||
border-color: #435466;
|
||||
}
|
||||
|
||||
.moving-tooltip{
|
||||
border-color: #435466;
|
||||
}
|
||||
38
client/src/components/ruler/Ruler.tsx
Normal file
38
client/src/components/ruler/Ruler.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="./leaflet-ruler.d.ts" />
|
||||
import { useEffect } from "react";
|
||||
import { useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import "leaflet-ruler";
|
||||
import "./Ruler.css"
|
||||
|
||||
export default function LeafletRuler() {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
|
||||
var options = {
|
||||
position: 'topleft',
|
||||
circleMarker: { // Leaflet circle marker options for points used in this plugin
|
||||
color: 'yellow',
|
||||
radius: 2
|
||||
},
|
||||
lineStyle: { // Leaflet polyline options for lines used in this plugin
|
||||
color: 'yellow',
|
||||
dashArray: '1,6'
|
||||
},
|
||||
lengthUnit: {
|
||||
factor: 0.539956803, // from km to nm
|
||||
display: 'NM',
|
||||
decimal: 2,
|
||||
label: "Distance",
|
||||
}
|
||||
};
|
||||
if( L.control.hasOwnProperty('ruler') )
|
||||
{
|
||||
L.control.ruler(options).addTo(map);
|
||||
}
|
||||
}, [map]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user