Migration to node.js completed

This commit is contained in:
Pax1601
2023-01-22 19:05:47 +01:00
parent 6655739957
commit 4e7f6c6c39
1817 changed files with 2770 additions and 139018 deletions

11
client/TODO.txt Normal file
View File

@@ -0,0 +1,11 @@
Change cursor when in moving mode
Show airfields and enable airfield spawn
RTB
tanker
scenario dropdown
explosion
wrong name for ground units
ground units don't move
improve map zIndex
fuel is wrong (either 0 or 1, its is casting it to int somewhere)
weapons should not be selected

View File

@@ -5544,4 +5544,4 @@
"dev": true
}
}
}
}

View File

@@ -26,4 +26,4 @@
"typescript": "^4.9.4",
"watchify": "^4.0.0"
}
}
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="ai-full.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="233.6716"
inkscape:cy="300.68357"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<rect
style="stroke-width:2.5;paint-order:stroke fill markers"
id="rect851"
width="398.59067"
height="44.384548"
x="25.146276"
y="37.924564"
rx="20"
ry="20" />
<rect
style="stroke-width:2.5;paint-order:stroke fill markers"
id="rect851-9"
width="398.59067"
height="44.384548"
x="26.978836"
y="439.85379"
rx="20"
ry="20" />
<path
d="m 262.05206,159.27038 v 132.1849 H 73.216485 V 159.27038 Z M 73.216485,121.50326 c -20.83092,0 -37.76711,16.93619 -37.76711,37.76712 v 132.1849 c 0,20.83093 16.93619,37.76712 37.76711,37.76712 h 69.220035 l -6.31419,18.88355 H 92.100045 c -10.44497,0 -18.88356,8.43859 -18.88356,18.88356 0,10.44497 8.43859,18.88356 18.88356,18.88356 H 243.1685 c 10.44497,0 18.88356,-8.43859 18.88356,-18.88356 0,-10.44497 -8.43859,-18.88356 -18.88356,-18.88356 h -44.0813 l -6.31419,-18.88355 h 69.27905 c 20.83092,0 37.76711,-16.93619 37.76711,-37.76712 v -132.1849 c 0,-20.83093 -16.93619,-37.76712 -37.76711,-37.76712 z m 273.811585,0 c -15.63795,0 -28.32534,12.68739 -28.32534,28.32534 v 207.71913 c 0,15.63795 12.68739,28.32534 28.32534,28.32534 h 37.76711 c 15.63795,0 28.32534,-12.68739 28.32534,-28.32534 V 149.8286 c 0,-15.63795 -12.68739,-28.32534 -28.32534,-28.32534 z m 9.44178,37.76712 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 z m -9.44178,47.20889 c 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 z m 18.88356,132.18491 c -10.44497,0 -18.88356,-8.43859 -18.88356,-18.88356 0,-10.44497 8.43859,-18.88356 18.88356,-18.88356 10.44496,0 18.88355,8.43859 18.88355,18.88356 0,10.44497 -8.43859,18.88356 -18.88355,18.88356 z"
id="path2"
style="stroke-width:0.590111" />
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="ai-hidden.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="233.6716"
inkscape:cy="300.68357"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 262.05206,159.27038 v 132.1849 H 73.216485 V 159.27038 Z M 73.216485,121.50326 c -20.83092,0 -37.76711,16.93619 -37.76711,37.76712 v 132.1849 c 0,20.83093 16.93619,37.76712 37.76711,37.76712 h 69.220035 l -6.31419,18.88355 H 92.100045 c -10.44497,0 -18.88356,8.43859 -18.88356,18.88356 0,10.44497 8.43859,18.88356 18.88356,18.88356 H 243.1685 c 10.44497,0 18.88356,-8.43859 18.88356,-18.88356 0,-10.44497 -8.43859,-18.88356 -18.88356,-18.88356 h -44.0813 l -6.31419,-18.88355 h 69.27905 c 20.83092,0 37.76711,-16.93619 37.76711,-37.76712 v -132.1849 c 0,-20.83093 -16.93619,-37.76712 -37.76711,-37.76712 z m 273.811585,0 c -15.63795,0 -28.32534,12.68739 -28.32534,28.32534 v 207.71913 c 0,15.63795 12.68739,28.32534 28.32534,28.32534 h 37.76711 c 15.63795,0 28.32534,-12.68739 28.32534,-28.32534 V 149.8286 c 0,-15.63795 -12.68739,-28.32534 -28.32534,-28.32534 z m 9.44178,37.76712 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 z m -9.44178,47.20889 c 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 z m 18.88356,132.18491 c -10.44497,0 -18.88356,-8.43859 -18.88356,-18.88356 0,-10.44497 8.43859,-18.88356 18.88356,-18.88356 10.44496,0 18.88355,8.43859 18.88355,18.88356 0,10.44497 -8.43859,18.88356 -18.88355,18.88356 z"
id="path2"
style="stroke-width:0.590111" />
<rect
style="stroke:#fffffc;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect851-9"
width="398.59067"
height="44.384548"
x="-224.52609"
y="315.65387"
rx="20"
ry="20"
transform="rotate(-45)" />
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="ai-none.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="233.6716"
inkscape:cy="300.68357"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 262.05206,159.27038 v 132.1849 H 73.216485 V 159.27038 Z M 73.216485,121.50326 c -20.83092,0 -37.76711,16.93619 -37.76711,37.76712 v 132.1849 c 0,20.83093 16.93619,37.76712 37.76711,37.76712 h 69.220035 l -6.31419,18.88355 H 92.100045 c -10.44497,0 -18.88356,8.43859 -18.88356,18.88356 0,10.44497 8.43859,18.88356 18.88356,18.88356 H 243.1685 c 10.44497,0 18.88356,-8.43859 18.88356,-18.88356 0,-10.44497 -8.43859,-18.88356 -18.88356,-18.88356 h -44.0813 l -6.31419,-18.88355 h 69.27905 c 20.83092,0 37.76711,-16.93619 37.76711,-37.76712 v -132.1849 c 0,-20.83093 -16.93619,-37.76712 -37.76711,-37.76712 z m 273.811585,0 c -15.63795,0 -28.32534,12.68739 -28.32534,28.32534 v 207.71913 c 0,15.63795 12.68739,28.32534 28.32534,28.32534 h 37.76711 c 15.63795,0 28.32534,-12.68739 28.32534,-28.32534 V 149.8286 c 0,-15.63795 -12.68739,-28.32534 -28.32534,-28.32534 z m 9.44178,37.76712 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 z m -9.44178,47.20889 c 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 z m 18.88356,132.18491 c -10.44497,0 -18.88356,-8.43859 -18.88356,-18.88356 0,-10.44497 8.43859,-18.88356 18.88356,-18.88356 10.44496,0 18.88355,8.43859 18.88355,18.88356 0,10.44497 -8.43859,18.88356 -18.88355,18.88356 z"
id="path2"
style="stroke-width:0.590111" />
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="a-ipartial.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="233.6716"
inkscape:cy="300.68357"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<rect
style="stroke-width:2.5;paint-order:stroke fill markers"
id="rect851-9"
width="398.59067"
height="44.384548"
x="26.978836"
y="439.85379"
rx="20"
ry="20" />
<path
d="m 262.05206,159.27038 v 132.1849 H 73.216485 V 159.27038 Z M 73.216485,121.50326 c -20.83092,0 -37.76711,16.93619 -37.76711,37.76712 v 132.1849 c 0,20.83093 16.93619,37.76712 37.76711,37.76712 h 69.220035 l -6.31419,18.88355 H 92.100045 c -10.44497,0 -18.88356,8.43859 -18.88356,18.88356 0,10.44497 8.43859,18.88356 18.88356,18.88356 H 243.1685 c 10.44497,0 18.88356,-8.43859 18.88356,-18.88356 0,-10.44497 -8.43859,-18.88356 -18.88356,-18.88356 h -44.0813 l -6.31419,-18.88355 h 69.27905 c 20.83092,0 37.76711,-16.93619 37.76711,-37.76712 v -132.1849 c 0,-20.83093 -16.93619,-37.76712 -37.76711,-37.76712 z m 273.811585,0 c -15.63795,0 -28.32534,12.68739 -28.32534,28.32534 v 207.71913 c 0,15.63795 12.68739,28.32534 28.32534,28.32534 h 37.76711 c 15.63795,0 28.32534,-12.68739 28.32534,-28.32534 V 149.8286 c 0,-15.63795 -12.68739,-28.32534 -28.32534,-28.32534 z m 9.44178,37.76712 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 z m -9.44178,47.20889 c 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 z m 18.88356,132.18491 c -10.44497,0 -18.88356,-8.43859 -18.88356,-18.88356 0,-10.44497 8.43859,-18.88356 18.88356,-18.88356 10.44496,0 18.88355,8.43859 18.88355,18.88356 0,10.44497 -8.43859,18.88356 -18.88355,18.88356 z"
id="path2"
style="stroke-width:0.590111" />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M384 96V320H64L64 96H384zM64 32C28.7 32 0 60.7 0 96V320c0 35.3 28.7 64 64 64H181.3l-10.7 32H96c-17.7 0-32 14.3-32 32s14.3 32 32 32H352c17.7 0 32-14.3 32-32s-14.3-32-32-32H277.3l-10.7-32H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm464 0c-26.5 0-48 21.5-48 48V432c0 26.5 21.5 48 48 48h64c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H528zm16 64h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H544c-8.8 0-16-7.2-16-16s7.2-16 16-16zm-16 80c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H544c-8.8 0-16-7.2-16-16zm32 224c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32z"/></svg>

After

Width:  |  Height:  |  Size: 832 B

View File

@@ -1 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M624 448H16c-8.84 0-16 7.16-16 16v32c0 8.84 7.16 16 16 16h608c8.84 0 16-7.16 16-16v-32c0-8.84-7.16-16-16-16zM80.55 341.27c6.28 6.84 15.1 10.72 24.33 10.71l130.54-.18a65.62 65.62 0 0 0 29.64-7.12l290.96-147.65c26.74-13.57 50.71-32.94 67.02-58.31 18.31-28.48 20.3-49.09 13.07-63.65-7.21-14.57-24.74-25.27-58.25-27.45-29.85-1.94-59.54 5.92-86.28 19.48l-98.51 49.99-218.7-82.06a17.799 17.799 0 0 0-18-1.11L90.62 67.29c-10.67 5.41-13.25 19.65-5.17 28.53l156.22 98.1-103.21 52.38-72.35-36.47a17.804 17.804 0 0 0-16.07.02L9.91 230.22c-10.44 5.3-13.19 19.12-5.57 28.08l76.21 82.97z"/></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 640 512"
version="1.1"
id="svg4"
sodipodi:docname="climb.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.8125"
inkscape:cx="465.84615"
inkscape:cy="22.769231"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) -->
<path
d="m 80.55,407.27 c 6.28,6.84 15.1,10.72 24.33,10.71 l 130.54,-0.18 a 65.62,65.62 0 0 0 29.64,-7.12 L 556.02,263.03 c 26.74,-13.57 50.71,-32.94 67.02,-58.31 18.31,-28.48 20.3,-49.09 13.07,-63.65 -7.21,-14.57 -24.74,-25.27 -58.25,-27.45 -29.85,-1.94 -59.54,5.92 -86.28,19.48 l -98.51,49.99 -218.7,-82.06 a 17.799,17.799 0 0 0 -18,-1.11 l -65.75,33.37 c -10.67,5.41 -13.25,19.65 -5.17,28.53 l 156.22,98.1 -103.21,52.38 -72.35,-36.47 a 17.804,17.804 0 0 0 -16.07,0.02 L 9.91,296.22 c -10.44,5.3 -13.19,19.12 -5.57,28.08 z"
id="path846" />
</svg>

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 512 512"
version="1.1"
id="svg4"
sodipodi:docname="dead-hidden.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.8125"
inkscape:cx="101.53846"
inkscape:cy="209.84615"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 336,333.63317 c 29.25,-22.32836 48,-56.55432 48,-95.01779 0,-67.2024 -57.3,-121.6923 -128,-121.6923 -70.7,0 -128,54.4899 -128,121.6923 0,38.40914 18.75,72.68943 48,95.01779 0,0.21731 0,0.38029 0,0.5976 V 369 c 0,14.39663 10.75,26.07692 24,26.07692 h 24 V 369 c 0,-4.78077 3.6,-8.69231 8,-8.69231 4.4,0 8,3.91154 8,8.69231 v 26.07692 h 32 V 369 c 0,-4.78077 3.6,-8.69231 8,-8.69231 4.4,0 8,3.91154 8,8.69231 v 26.07692 h 24 c 13.25,0 24,-11.68029 24,-26.07692 v -34.76923 c 0,-0.21731 0,-0.38029 0,-0.5976 z M 240,256 c 0,19.1774 -14.35,34.76923 -32,34.76923 -17.65,0 -32,-15.59183 -32,-34.76923 0,-19.1774 14.35,-34.76923 32,-34.76923 17.65,0 32,15.59183 32,34.76923 z m 64,34.76923 c -17.65,0 -32,-15.59183 -32,-34.76923 0,-19.1774 14.35,-34.76923 32,-34.76923 17.65,0 32,15.59183 32,34.76923 0,19.1774 -14.35,34.76923 -32,34.76923 z"
id="path2"
style="stroke-width:0.521186" />
<rect
style="stroke:#fffffc;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect851-9"
width="398.59067"
height="44.384548"
x="-197.55475"
y="339.84641"
rx="20"
ry="20"
transform="rotate(-45)" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 512 512"
version="1.1"
id="svg4"
sodipodi:docname="dead.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.8125"
inkscape:cx="101.53846"
inkscape:cy="209.84615"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 336,333.63317 c 29.25,-22.32836 48,-56.55432 48,-95.01779 0,-67.2024 -57.3,-121.6923 -128,-121.6923 -70.7,0 -128,54.4899 -128,121.6923 0,38.40914 18.75,72.68943 48,95.01779 0,0.21731 0,0.38029 0,0.5976 V 369 c 0,14.39663 10.75,26.07692 24,26.07692 h 24 V 369 c 0,-4.78077 3.6,-8.69231 8,-8.69231 4.4,0 8,3.91154 8,8.69231 v 26.07692 h 32 V 369 c 0,-4.78077 3.6,-8.69231 8,-8.69231 4.4,0 8,3.91154 8,8.69231 v 26.07692 h 24 c 13.25,0 24,-11.68029 24,-26.07692 v -34.76923 c 0,-0.21731 0,-0.38029 0,-0.5976 z M 240,256 c 0,19.1774 -14.35,34.76923 -32,34.76923 -17.65,0 -32,-15.59183 -32,-34.76923 0,-19.1774 14.35,-34.76923 32,-34.76923 17.65,0 32,15.59183 32,34.76923 z m 64,34.76923 c -17.65,0 -32,-15.59183 -32,-34.76923 0,-19.1774 14.35,-34.76923 32,-34.76923 17.65,0 32,15.59183 32,34.76923 0,19.1774 -14.35,34.76923 -32,34.76923 z"
id="path2"
style="stroke-width:0.521186" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M624 448H16c-8.84 0-16 7.16-16 16v32c0 8.84 7.16 16 16 16h608c8.84 0 16-7.16 16-16v-32c0-8.84-7.16-16-16-16zM44.81 205.66l88.74 80a62.607 62.607 0 0 0 25.47 13.93l287.6 78.35c26.48 7.21 54.56 8.72 81 1.36 29.67-8.27 43.44-21.21 47.25-35.71 3.83-14.5-1.73-32.71-23.37-54.96-19.28-19.82-44.35-32.79-70.83-40l-97.51-26.56L282.8 30.22c-1.51-5.81-5.95-10.35-11.66-11.91L206.05.58c-10.56-2.88-20.9 5.32-20.71 16.44l47.92 164.21-102.2-27.84-27.59-67.88c-1.93-4.89-6.01-8.57-11.02-9.93L52.72 64.75c-10.34-2.82-20.53 5-20.72 15.88l.23 101.78c.19 8.91 6.03 17.34 12.58 23.25z"/></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 640 512"
version="1.1"
id="svg4"
sodipodi:docname="descend.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.8125"
inkscape:cx="313.23077"
inkscape:cy="312"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) -->
<path
d="m 52.81,265.66 88.74,80 a 62.607,62.607 0 0 0 25.47,13.93 l 287.6,78.35 c 26.48,7.21 54.56,8.72 81,1.36 29.67,-8.27 43.44,-21.21 47.25,-35.71 3.83,-14.5 -1.73,-32.71 -23.37,-54.96 -19.28,-19.82 -44.35,-32.79 -70.83,-40 L 391.16,282.07 290.8,90.22 C 289.29,84.41 284.85,79.87 279.14,78.31 L 214.05,60.58 c -10.56,-2.88 -20.9,5.32 -20.71,16.44 l 47.92,164.21 -102.2,-27.84 -27.59,-67.88 c -1.93,-4.89 -6.01,-8.57 -11.02,-9.93 L 60.72,124.75 c -10.34,-2.82 -20.53,5 -20.72,15.88 l 0.23,101.78 c 0.19,8.91 6.03,17.34 12.58,23.25 z"
id="path827" />
</svg>

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="user-full.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="233.67159"
inkscape:cy="300.68356"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 223.51472,258.30254 c 41.41184,0 74.97476,-33.56293 74.97476,-74.97477 0,-41.41184 -33.56292,-74.97476 -74.97476,-74.97476 -41.41185,0 -74.97476,33.56292 -74.97476,74.97476 0,41.41184 33.56291,74.97477 74.97476,74.97477 z m -26.76834,28.11553 c -57.69542,0 -104.437495,46.74208 -104.437495,104.43749 0,9.60615 7.790345,17.39649 17.396485,17.39649 h 227.61869 c 9.60614,0 17.39649,-7.79034 17.39649,-17.39649 0,-57.69541 -46.74208,-104.43749 -104.4375,-104.43749 z"
id="path2"
style="stroke-width:0.58574" />
<rect
style="stroke-width:2.5;paint-order:stroke fill markers"
id="rect851"
width="398.59067"
height="44.384548"
x="25.146276"
y="37.924564"
rx="20"
ry="20" />
<rect
style="stroke-width:2.5;paint-order:stroke fill markers"
id="rect851-9"
width="398.59067"
height="44.384548"
x="26.978836"
y="439.85379"
rx="20"
ry="20" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="user-hidden.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="233.67159"
inkscape:cy="300.68356"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 223.51472,258.30254 c 41.41184,0 74.97476,-33.56293 74.97476,-74.97477 0,-41.41184 -33.56292,-74.97476 -74.97476,-74.97476 -41.41185,0 -74.97476,33.56292 -74.97476,74.97476 0,41.41184 33.56291,74.97477 74.97476,74.97477 z m -26.76834,28.11553 c -57.69542,0 -104.437495,46.74208 -104.437495,104.43749 0,9.60615 7.790345,17.39649 17.396485,17.39649 h 227.61869 c 9.60614,0 17.39649,-7.79034 17.39649,-17.39649 0,-57.69541 -46.74208,-104.43749 -104.4375,-104.43749 z"
id="path2"
style="stroke-width:0.58574" />
<rect
style="stroke-width:20;paint-order:stroke fill markers;stroke:#fffffc;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
id="rect851-9"
width="398.59067"
height="44.384548"
x="-237.39584"
y="307.60056"
rx="20"
ry="20"
transform="rotate(-45)" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="user-none.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="233.67159"
inkscape:cy="300.68356"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 223.51472,258.30254 c 41.41184,0 74.97476,-33.56293 74.97476,-74.97477 0,-41.41184 -33.56292,-74.97476 -74.97476,-74.97476 -41.41185,0 -74.97476,33.56292 -74.97476,74.97476 0,41.41184 33.56291,74.97477 74.97476,74.97477 z m -26.76834,28.11553 c -57.69542,0 -104.437495,46.74208 -104.437495,104.43749 0,9.60615 7.790345,17.39649 17.396485,17.39649 h 227.61869 c 9.60614,0 17.39649,-7.79034 17.39649,-17.39649 0,-57.69541 -46.74208,-104.43749 -104.4375,-104.43749 z"
id="path2"
style="stroke-width:0.58574" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="user-partial.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="233.67159"
inkscape:cy="300.68356"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path
d="m 223.51472,258.30254 c 41.41184,0 74.97476,-33.56293 74.97476,-74.97477 0,-41.41184 -33.56292,-74.97476 -74.97476,-74.97476 -41.41185,0 -74.97476,33.56292 -74.97476,74.97476 0,41.41184 33.56291,74.97477 74.97476,74.97477 z m -26.76834,28.11553 c -57.69542,0 -104.437495,46.74208 -104.437495,104.43749 0,9.60615 7.790345,17.39649 17.396485,17.39649 h 227.61869 c 9.60614,0 17.39649,-7.79034 17.39649,-17.39649 0,-57.69541 -46.74208,-104.43749 -104.4375,-104.43749 z"
id="path2"
style="stroke-width:0.58574" />
<rect
style="stroke-width:2.5;paint-order:stroke fill markers"
id="rect851-9"
width="398.59067"
height="44.384548"
x="26.978836"
y="439.85379"
rx="20"
ry="20" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="weapon-hidden.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="140.55107"
inkscape:cy="295.46185"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<ellipse
style="stroke:#fffffc;stroke-width:20;paint-order:stroke fill markers"
id="path1385"
cx="341.43585"
cy="18.914345"
rx="181.45448"
ry="29.154556"
transform="rotate(45)" />
<path
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 100.09292,126.05632 28.85702,-28.857023 25.34012,25.340123 4.35147,41.90701 z"
id="path1519" />
<path
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 100.09292,126.05632 -28.857017,28.85702 25.340121,25.34012 41.907006,4.35147 z"
id="path1519-5" />
<rect
style="stroke:#fffffc;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect851-9"
width="398.59067"
height="44.384548"
x="-218.37225"
y="300.88464"
rx="20"
ry="20"
transform="rotate(-45)" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="weapon-none.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="140.55107"
inkscape:cy="295.46185"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<ellipse
style="stroke:#fffffc;stroke-width:20;paint-order:stroke fill markers"
id="path1385"
cx="341.43585"
cy="18.914345"
rx="181.45448"
ry="29.154556"
transform="rotate(45)" />
<path
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 100.09292,126.05632 28.85702,-28.857023 25.34012,25.340123 4.35147,41.90701 z"
id="path1519" />
<path
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 100.09292,126.05632 -28.857017,28.85702 25.340121,25.34012 41.907006,4.35147 z"
id="path1519-5" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448 512"
version="1.1"
id="svg4"
sodipodi:docname="weapon-partial.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.1490485"
inkscape:cx="140.55107"
inkscape:cy="295.46185"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<rect
style="stroke-width:2.5;paint-order:stroke fill markers"
id="rect851-9"
width="398.59067"
height="44.384548"
x="26.978836"
y="439.85379"
rx="20"
ry="20" />
<ellipse
style="stroke:#fffffc;stroke-width:20;paint-order:stroke fill markers"
id="path1385"
cx="341.43585"
cy="18.914345"
rx="181.45448"
ry="29.154556"
transform="rotate(45)" />
<path
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 100.09292,126.05632 28.85702,-28.857023 25.34012,25.340123 4.35147,41.90701 z"
id="path1519" />
<path
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 100.09292,126.05632 -28.857017,28.85702 25.340121,25.34012 41.907006,4.35147 z"
id="path1519-5" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
.airbasemarker-container {
height: 60px;
width: 60px;
left: -30px;
top: -30px;
border: 1px transparent solid;
position: absolute;
}
.airbasemarker-icon {
height: 60px;
width: 60px;
left: 0px;
top: 0px;
display: block;
position: absolute;
filter: drop-shadow(1px 1px 0 white) drop-shadow(1px -1px 0 white) drop-shadow(-1px 1px 0 white) drop-shadow(-1px -1px 0 white);
}
.airbasemarker-icon-blue {
filter: invert(69%) sepia(84%) saturate(264%) hue-rotate(162deg) brightness(100%) contrast(102%) drop-shadow(0px 1px 0 #000A) drop-shadow(1px 0px 0 #000A) drop-shadow(-1px 0px 0 #000A) drop-shadow(0px -1px 0 #000A);
}
.airbasemarker-icon-red {
filter: invert(68%) sepia(85%) saturate(2385%) hue-rotate(313deg) brightness(108%) contrast(102%) drop-shadow(0px 1px 0 #000A) drop-shadow(1px 0px 0 #000A) drop-shadow(-1px 0px 0 #000A) drop-shadow(0px -1px 0 #000A);
}
.airbasemarker-name {
bottom: -20px;
position: absolute;
text-align: center;
font: 800 14px Arial;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}

View File

@@ -1,4 +1,4 @@
.olympus-button{
.olympus-button {
width: 24px;
height: 24px;
background-color: transparent;
@@ -7,16 +7,11 @@
align-items: center;
}
.olympus-button img{
.olympus-button img {
width: 24px;
height: 24px;
}
.olympus-button:hover{
}
.olympus-button:active{
}
.olympus-button:hover {}
.olympus-button:active {}

View File

@@ -1,7 +1,25 @@
.olympus-status-disconnected{
color: red;
#status-string {
font-size: 14px;
}
.olympus-status-connected{
color: #00FF00;
.olympus-status-disconnected::after {
content: "";
position: absolute;
right: 5px;
top: 5px;
border-radius: 50%;
width: 20px;
height: 20px;
background-color: red;
}
.olympus-status-connected::after {
content: "";
position: absolute;
right: 5px;
top: 5px;
border-radius: 50%;
width: 20px;
height: 20px;
background-color: 00FF00;
}

View File

@@ -3,14 +3,14 @@
background-color: var(--background-color-dark);
font-size: 12px;
transition: bottom 0.2s;
border-radius: 15px;
font-family: "Lucida Console", "Courier New", monospace !important;
border-radius: 15px;
font-family: "Lucida Console", "Courier New", monospace !important;
box-shadow: 0px 2px 5px #000A;
}
.olympus-panel table {
margin: 5px;
width: 100%;
margin: 5px;
width: 100%;
}
.olympus-panel-title {
@@ -30,4 +30,4 @@
font-size: 12px;
color: var(--text-color);
margin: 5px;
}
}

View File

@@ -15,8 +15,8 @@
border-radius: 50%;
background-color: var(--active-coalition-color);
opacity: 0.8;
-webkit-mask: radial-gradient(transparent 30%,#000 31%);
mask: radial-gradient(transparent 30%,#000 31%);
-webkit-mask: radial-gradient(transparent 30%, #000 31%);
mask: radial-gradient(transparent 30%, #000 31%);
transition: background-color 0.2s;
}
@@ -94,11 +94,11 @@
-webkit-transition: 0.2s;
transition: 0.2s;
border-radius: 50%;
}
input:checked + .olympus-selection-wheel-switch:before {
input:checked+.olympus-selection-wheel-switch:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
}

View File

@@ -6,58 +6,58 @@
@import url("connectionstatuspanel.css");
@import url("button.css");
@import url("unitcontrolpanel.css");
@import url("visibilitycontrolpanel.css");
@import url("airbasemarker.css");
/* Variables definitions */
:root {
--background-color-dark:#202831;
--background-color-light:#aaaaaa;
--title-color:#d3e9ff;
--text-color:white;
--blue-coalition-color: #2196F3;
--red-coalition-color: #f32121;
--background-color-dark: #202831;
--background-color-light: #aaaaaa;
--title-color: #d3e9ff;
--text-color: white;
--blue-coalition-color: #2196F3;
--red-coalition-color: #f32121;
--neutral-coalition-color: #AAAAAA;
--active-coalition-color: var(--blue-coalition-color);
--highlight-color: #FFFFFFAA;
--active-coalition-color: var(--blue-coalition-color);
--highlight-color: #FFFFFFAA;
}
/* Page style */
body {
padding: 0;
margin: 0;
}
html, body {
html,
body {
height: 100%;
width: 100%;
}
#map-container
{
height: 100%;
#map-container {
height: 100%;
width: 100%;
min-width: 820px;
}
#unit-info-panel {
position: fixed;
position: fixed;
height: 100px;
width: 800px;
left: 10px;
bottom: 10px;
width: 800px;
left: 10px;
bottom: 10px;
z-index: 1000;
display: flex;
}
#map-source-dropdown{
#map-source-dropdown {
position: absolute;
left: 10px;
top: 10px;
width: 150px;
}
#scenario-dropdown{
#scenario-dropdown {
position: absolute;
left: 170px;
top: 10px;
@@ -69,8 +69,8 @@ html, body {
left: 50%;
margin-left: -60px;
height: 30px;
width: 120px;
top: 10px;
width: 120px;
top: 10px;
z-index: 1000;
display: flex;
justify-content: space-between;
@@ -83,33 +83,31 @@ html, body {
position: absolute;
left: 340;
height: 30px;
width: 110;
top: 10px;
width: 110;
top: 10px;
z-index: 1000;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 4px;
padding-right: 4px;
padding-left: 10px;
padding-right: 10px;
}
#connection-status-panel {
position: absolute;
height: 30px;
width: 150px;
top: 10px;
width: 140px;
top: 10px;
right: 10px;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding-left: 4px;
padding-right: 4px;
}
@media only screen and (max-width: 1440px) {
#unit-control-panel {
top: 50px;
}
}
}

View File

@@ -1,29 +1,27 @@
.unit-marker-container {
height: 60px;
width: 60px;
left: -30px;
top: -30px;
border: 0px black solid;
height: 60px;
width: 60px;
left: -30px;
top: -30px;
border: 0px black solid;
position: absolute;
padding: 0;
margin: 0;
margin: 0;
border-collapse: collapse;
}
.unit-marker-icon {
height: 60px;
width: 60px;
left: 0px;
top: 0px;
display: flex;
width: 60px;
left: 0px;
top: 0px;
display: flex;
position: absolute;
justify-content: center;
align-items: center;
}
.unit-marker-icon img {
height: 30px;
}
.unit-marker-selected {
animation-name: flash;
@@ -34,62 +32,67 @@
}
@keyframes flash {
  from {filter: brightness(100%); }
to {filter: brightness(130%);}
from {
filter: brightness(100%);
}
to {
filter: brightness(130%);
}
}
.unit-marker-hovered {
filter: brightness(130%);
}
.unit-marker-container-table-dead {
filter: brightness(50%);
.unit-marker-dead {
filter: brightness(50%);
}
.unit-marker-unitName {
top: -20px;
position: absolute;
text-align: center;
font: 800 16px Arial;
top: -20px;
position: absolute;
text-align: center;
font: 800 16px Arial;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}
.unit-marker-name {
bottom: -20px;
position: absolute;
text-align: center;
font: 800 14px Arial;
bottom: -20px;
position: absolute;
text-align: center;
font: 800 14px Arial;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}
.unit-marker-altitude {
width: 100%;
left: 0px;
top: 0px;
position: absolute;
text-align: right;
font: 800 12px Arial;
width: 100%;
left: 0px;
top: 0px;
position: absolute;
text-align: right;
font: 800 12px Arial;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}
.unit-marker-speed {
width: 100%;
left: 0px;
top: 0px;
position: absolute;
text-align: left;
font: 800 12px Arial;
width: 100%;
left: 0px;
top: 0px;
position: absolute;
text-align: left;
font: 800 12px Arial;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}
.unit-marker-container-table-dead .unit-marker-name{
.unit-marker-container-table-dead .unit-marker-name {
opacity: 0;
}

View File

@@ -0,0 +1,4 @@
#visibility-control-panel .olympus-button {
filter: invert(100%);
opacity: 0.8;
}

View File

@@ -2,7 +2,7 @@ var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
router.get('/', function (req, res, next) {
res.render('index', { title: 'Express' });
});

View File

@@ -2,7 +2,7 @@ var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
router.get('/', function (req, res, next) {
res.send('respond with a resource');
});

View File

@@ -1,14 +1,38 @@
export class Button
{
export class Button {
#container: HTMLElement | null;
constructor(ID: string, srcs: string[], callback: CallableFunction)
{
#srcs: string[];
#callback: CallableFunction;
#img: any;
#state: number = 0;
constructor(ID: string, srcs: string[], callback: CallableFunction) {
this.#container = document.getElementById(ID);
if (this.#container != null)
{
var img = document.createElement("img");
img.src = srcs[0];
this.#container.appendChild(img);
this.#srcs = srcs;
this.#callback = callback;
if (this.#container != null) {
this.#img = document.createElement("img");
this.#img.src = this.#srcs[this.#state];
this.#container.appendChild(this.#img);
this.#container.addEventListener("click", () => this.#onClick());
}
}
setState(state: number) {
if (state < this.#srcs.length) {
this.#state = state;
this.#img.src = this.#srcs[this.#state];
}
}
getState() {
return this.#state;
}
#onClick() {
if (this.#img != null) {
this.setState(this.#state < this.#srcs.length - 1 ? this.#state + 1 : 0);
if (this.#callback)
this.#callback(this.#state);
}
}
}

View File

@@ -1,41 +1,36 @@
export class Dropdown
{
export class Dropdown {
#container: HTMLElement | null;
#options: string[];
#options: string[];
#open?: boolean;
#content?: HTMLElement;
#callback?: CallableFunction;
constructor(ID: string, options: string[], callback: CallableFunction)
{
constructor(ID: string, options: string[], callback: CallableFunction) {
this.#container = document.getElementById(ID);
this.#options = options;
this.#callback = callback;
this.close()
this.#container?.addEventListener("click", () => {
this.#open ? this.close(): this.open();
this.#open ? this.close() : this.open();
})
}
open()
{
if (this.#container != null)
{
open() {
if (this.#container != null) {
this.#open = true;
this.#container.classList.add("olympus-dropdown-open");
this.#container.classList.remove("olympus-dropdown-closed");
this.#content = document.createElement("div");
this.#content.classList.add("olympus-dropdown-content");
this.#content.style.width = (this.#container.offsetWidth - this.#container.offsetHeight) + "px";
this.#content.style.left = this.#container.offsetLeft + "px";
this.#content.style.top = this.#container.offsetTop + this.#container.offsetHeight + "px";
console.log(this.#container);
document.body.appendChild(this.#content);
var height = 2;
for (let optionID in this.#options)
{
for (let optionID in this.#options) {
var node = document.createElement("div");
node.classList.add("olympus-dropdown-element");
node.appendChild(document.createTextNode(this.#options[optionID]));
@@ -53,10 +48,8 @@ export class Dropdown
}
}
close()
{
if (this.#container != null)
{
close() {
if (this.#container != null) {
this.#open = false;
this.#container?.classList.remove("olympus-dropdown-open");
this.#container?.classList.add("olympus-dropdown-closed");

View File

@@ -1,32 +1,28 @@
export class SelectionScroll
{
#container : HTMLElement | null;
import { LatLng } from "leaflet";
export class SelectionScroll {
#container: HTMLElement | null;
#display: string;
constructor(id: string, )
{
constructor(id: string,) {
this.#container = document.getElementById(id);
this.#display = '';
if (this.#container != null)
{
if (this.#container != null) {
this.#display = this.#container.style.display;
this.hide();
}
}
show(x: number, y: number, options: any, callback: CallableFunction)
{
show(x: number, y: number, options: any, callback: CallableFunction) {
/* Hide to remove buttons, if present */
this.hide();
if (this.#container != null && options.length > 1)
{
if (this.#container != null && options.length > 1) {
this.#container.style.display = this.#display;
this.#container.style.left = x - 110 + "px";
this.#container.style.top = y - 110 + "px";
this.#container.style.top = y - 110 + "px";
for (let optionID in options)
{
for (let optionID in options) {
var node = document.createElement("div");
node.classList.add("olympus-selection-scroll-element");
node.appendChild(document.createTextNode(options[optionID]));
@@ -36,14 +32,11 @@ export class SelectionScroll
}
}
hide()
{
if (this.#container != null)
{
hide() {
if (this.#container != null) {
this.#container.style.display = "none";
var buttons = this.#container.querySelectorAll(".olympus-selection-scroll-element");
for (let child of buttons)
{
for (let child of buttons) {
this.#container.removeChild(child);
}
}

View File

@@ -1,40 +1,34 @@
import { getActiveCoalition, setActiveCoalition } from "..";
import { deg2rad } from "../other/utils";
export class SelectionWheel
{
export class SelectionWheel {
#container: HTMLElement | null;
#display: string;
constructor(id: string)
{
constructor(id: string) {
this.#container = document.getElementById(id);
this.#display = '';
if (this.#container != null)
{
if (this.#container != null) {
this.#container.querySelector("#coalition-switch")?.addEventListener('change', (e) => this.#onSwitch(e))
this.#display = this.#container.style.display;
this.hide();
}
}
show(x: number, y: number, options: any, showCoalition: boolean)
{
show(x: number, y: number, options: any, showCoalition: boolean) {
/* Hide to remove buttons, if present */
this.hide();
if (this.#container != null)
{
if (this.#container != null) {
this.#container.style.display = this.#display;
this.#container.style.left = x - 110 + "px";
this.#container.style.top = y - 110 + "px";
this.#container.style.top = y - 110 + "px";
var angularSize = 360 / options.length;
var r = 80;
/* Create the buttons */
for (let id in options)
{
for (let id in options) {
var button = document.createElement("div");
button.classList.add("selection-wheel-button");
button.style.left = x - 25 + "px";
@@ -50,8 +44,7 @@ export class SelectionWheel
image.classList.add("selection-wheel-image");
image.src = `images/buttons/${options[id].src}`
image.title = options[id].tooltip;
if ('tint' in options[id])
{
if ('tint' in options[id]) {
button.style.setProperty('background-color', options[id].tint);
image.style.opacity = "0";
}
@@ -59,14 +52,12 @@ export class SelectionWheel
}
/* Hide the coalition switch if required */
var switchContainer = <HTMLElement> this.#container.querySelector("#coalition-switch-container");
if (showCoalition == false)
{
var switchContainer = <HTMLElement>this.#container.querySelector("#coalition-switch-container");
if (showCoalition == false) {
switchContainer.style.display = "none";
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--neutral-coalition-color"));
}
else
{
else {
switchContainer.style.display = "block";
if (getActiveCoalition() == "blue")
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
@@ -76,33 +67,26 @@ export class SelectionWheel
}
}
hide()
{
if (this.#container != null)
{
hide() {
if (this.#container != null) {
this.#container.style.display = "none";
var buttons = this.#container.querySelectorAll(".selection-wheel-button");
for (let child of buttons)
{
for (let child of buttons) {
this.#container.removeChild(child);
}
}
}
#onSwitch(e: any)
{
if (this.#container != null)
{
if (e.currentTarget.checked)
{
#onSwitch(e: any) {
if (this.#container != null) {
if (e.currentTarget.checked) {
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--red-coalition-color"));
setActiveCoalition("red");
}
else
{
else {
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
setActiveCoalition("blue");
}
}
}
}
}

View File

@@ -5,14 +5,12 @@ import { ConvertDDToDMS } from '../other/utils';
/* Edit here to change server address */
var RESTaddress = "http://localhost:30000/restdemo";
export function getDataFromDCS(callback: CallableFunction)
{
export function getDataFromDCS(callback: CallableFunction) {
/* Request the updated unit data from the server */
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", RESTaddress, true);
xmlHttp.open("GET", RESTaddress, true);
xmlHttp.onload = function(e)
{
xmlHttp.onload = function (e) {
var data = JSON.parse(xmlHttp.responseText);
callback(data);
setConnected(true);
@@ -22,24 +20,22 @@ export function getDataFromDCS(callback: CallableFunction)
console.error("An error occurred during the XMLHttpRequest");
setConnected(false);
};
xmlHttp.send( null );
xmlHttp.send(null);
}
export function addDestination(ID: number, path: any)
{
export function addDestination(ID: number, path: any) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {};
xhr.onreadystatechange = () => { };
var command = {"ID": ID, "path": path}
var data = {"setPath": command}
var command = { "ID": ID, "path": path }
var data = { "setPath": command }
xhr.send(JSON.stringify(data));
}
export function spawnSmoke(color: string, latlng: L.LatLng)
{
export function spawnSmoke(color: string, latlng: L.LatLng) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
@@ -49,14 +45,13 @@ export function spawnSmoke(color: string, latlng: L.LatLng)
}
};
var command = {"color": color, "location": latlng};
var data = {"smoke": command}
var command = { "color": color, "location": latlng };
var data = { "smoke": command }
xhr.send(JSON.stringify(data));
}
export function spawnGroundUnit(type: string, latlng: L.LatLng, coalition: string)
{
export function spawnGroundUnit(type: string, latlng: L.LatLng, coalition: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
@@ -66,14 +61,13 @@ export function spawnGroundUnit(type: string, latlng: L.LatLng, coalition: strin
}
};
var command = {"type": type, "location": latlng, "coalition": coalition};
var data = {"spawnGround": command}
var command = { "type": type, "location": latlng, "coalition": coalition };
var data = { "spawnGround": command }
xhr.send(JSON.stringify(data));
}
export function spawnAircraft(type: string, latlng: L.LatLng, coalition: string, payloadName = "", airbaseName = "")
{
export function spawnAircraft(type: string, latlng: L.LatLng, coalition: string, payloadName: string | null = null, airbaseName: string | null = null) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
@@ -83,42 +77,72 @@ export function spawnAircraft(type: string, latlng: L.LatLng, coalition: string,
}
};
var command = {"type": type, "location": latlng, "coalition": coalition, "payloadName": payloadName, "airbaseName": airbaseName};
var data = {"spawnAir": command}
var command = { "type": type, "location": latlng, "coalition": coalition, "payloadName": payloadName != null? payloadName: "", "airbaseName": airbaseName != null? airbaseName: ""};
var data = { "spawnAir": command }
xhr.send(JSON.stringify(data));
}
export function attackUnit(ID: number, targetID: number)
{
export function attackUnit(ID: number, targetID: number) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " attack " + getUnitsManager().getUnitByID(targetID).unitName );
}
if (xhr.readyState === 4) {
console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " attack " + getUnitsManager().getUnitByID(targetID).unitName);
}
};
var command = {"ID": ID, "targetID": targetID};
var data = {"attackUnit": command}
var command = { "ID": ID, "targetID": targetID };
var data = { "attackUnit": command }
xhr.send(JSON.stringify(data));
}
export function cloneUnit(ID: number)
{
export function cloneUnit(ID: number) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " cloned");
}
if (xhr.readyState === 4) {
console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " cloned");
}
};
var command = {"ID": ID};
var data = {"cloneUnit": command}
var command = { "ID": ID };
var data = { "cloneUnit": command }
xhr.send(JSON.stringify(data));
}
export function changeSpeed(ID: number, speedChange: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "change": speedChange}
var data = {"changeSpeed": command}
xhr.send(JSON.stringify(data));
}
export function changeAltitude(ID: number, altitudeChange: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(getUnitsManager().getUnitByID(ID).unitName + " altitude change request: " + altitudeChange);
}
};
var command = {"ID": ID, "change": altitudeChange}
var data = {"changeAltitude": command}
xhr.send(JSON.stringify(data));
}

View File

@@ -7,6 +7,7 @@ import { SelectionScroll } from "./controls/selectionscroll";
import { Dropdown } from "./controls/dropdown";
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
import { Button } from "./controls/button";
import { MissionData } from "./missiondata/missiondata";
/* TODO: should this be a class? */
var map: Map;
@@ -19,26 +20,44 @@ var scenarioDropdown: Dropdown;
var mapSourceDropdown: Dropdown;
var connected: boolean;
var connectionStatusPanel: ConnectionStatusPanel;
var missionData: MissionData;
var slowButton: Button;
var fastButton: Button;
var climbButton: Button;
var descendButton: Button;
var userVisibilityButton: Button;
var aiVisibilityButton: Button;
var weaponVisibilityButton: Button;
var deadVisibilityButton: Button;
function setup()
{
function setup() {
/* Initialize */
map = new Map('map-container');
selectionWheel = new SelectionWheel("selection-wheel");
selectionScroll = new SelectionScroll("selection-scroll");
unitsManager = new UnitsManager();
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
scenarioDropdown = new Dropdown("scenario-dropdown", ["Caucasus", "Syria", "Nevada", "Marianas", "South Atlantic", "The channel"], () => {});
scenarioDropdown = new Dropdown("scenario-dropdown", ["Caucasus", "Syria", "Nevada", "Marianas", "South Atlantic", "The channel"], () => { });
mapSourceDropdown = new Dropdown("map-source-dropdown", map.getLayers(), (option: string) => map.setLayer(option));
connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel");
slowButton = new Button("slow-button", ["images/buttons/slow.svg"], () => {});
fastButton = new Button("fast-button", ["images/buttons/fast.svg"], () => {});
climbButton = new Button("climb-button", ["images/buttons/climb.svg"], () => {});
descendButton = new Button("descend-button", ["images/buttons/descend.svg"], () => {});
missionData = new MissionData();
/* Unit control buttons */
slowButton = new Button("slow-button", ["images/buttons/slow.svg"], () => { getUnitsManager().selectedUnitsChangeSpeed("slow"); });
fastButton = new Button("fast-button", ["images/buttons/fast.svg"], () => { getUnitsManager().selectedUnitsChangeSpeed("fast"); });
climbButton = new Button("climb-button", ["images/buttons/climb.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("climb"); });
descendButton = new Button("descend-button", ["images/buttons/descend.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("descend"); });
/* Visibility buttons */
userVisibilityButton = new Button("user-visibility-button", ["images/buttons/user-full.svg", "images/buttons/user-partial.svg", "images/buttons/user-none.svg", "images/buttons/user-hidden.svg"], () => { });
aiVisibilityButton = new Button("ai-visibility-button", ["images/buttons/ai-full.svg", "images/buttons/ai-partial.svg", "images/buttons/ai-none.svg", "images/buttons/ai-hidden.svg"], () => { });
weaponVisibilityButton = new Button("weapon-visibility-button", ["images/buttons/weapon-partial.svg", "images/buttons/weapon-none.svg", "images/buttons/weapon-hidden.svg"], () => { });
deadVisibilityButton = new Button("dead-visibility-button", ["images/buttons/dead.svg", "images/buttons/dead-hidden.svg"], () => { });
aiVisibilityButton.setState(1);
weaponVisibilityButton.setState(1);
deadVisibilityButton.setState(1);
/* Default values */
activeCoalition = "blue";
@@ -47,62 +66,100 @@ function setup()
requestUpdate();
}
function requestUpdate()
{
function requestUpdate() {
getDataFromDCS(update);
/* Main update rate = 250ms is minimum time, equal to server update time. */
setTimeout(() => requestUpdate(), getConnected() ? 250: 1000);
connectionStatusPanel.update(getConnected() );
setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
connectionStatusPanel.update(getConnected());
}
export function update(data: JSON)
{
export function update(data: JSON) {
unitsManager.update(data);
missionData.update(data);
}
export function getMap()
{
export function getMap() {
return map;
}
export function getSelectionWheel()
{
export function getSelectionWheel() {
return selectionWheel;
}
export function getSelectionScroll()
{
export function getSelectionScroll() {
return selectionScroll;
}
export function getUnitsManager()
{
export function getUnitsManager() {
return unitsManager;
}
export function getUnitInfoPanel()
{
export function getUnitInfoPanel() {
return unitInfoPanel;
}
export function setActiveCoalition(newActiveCoalition: string)
{
export function setActiveCoalition(newActiveCoalition: string) {
activeCoalition = newActiveCoalition;
}
export function getActiveCoalition()
{
export function getActiveCoalition() {
return activeCoalition;
}
export function setConnected(newConnected: boolean)
{
export function setConnected(newConnected: boolean) {
connected = newConnected
}
export function getConnected()
{
export function getConnected() {
return connected;
}
export function getVisibilitySettings() {
var visibility = {
user: "",
ai: "",
weapon: "",
dead: ""
};
switch (userVisibilityButton.getState()) {
case 0:
visibility.user = "full"; break;
case 1:
visibility.user = "partial"; break;
case 2:
visibility.user = "none"; break;
case 3:
visibility.user = "hidden"; break;
}
switch (aiVisibilityButton.getState()) {
case 0:
visibility.ai = "full"; break;
case 1:
visibility.ai = "partial"; break;
case 2:
visibility.ai = "none"; break;
case 3:
visibility.ai = "hidden"; break;
}
switch (weaponVisibilityButton.getState()) {
case 0:
visibility.weapon = "partial"; break;
case 1:
visibility.weapon = "none"; break;
case 2:
visibility.weapon = "hidden"; break;
}
switch (deadVisibilityButton.getState()) {
case 0:
visibility.dead = "none"; break;
case 1:
visibility.dead = "hidden"; break;
}
return visibility;
}
window.onload = setup;

133
client/src/map/boxselect.ts Normal file
View File

@@ -0,0 +1,133 @@
import { Map } from 'leaflet';
import { Handler} from 'leaflet';
import { Util } from 'leaflet';
import { DomUtil } from 'leaflet';
import { DomEvent } from 'leaflet';
import { LatLngBounds } from 'leaflet';
import { Bounds } from 'leaflet';
export var BoxSelect = Handler.extend({
initialize: function (map: Map) {
this._map = map;
this._container = map.getContainer();
this._pane = map.getPanes().overlayPane;
this._resetStateTimeout = 0;
map.on('unload', this._destroy, this);
},
addHooks: function () {
DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
},
moved: function () {
return this._moved;
},
_destroy: function () {
DomUtil.remove(this._pane);
delete this._pane;
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_onMouseDown: function (e: any) {
if (((e.which !== 3) && (e.button !== 2))) { return false; }
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
DomUtil.disableTextSelection();
DomUtil.disableImageDrag();
this._startPoint = this._map.mouseEventToContainerPoint(e);
//@ts-ignore
DomEvent.on(document, {
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
},
_onMouseMove: function (e: any) {
if (!this._moved) {
this._moved = true;
this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container);
DomUtil.addClass(this._container, 'leaflet-crosshair');
this._map.fire('boxzoomstart');
}
this._point = this._map.mouseEventToContainerPoint(e);
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
if (bounds.min != undefined)
DomUtil.setPosition(this._box, bounds.min);
this._box.style.width = size.x + 'px';
this._box.style.height = size.y + 'px';
},
_finish: function () {
if (this._moved) {
DomUtil.remove(this._box);
DomUtil.removeClass(this._container, 'leaflet-crosshair');
}
DomUtil.enableTextSelection();
DomUtil.enableImageDrag();
//@ts-ignore
DomEvent.off(document, {
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
},
_onMouseUp: function (e: any) {
if ((e.which !== 3) && (e.button !== 2)) { return; }
this._finish();
if (!this._moved) { return; }
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
setTimeout(Util.bind(this._resetState, this), 0);
var bounds = new LatLngBounds(
this._map.containerPointToLatLng(this._startPoint),
this._map.containerPointToLatLng(this._point));
this._map.fire('selectionend', {selectionBounds: bounds});
},
_onKeyDown: function (e: any) {
if (e.keyCode === 27) {
this._finish();
this._clearDeferredResetState();
this._resetState();
}
}
});

View File

@@ -1,75 +1,83 @@
import * as L from "leaflet"
import { getSelectionWheel, getSelectionScroll, getUnitsManager, getActiveCoalition } from "..";
import { spawnAircraft } from "../dcs/dcs";
import { spawnAircraft, spawnGroundUnit, spawnSmoke } from "../dcs/dcs";
import { payloadNames } from "../units/payloadNames";
import { unitTypes } from "../units/unitTypes";
import { BoxSelect } from "./boxselect";
export class Map extends L.Map
{
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
export interface ClickEvent {
x: number;
y: number;
latlng: L.LatLng;
}
export interface SpawnEvent extends ClickEvent{
airbaseName: string | null;
coalitionID: number | null;
}
export class Map extends L.Map {
#state: string;
#layer?: L.TileLayer;
#preventRightClick: boolean = false;
#rightClickTimer: number = 0;
constructor(ID: string)
{
constructor(ID: string) {
/* Init the leaflet map */
super(ID, {doubleClickZoom: false, zoomControl: false});
//@ts-ignore
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true });
this.setView([37.23, -115.8], 12);
this.setLayer("ArcGIS Satellite");
/* Init the state machine */
this.#state = "IDLE";
/* Register event handles */
this.on("contextmenu", (e) => this.#onContextMenu(e));
this.on("click", (e) => this.#onClick(e));
this.on("dblclick", (e) => this.#onDoubleClick(e));
this.on("click", (e: any) => this.#onClick(e));
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
this.on("contextmenu", (e: any) => this.#onContextMenu(e));
this.on('selectionend', (e: any) => this.#onSelectionEnd(e));
}
setLayer(layerName: string)
{
if (this.#layer != null)
{
setLayer(layerName: string) {
if (this.#layer != null) {
this.removeLayer(this.#layer)
}
if (layerName == "ArcGIS Satellite")
{
if (layerName == "ArcGIS Satellite") {
this.#layer = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", {
attribution: "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
});
}
else if (layerName == "USGS Topo")
{
else if (layerName == "USGS Topo") {
this.#layer = L.tileLayer('https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 20,
attribution: 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
});
}
else if (layerName == "OpenStreetMap Mapnik")
{
else if (layerName == "OpenStreetMap Mapnik") {
this.#layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
else if (layerName == "OPENVKarte")
{
else if (layerName == "OPENVKarte") {
this.#layer = L.tileLayer('https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map <a href="https://memomaps.de/">memomaps.de</a> <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
else if (layerName == "Esri.DeLorme")
{
else if (layerName == "Esri.DeLorme") {
this.#layer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Copyright: &copy;2012 DeLorme',
minZoom: 1,
maxZoom: 11
});
}
else if (layerName == "CyclOSM")
{
else if (layerName == "CyclOSM") {
this.#layer = L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
@@ -78,169 +86,194 @@ export class Map extends L.Map
this.#layer?.addTo(this);
}
getLayers()
{
getLayers() {
return ["ArcGIS Satellite", "USGS Topo", "OpenStreetMap Mapnik", "OPENVKarte", "Esri.DeLorme", "CyclOSM"]
}
/* State machine */
setState(state: string)
{
setState(state: string) {
this.#state = state;
if (this.#state === "IDLE")
{
if (this.#state === "IDLE") {
}
else if (this.#state === "MOVE_UNIT")
{
else if (this.#state === "MOVE_UNIT") {
}
else if (this.#state === "ATTACK")
{
else if (this.#state === "ATTACK") {
}
else if (this.#state === "FORMATION")
{
else if (this.#state === "FORMATION") {
}
}
getState()
{
getState() {
return this.#state;
}
/* Selection wheel */
showSelectionWheel(e: PointerEvent, options: any, showCoalition: boolean)
{
showSelectionWheel(e: ClickEvent | SpawnEvent, options: any, showCoalition: boolean) {
var x = e.x;
var y = e.y;
getSelectionWheel().show(x, y, options, showCoalition);
}
hideSelectionWheel()
{
hideSelectionWheel() {
getSelectionWheel().hide();
}
/* Selection scroll */
showSelectionScroll(e: PointerEvent, options: any, callback: CallableFunction)
{
showSelectionScroll(e: ClickEvent | SpawnEvent, options: any, callback: CallableFunction) {
var x = e.x;
var y = e.y;
getSelectionScroll().show(x, y, options, callback);
}
hideSelectionScroll()
{
hideSelectionScroll() {
getSelectionScroll().hide();
}
/* Event handlers */
#onContextMenu(e: any)
{
this.setState("IDLE");
getUnitsManager().deselectAllUnits();
this.hideSelectionWheel();
this.hideSelectionScroll();
}
#onClick(e: any)
{
/* Event handlers */
#onClick(e: any) {
this.hideSelectionWheel();
this.hideSelectionScroll();
if (this.#state === "IDLE")
{
if (this.#state === "IDLE") {
}
else if (this.#state === "MOVE_UNIT")
{
if (!e.originalEvent.ctrlKey)
{
else if (this.#state === "MOVE_UNIT") {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().clearDestinations();
}
getUnitsManager().addDestination(e.latlng)
}
}
#onDoubleClick(e: any)
{
if (this.#state == "IDLE")
{
#onDoubleClick(e: any) {
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: null, coalitionID: null};
if (this.#state == "IDLE") {
var options = [
{"tooltip": "Air unit", "src": "spawnAir.png", "callback": () => this.#aircraftSpawnMenu(e)},
{"tooltip": "Ground unit", "src": "spawnGround.png", "callback": () => this.#groundUnitSpawnMenu(e)},
{"tooltip": "Smoke", "src": "spawnSmoke.png", "callback": () => this.#smokeSpawnMenu(e)},
{"tooltip": "Explosion", "src": "spawnExplosion.png", "callback": () => this.#explosionSpawnMenu(e)}
{ "tooltip": "Spawn air unit", "src": "spawnAir.png", "callback": () => this.#aircraftSpawnMenu(spawnEvent) },
{ "tooltip": "Spawn ground unit", "src": "spawnGround.png", "callback": () => this.#groundUnitSpawnMenu(spawnEvent) },
{ "tooltip": "Smoke", "src": "spawnSmoke.png", "callback": () => this.#smokeSpawnMenu(spawnEvent) },
//{ "tooltip": "Explosion", "src": "spawnExplosion.png", "callback": () => this.#explosionSpawnMenu(e) }
]
this.showSelectionWheel(e.originalEvent, options, true);
this.showSelectionWheel(spawnEvent, options, true);
}
}
/* Spawning menus */
#groundUnitSpawnMenu(e: any)
{
#onContextMenu(e: any) {
this.#rightClickTimer = setTimeout(() => {
if (!this.#preventRightClick) {
this.setState("IDLE");
getUnitsManager().deselectAllUnits();
this.hideSelectionWheel();
this.hideSelectionScroll();
}
this.#preventRightClick = false;
}, 200);
}
#smokeSpawnMenu(e: any)
#onSelectionEnd(e: any)
{
clearTimeout(this.#rightClickTimer);
this.#preventRightClick = true;
getUnitsManager().selectFromBounds(e.selectionBounds);
}
#explosionSpawnMenu(e: any)
/* Spawn from air base */
spawnFromAirbase(e: SpawnEvent)
{
this.#aircraftSpawnMenu(e);
}
#aircraftSpawnMenu(e: any)
{
/* Spawning menus */
#groundUnitSpawnMenu(e: SpawnEvent) {
var options = [
{'coalition': true, 'tooltip': 'CAP', 'src': 'spawnCAP.png', 'callback': () => this.#selectAircraft(e, "CAP")},
{'coalition': true, 'tooltip': 'CAS', 'src': 'spawnCAS.png', 'callback': () => this.#selectAircraft(e, "CAS")},
{'coalition': true, 'tooltip': 'Tanker', 'src': 'spawnTanker.png', 'callback': () => this.#selectAircraft(e, "tanker")},
{'coalition': true, 'tooltip': 'AWACS', 'src': 'spawnAWACS.png', 'callback': () => this.#selectAircraft(e, "awacs")},
{'coalition': true, 'tooltip': 'Strike', 'src': 'spawnStrike.png', 'callback': () => this.#selectAircraft(e, "strike")},
{'coalition': true, 'tooltip': 'Drone', 'src': 'spawnDrone.png', 'callback': () => this.#selectAircraft(e, "drone")},
{'coalition': true, 'tooltip': 'Transport', 'src': 'spawnTransport.png','callback': () => this.#selectAircraft(e, "transport")},
{'coalition': true, 'tooltip': 'Howitzer', 'src': 'spawnHowitzer.png', 'callback': () => this.#selectGroundUnit(e, "Howitzers")},
{'coalition': true, 'tooltip': 'SAM', 'src': 'spawnSAM.png', 'callback': () => this.#selectGroundUnit(e, "SAM")},
{'coalition': true, 'tooltip': 'IFV', 'src': 'spawnIFV.png', 'callback': () => this.#selectGroundUnit(e, "IFV")},
{'coalition': true, 'tooltip': 'Tank', 'src': 'spawnTank.png', 'callback': () => this.#selectGroundUnit(e, "Tanks")},
{'coalition': true, 'tooltip': 'MLRS', 'src': 'spawnMLRS.png', 'callback': () => this.#selectGroundUnit(e, "MLRS")},
{'coalition': true, 'tooltip': 'Radar', 'src': 'spawnRadar.png', 'callback': () => this.#selectGroundUnit(e, "Radar")},
{'coalition': true, 'tooltip': 'Unarmed', 'src': 'spawnUnarmed.png', 'callback': () => this.#selectGroundUnit(e, "Unarmed")}
]
this.showSelectionWheel(e.originalEvent, options, true);
this.showSelectionWheel(e, options, true);
}
#smokeSpawnMenu(e: SpawnEvent) {
this.hideSelectionWheel();
this.hideSelectionScroll();
var options = [
{'tooltip': 'Red smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('red', e.latlng)}, 'tint': 'red'},
{'tooltip': 'White smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('white', e.latlng)}, 'tint': 'white'},
{'tooltip': 'Blue smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('blue', e.latlng)}, 'tint': 'blue'},
{'tooltip': 'Green smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('green', e.latlng)}, 'tint': 'green'},
{'tooltip': 'Orange smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('orange', e.latlng)}, 'tint': 'orange'},
]
this.showSelectionWheel(e, options, true);
}
#explosionSpawnMenu(e: SpawnEvent) {
}
#aircraftSpawnMenu(e: SpawnEvent) {
var options = [
{ 'coalition': true, 'tooltip': 'CAP', 'src': 'spawnCAP.png', 'callback': () => this.#selectAircraft(e, "CAP") },
{ 'coalition': true, 'tooltip': 'CAS', 'src': 'spawnCAS.png', 'callback': () => this.#selectAircraft(e, "CAS") },
{ 'coalition': true, 'tooltip': 'Tanker', 'src': 'spawnTanker.png', 'callback': () => this.#selectAircraft(e, "tanker") },
{ 'coalition': true, 'tooltip': 'AWACS', 'src': 'spawnAWACS.png', 'callback': () => this.#selectAircraft(e, "awacs") },
{ 'coalition': true, 'tooltip': 'Strike', 'src': 'spawnStrike.png', 'callback': () => this.#selectAircraft(e, "strike") },
{ 'coalition': true, 'tooltip': 'Drone', 'src': 'spawnDrone.png', 'callback': () => this.#selectAircraft(e, "drone") },
{ 'coalition': true, 'tooltip': 'Transport', 'src': 'spawnTransport.png', 'callback': () => this.#selectAircraft(e, "transport") },
]
this.showSelectionWheel(e, options, true);
}
/* Show unit selection for air units */
#selectAircraft(e: any, group: string)
{
#selectAircraft(e: SpawnEvent, group: string) {
this.hideSelectionWheel();
this.hideSelectionScroll();
var options = unitTypes.air[group];
options.sort();
this.showSelectionScroll(e.originalEvent, options, (unitType: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
this.showSelectionScroll(e, options, (unitType: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
this.#unitSelectPayload(e, unitType);
});
}
/* Show weapon selection for air units */
#unitSelectPayload(e: any, unitType: string)
{
#unitSelectPayload(e: SpawnEvent, unitType: string) {
this.hideSelectionWheel();
this.hideSelectionScroll();
var options = [];
options = payloadNames[unitType]
if (options != undefined && options.length > 0)
{
if (options != undefined && options.length > 0) {
options.sort();
this.showSelectionScroll(e.originalEvent, options, (payloadName: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
this.showSelectionScroll({x: e.x, y: e.y, latlng: e.latlng}, options, (payloadName: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
spawnAircraft(unitType, e.latlng, getActiveCoalition(), payloadName, e.airbaseName);
});
}
else
{
}
else {
spawnAircraft(unitType, e.latlng, getActiveCoalition());
}
}
/* Show unit selection for ground units */
#selectGroundUnit(e: any, group: string)
{
this.hideSelectionWheel();
this.hideSelectionScroll();
var options = unitTypes.vehicles[group];
options.sort();
this.showSelectionScroll(e, options, (unitType: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
spawnGroundUnit(unitType, e.latlng, getActiveCoalition());
});
}
}

View File

@@ -0,0 +1,66 @@
import * as L from 'leaflet'
export interface AirbaseOptions
{
name: string,
position: L.LatLng,
src: string
}
export class AirbaseMarker extends L.Marker
{
#name: string = "";
#coalitionID: number = -1;
constructor(options: AirbaseOptions)
{
super(options.position, { riseOnHover: true });
this.#name = options.name;
var icon = new L.DivIcon({
html: `<table class="airbasemarker-container" id="container">
<tr>
<td>
<img class="airbasemarker-icon" id="icon" src="${options.src}">
<div class="airbasemarker-name" id="name">${options.name}</div>
</td>
</tr>
</table>`,
className: 'airbase-marker'}); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
}
setCoalitionID(coalitionID: number)
{
this.#coalitionID = coalitionID;
var element = this.getElement();
if (element != null)
{
var img = element.querySelector("#icon");
if (img != null)
{
img.classList.remove("airbasemarker-icon-blue");
img.classList.remove("airbasemarker-icon-red");
if (this.#coalitionID == 2)
{
img.classList.add("airbasemarker-icon-blue");
}
else if (this.#coalitionID == 1)
{
img.classList.add("airbasemarker-icon-red");
}
}
}
}
getName()
{
return this.#name;
}
getCoalitionID()
{
return this.#coalitionID;
}
}

View File

@@ -0,0 +1,65 @@
import { Marker, LatLng } from "leaflet";
import { getMap } from "..";
import { SpawnEvent } from "../map/map";
import { AirbaseMarker } from "./airbasemarker";
export class MissionData
{
//#bullseye : any; //TODO declare interface
//#bullseyeMarker : Marker;
#airbases : any; //TODO declare interface
#airbasesMarkers: {[name: string]: AirbaseMarker};
constructor()
{
//this.#bullseye = undefined;
//this.#bullseyeMarker = undefined;
this.#airbasesMarkers = {};
}
update(data: any)
{
//this.#bullseye = data.missionData.bullseye;
this.#airbases = data.airbases;
//this.#drawBullseye();
this.#drawAirbases();
}
//#drawBullseye()
//{
// if (this.#bullseyeMarker === undefined)
// {
// this.#bullseyeMarker = new Marker([this.#bullseye.lat, this.#bullseye.lng]).addTo(map.getMap());
// }
// else
// {
// this.#bullseyeMarker.setLatLng(new LatLng(this.#bullseye.lat, this.#bullseye.lng));
// }
//}
#drawAirbases()
{
for (let idx in this.#airbases)
{
var airbase = this.#airbases[idx]
if (this.#airbasesMarkers[idx] === undefined)
{
this.#airbasesMarkers[idx] = new AirbaseMarker({
position: new LatLng(airbase.lat, airbase.lng),
name: airbase.callsign,
src: "images/airbase.png"}).addTo(getMap());
this.#airbasesMarkers[idx].on('click', (e) => this.#onAirbaseClick(e));
}
else
{
this.#airbasesMarkers[idx].setCoalitionID(airbase.coalition);
}
}
}
#onAirbaseClick(e: any)
{
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: e.sourceTarget.getName(), coalitionID: e.sourceTarget.getCoalitionID()};
getMap().spawnFromAirbase(spawnEvent);
}
}

View File

@@ -1,45 +1,40 @@
export function distance(lat1: number, lon1: number, lat2: number, lon2: number)
{
export function distance(lat1: number, lon1: number, lat2: number, lon2: number) {
const R = 6371e3; // metres
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
const Δφ = deg2rad(lat2-lat1);
const Δλ = deg2rad(lon2-lon1);
const Δφ = deg2rad(lat2 - lat1);
const Δλ = deg2rad(lon2 - lon1);
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c; // in metres
return d;
}
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number)
{
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
const λ1 = deg2rad(lon1); // φ, λ in radians
const λ2 = deg2rad(lon2);
const y = Math.sin(λ2-λ1) * Math.cos(φ2);
const x = Math.cos(φ1)*Math.sin(φ2) - Math.sin(φ1)*Math.cos(φ2)*Math.cos(λ2-λ1);
const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1);
const θ = Math.atan2(y, x);
const brng = (rad2deg(θ) + 360) % 360; // in degrees
return brng;
}
const zeroPad = function(num: number, places: number)
{
const zeroPad = function (num: number, places: number) {
var string = String(num);
while (string.length < places)
{
while (string.length < places) {
string += "0";
}
return string;
}
}
export function ConvertDDToDMS(D: number, lng: boolean)
{
export function ConvertDDToDMS(D: number, lng: boolean) {
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
var deg = 0 | (D < 0 ? (D = -D) : D);
var min = 0 | (((D += 1e-9) % 1) * 60);
@@ -52,14 +47,12 @@ export function ConvertDDToDMS(D: number, lng: boolean)
return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
}
export function deg2rad(deg: number)
{
export function deg2rad(deg: number) {
var pi = Math.PI;
return deg * (pi/180);
return deg * (pi / 180);
}
export function rad2deg(rad: number)
{
export function rad2deg(rad: number) {
var pi = Math.PI;
return rad / (pi/180);
return rad / (pi / 180);
}

View File

@@ -1,27 +1,20 @@
export class ConnectionStatusPanel
{
export class ConnectionStatusPanel {
#element: HTMLElement
constructor(ID: string)
{
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
}
update(connected: boolean)
{
if (this.#element != null)
{
update(connected: boolean) {
if (this.#element != null) {
var div = this.#element.querySelector("#status-string");
if (div != null)
{
if (connected)
{
if (div != null) {
if (connected) {
div.innerHTML = "Connected";
div.classList.add("olympus-status-connected");
div.classList.remove("olympus-status-disconnected");
}
else
{
else {
div.innerHTML = "Disconnected";
div.classList.add("olympus-status-disconnected");
div.classList.remove("olympus-status-connected");

View File

@@ -1,43 +1,49 @@
import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { Unit } from "../units/unit";
export class UnitInfoPanel
{
export class UnitInfoPanel {
#element: HTMLElement
#display: string;
constructor(ID: string)
{
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
this.#display = '';
if (this.#element != null)
{
if (this.#element != null) {
this.#display = this.#element.style.display;
this.show();
this.hide();
}
}
show()
{
show() {
this.#element.style.display = this.#display;
}
hide()
{
hide() {
this.#element.style.display = "none";
}
update(unit: Unit)
{
if (this.#element != null)
{
this.#element.querySelector("#unit-name")!.innerHTML = unit.unitName;
update(unit: Unit) {
var loadout = "";
for (let index in unit.ammo) {
var ammo = unit.ammo[index];
var displayName = ammo.desc.displayName;
var amount = ammo.count;
loadout += amount + "x" + displayName;
if (parseInt(index) < Object.keys(unit.ammo).length)
loadout += ", ";
}
if (this.#element != null) {
this.#element.querySelector("#unit-name")!.innerHTML = unit.unitName;
this.#element.querySelector("#group-name")!.innerHTML = unit.groupName;
this.#element.querySelector("#heading")!.innerHTML = String(Math.floor(rad2deg(unit.heading)) + "°");
this.#element.querySelector("#altitude")!.innerHTML = String(Math.floor(unit.altitude / 0.3048) + "ft");
this.#element.querySelector("#groundspeed")!.innerHTML = String(Math.floor(unit.speed * 1.94384) + "kts");
//this.#element.querySelector("#altitude")!.innerHTML = String(Math.floor(unit.missionData.fuel * 100) + "%");
this.#element.querySelector("#fuel")!.innerHTML = String(unit.fuel + "%");
this.#element.querySelector("#position")!.innerHTML = ConvertDDToDMS(unit.latitude, false) + " " + ConvertDDToDMS(unit.longitude, true);
}
this.#element.querySelector("#task")!.innerHTML = unit.currentTask;
this.#element.querySelector("#loadout")!.innerHTML = loadout;
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,43 +1,49 @@
import { Marker, LatLng, Polyline } from 'leaflet';
import { Marker, LatLng, Polyline, Icon } from 'leaflet';
import { ConvertDDToDMS } from '../other/utils';
import { getMap, getUnitsManager } from '..';
import { UnitMarker, MarkerOptions } from './unitmarker';
import { addDestination, attackUnit } from '../dcs/dcs';
import { getMap, getUnitsManager, getVisibilitySettings } from '..';
import { UnitMarker, MarkerOptions, AircraftMarker, HelicopterMarker, GroundUnitMarker, NavyUnitMarker, WeaponMarker } from './unitmarker';
import { addDestination, attackUnit, changeAltitude, changeSpeed } from '../dcs/dcs';
export class Unit
{
ID : number;
leader : boolean;
wingman : boolean;
wingmen : Unit[];
formation : string;
name : string;
unitName : string;
groupName : string;
latitude : number;
longitude : number;
altitude : number;
heading : number;
coalitionID : number;
alive : boolean;
speed : number;
currentTask : string;
type : Object | null;
flags : Object | null;
activePath : any | null; // TODO: declare inteface
missionData : Object | null;
var pathIcon = new Icon({
iconUrl: 'images/marker-icon.png',
shadowUrl: 'images/marker-shadow.png',
iconAnchor: [13, 41]
});
#selectable : boolean;
#selected : boolean;
#preventClick : boolean;
#pathMarkers : Marker[];
#pathPolyline : Polyline;
#targetsPolylines : Polyline[];
#marker : UnitMarker;
#timer : number;
export class Unit {
ID: number = -1;
leader: boolean = false;
wingman: boolean = false;
wingmen: Unit[] = [];
formation: string = "";
name: string = "";
unitName: string = "";
groupName: string = "";
latitude: number = 0;
longitude: number = 0;
altitude: number = 0;
heading: number = 0;
speed: number = 0;
coalitionID: number = -1;
alive: boolean = true;
currentTask: string = "";
fuel: number = 0;
type: any = null;
flags: any = null;
activePath: any = null;
ammo: any = null;
targets: any = null;
static getConstructor(name: string)
{
#selectable: boolean;
#selected: boolean = false;
#preventClick: boolean = false;
#pathMarkers: Marker[] = [];
#pathPolyline: Polyline;
#targetsPolylines: Polyline[];
#marker: UnitMarker;
#timer: number = 0;
static getConstructor(name: string) {
if (name === "GroundUnit") return GroundUnit;
if (name === "Aircraft") return Aircraft;
if (name === "Helicopter") return Helicopter;
@@ -46,73 +52,41 @@ export class Unit
if (name === "NavyUnit") return NavyUnit;
}
constructor(ID: number, marker: UnitMarker)
{
constructor(ID: number, marker: UnitMarker) {
this.ID = ID;
/* Names */
this.name = "";
this.unitName = "";
this.groupName = "";
/* Position and speed */
this.latitude = 0;
this.longitude = 0;
this.altitude = 0;
this.heading = 0;
this.speed = 0;
/* Tasking */
this.coalitionID = 0;
this.alive = true;
this.currentTask = "";
/* Formation */
this.leader = false;
this.wingman = false;
this.wingmen = [];
this.formation = "";
/* Structures */
this.type = null;
this.flags = null;
this.activePath = null;
this.missionData = null;
this.#selectable = true;
this.#timer = 0;
/* The marker is set by the inherited class */
this.#marker = marker;
this.#marker.on('click', (e) => this.#onClick(e));
this.#marker.on('dblclick', (e) => this.#onDoubleClick(e));
this.#selected = false;
this.#preventClick = false;
this.#pathMarkers = [];
this.#pathPolyline = new Polyline([], {color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1});
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.#pathPolyline.addTo(getMap());
this.#targetsPolylines = [];
}
update(response: JSON)
{
for (let entry in response)
{
update(response: JSON) {
for (let entry in response) {
// @ts-ignore
this[entry] = response[entry];
}
this.#updateMarker();
this.#clearTargets();
if (this.getSelected())
{
this.#drawPath();
this.#drawTargets();
}
else
this.#clearPath();
/*
this.wingmen = [];
if (response["wingmenIDs"] != undefined)
if (response["wingmenIDs"] != null)
{
for (let ID of response["wingmenIDs"])
{
@@ -124,134 +98,114 @@ export class Unit
this.missionData = missionData.getUnitData(this.ID)
this.setSelected(this.getSelected() && this.alive)
this.clearTargets();
this.missionData = missionData.getUnitData(this.ID);
if (this.missionData != undefined)
{
if (this.getSelected())
{
this.drawTargets();
}
}
*/
}
setSelected(selected: boolean)
{
setSelected(selected: boolean) {
/* Only alive units can be selected. Some units are not selectable (weapons) */
if ((this.alive || !selected) && this.#selectable && this.#selected != selected)
{
if ((this.alive || !selected) && this.#selectable && this.#selected != selected) {
this.#selected = selected;
this.#marker.setSelected(selected);
getUnitsManager().onUnitSelection();
}
}
getSelected()
{
getSelected() {
return this.#selected;
}
addDestination(latlng: L.LatLng)
{
addDestination(latlng: L.LatLng) {
var path: any = {};
if (this.activePath != undefined)
{
if (this.activePath != null) {
path = this.activePath;
path[(Object.keys(path).length + 1).toString()] = latlng;
}
else
{
path = {"1": latlng};
else {
path = { "1": latlng };
}
addDestination(this.ID, path);
}
clearDestinations()
{
this.activePath = undefined;
clearDestinations() {
this.activePath = null;
}
#onClick(e: any)
{
getHidden() {
return false;
}
#onClick(e: any) {
this.#timer = setTimeout(() => {
if (!this.#preventClick) {
if (getMap().getState() === 'IDLE' || getMap().getState() === 'MOVE_UNIT' || e.originalEvent.ctrlKey)
{
if (!e.originalEvent.ctrlKey)
{
if (getMap().getState() === 'IDLE' || getMap().getState() === 'MOVE_UNIT' || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().deselectAllUnits();
}
this.setSelected(true);
}
}
this.#preventClick = false;
}, 200);
}, 200);
}
#onDoubleClick(e: any)
{
#onDoubleClick(e: any) {
clearTimeout(this.#timer);
this.#preventClick = true;
var options = [
{'tooltip': 'Attack', 'src': 'attack.png', 'callback': () => {getMap().hideSelectionWheel(); getUnitsManager().attackUnit(this.ID);}},
{'tooltip': 'Go to tanker', 'src': 'tanker.png', 'callback': () => {getMap().hideSelectionWheel(); /*showMessage("Function not implemented yet");*/}},
{'tooltip': 'RTB', 'src': 'rtb.png', 'callback': () => {getMap().hideSelectionWheel(); /*showMessage("Function not implemented yet");*/}}
{ 'tooltip': 'Attack', 'src': 'attack.png', 'callback': () => { getMap().hideSelectionWheel(); getUnitsManager().attackUnit(this.ID); } },
{ 'tooltip': 'Go to tanker', 'src': 'tanker.png', 'callback': () => { getMap().hideSelectionWheel(); /*showMessage("Function not implemented yet");*/ } },
{ 'tooltip': 'RTB', 'src': 'rtb.png', 'callback': () => { getMap().hideSelectionWheel(); /*showMessage("Function not implemented yet");*/ } }
]
if (!this.leader && !this.wingman)
{
options.push({'tooltip': 'Create formation', 'src': 'formation.png', 'callback': () => {getMap().hideSelectionWheel(); /*unitsManager.createFormation(this.ID);*/}});
if (!this.leader && !this.wingman) {
options.push({ 'tooltip': 'Create formation', 'src': 'formation.png', 'callback': () => { getMap().hideSelectionWheel(); /*unitsManager.createFormation(this.ID);*/ } });
}
getMap().showSelectionWheel(e.originalEvent, options, false);
}
#updateMarker()
{
#updateMarker() {
/* Add the marker if not present */
if (!getMap().hasLayer(this.#marker))
{
if (!getMap().hasLayer(this.#marker) && !this.getHidden()) {
this.#marker.addTo(getMap());
}
this.#marker.setLatLng(new LatLng(this.latitude, this.longitude));
this.#marker.draw({
heading: this.heading,
speed: this.speed,
altitude: this.altitude,
alive: this.alive
});
/* Hide the marker if necessary*/
if (getMap().hasLayer(this.#marker) && this.getHidden()) {
getMap().removeLayer(this.#marker);
}
else
{
this.#marker.setLatLng(new LatLng(this.latitude, this.longitude));
this.#marker.draw({
heading: this.heading,
speed: this.speed,
altitude: this.altitude,
alive: this.alive
});
}
}
#drawPath()
{
if (this.activePath != null)
{
#drawPath() {
if (this.activePath != null) {
var _points = [];
_points.push(new LatLng(this.latitude, this.longitude));
/* Add markers if missing */
while (this.#pathMarkers.length < Object.keys(this.activePath).length)
{
var marker = new Marker([0, 0]).addTo(getMap());
while (this.#pathMarkers.length < Object.keys(this.activePath).length) {
var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getMap());
this.#pathMarkers.push(marker);
}
/* Remove markers if too many */
while (this.#pathMarkers.length > Object.keys(this.activePath).length)
{
while (this.#pathMarkers.length > Object.keys(this.activePath).length) {
getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]);
this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1)
}
/* Update the position of the existing markers (to avoid creating markers uselessly) */
for (let WP in this.activePath)
{
for (let WP in this.activePath) {
var destination = this.activePath[WP];
this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]);
_points.push(new LatLng(destination.lat, destination.lng));
@@ -260,26 +214,24 @@ export class Unit
}
}
#clearPath()
{
for (let WP in this.#pathMarkers)
{
#clearPath() {
for (let WP in this.#pathMarkers) {
getMap().removeLayer(this.#pathMarkers[WP]);
}
this.#pathMarkers = [];
this.#pathPolyline.setLatLngs([]);
}
/*
drawTargets()
#drawTargets()
{
for (let typeIndex in this.missionData['targets'])
for (let typeIndex in this.targets)
{
for (let index in this.missionData['targets'][typeIndex])
for (let index in this.targets[typeIndex])
{
var targetData = this.missionData['targets'][typeIndex][index];
var target = unitsManager.getUnitByID(targetData.object["id_"])
if (target != undefined){
var targetData = this.targets[typeIndex][index];
var target = getUnitsManager().getUnitByID(targetData.object["id_"])
if (target != null){
var startLatLng = new LatLng(this.latitude, this.longitude)
var endLatLng = new LatLng(target.latitude, target.longitude)
@@ -301,72 +253,44 @@ export class Unit
color = "#FFFFFF";
}
var targetPolyline = new Polyline([startLatLng, endLatLng], {color: color, weight: 3, opacity: 1, smoothFactor: 1});
targetPolyline.addTo(map.getMap());
targetPolyline.addTo(getMap());
this.#targetsPolylines.push(targetPolyline)
}
}
}
}
clearTargets()
#clearTargets()
{
for (let index in this.#targetsPolylines)
{
map.getMap().removeLayer(this.#targetsPolylines[index])
getMap().removeLayer(this.#targetsPolylines[index])
}
}
*/
attackUnit(targetID: number)
{
// Call DCS attackUnit function
if (this.ID != targetID)
{
attackUnit(targetID: number) {
/* Call DCS attackUnit function */
if (this.ID != targetID) {
attackUnit(this.ID, targetID);
}
else
{
else {
// TODO: show a message
}
}
changeSpeed(speedChange: string)
{
changeSpeed(this.ID, speedChange);
}
changeAltitude(altitudeChange: string)
{
changeAltitude(this.ID, altitudeChange);
}
/*
changeSpeed(speedChange)
{
// TODO move in dedicated file
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(this.unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": this.ID, "change": speedChange}
var data = {"changeSpeed": command}
xhr.send(JSON.stringify(data));
}
changeAltitude(altitudeChange)
{
// TODO move in dedicated file
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(this.unitName + " altitude change request: " + altitudeChange);
}
};
var command = {"ID": this.ID, "change": altitudeChange}
var data = {"changeAltitude": command}
xhr.send(JSON.stringify(data));
}
setformation(formation)
{
// TODO move in dedicated file
@@ -405,66 +329,98 @@ export class Unit
*/
}
export class AirUnit extends Unit
{
export class AirUnit extends Unit {
getHidden() {
if (this.alive)
{
if (this.flags.user && getVisibilitySettings().user === "hidden")
return true
else if (!this.flags.user && getVisibilitySettings().ai === "hidden")
return true
}
else
return getVisibilitySettings().dead === "hidden"
return false;
}
}
export class Aircraft extends AirUnit
{
constructor(ID: number, options: MarkerOptions)
{
var marker = new UnitMarker(options);
export class Aircraft extends AirUnit {
constructor(ID: number, options: MarkerOptions) {
var marker = new AircraftMarker(options);
super(ID, marker);
}
}
export class Helicopter extends AirUnit
{
constructor(ID: number, options: MarkerOptions)
{
var marker = new UnitMarker(options);
export class Helicopter extends AirUnit {
constructor(ID: number, options: MarkerOptions) {
var marker = new HelicopterMarker(options);
super(ID, marker);
}
}
export class GroundUnit extends Unit
{
constructor(ID: number, options: MarkerOptions)
{
var marker = new UnitMarker(options);
export class GroundUnit extends Unit {
constructor(ID: number, options: MarkerOptions) {
var marker = new GroundUnitMarker(options);
super(ID, marker);
}
getHidden() {
if (this.alive)
{
if (this.flags.user && getVisibilitySettings().user === "hidden")
return true
else if (!this.flags.user && getVisibilitySettings().ai === "hidden")
return true
}
else
return getVisibilitySettings().dead === "hidden"
return false;
}
}
export class NavyUnit extends Unit {
constructor(ID: number, options: MarkerOptions) {
var marker = new NavyUnitMarker(options);
super(ID, marker);
}
getHidden() {
if (this.alive)
{
if (this.flags.user && getVisibilitySettings().user === "hidden")
return true
else if (!this.flags.user && getVisibilitySettings().ai === "hidden")
return true
}
else
return getVisibilitySettings().dead === "hidden"
return false;
}
}
export class Weapon extends Unit {
getHidden() {
if (this.alive)
{
if (!this.flags.user && getVisibilitySettings().weapon === "hidden")
return true
}
else
return getVisibilitySettings().dead === "hidden"
return false;
}
}
export class Missile extends Weapon {
constructor(ID: number, options: MarkerOptions) {
var marker = new WeaponMarker(options);
super(ID, marker);
}
}
export class NavyUnit extends Unit
{
constructor(ID: number, options: MarkerOptions)
{
var marker = new UnitMarker(options);
super(ID, marker);
}
}
export class Weapon extends Unit
{
}
export class Missile extends Weapon
{
constructor(ID: number, options: MarkerOptions)
{
var marker = new UnitMarker(options);
super(ID, marker);
}
}
export class Bomb extends Weapon
{
constructor(ID: number, options: MarkerOptions)
{
var marker = new UnitMarker(options);
export class Bomb extends Weapon {
constructor(ID: number, options: MarkerOptions) {
var marker = new WeaponMarker(options);
super(ID, marker);
}
}

View File

@@ -1,43 +1,37 @@
import * as L from 'leaflet'
import { Symbol } from 'milsymbol'
import { getVisibilitySettings } from '..'
export interface MarkerOptions
{
unitName: string
name: string
human: boolean
coalitionID: number
type: any
export interface MarkerOptions {
unitName: string
name: string
human: boolean
coalitionID: number
type: any
}
export interface MarkerData
{
heading: number
speed: number
altitude: number
alive: boolean
export interface MarkerData {
heading: number
speed: number
altitude: number
alive: boolean
}
export class UnitMarker extends L.Marker
{
#unitName: string
#name: string
#human: boolean
#coalitionID: number
#alive: boolean
export class UnitMarker extends L.Marker {
#unitName: string
#name: string
#human: boolean
#alive: boolean = true
#selected: boolean = false
constructor(options: MarkerOptions)
{
super(new L.LatLng(0, 0), {riseOnHover: true});
this.#unitName = options.unitName
this.#name = options.name
this.#human = options.human
this.#coalitionID = options.coalitionID
this.#alive = true;
constructor(options: MarkerOptions) {
super(new L.LatLng(0, 0), { riseOnHover: true });
this.#unitName = options.unitName;
this.#name = options.name;
this.#human = options.human;
var symbol = new Symbol(this.#computeMarkerCode(options), {size: 100});
var img = symbol.asCanvas().toDataURL('image/png');
var symbol = new Symbol(this.#computeMarkerCode(options), { size: 25 });
var img = symbol.asCanvas().toDataURL('image/png');
var icon = new L.DivIcon({
html: `<table class="unit-marker-container" id="container">
@@ -50,25 +44,23 @@ export class UnitMarker extends L.Marker
<div class="unit-marker-name" id="name">${this.#name}</div>
</td>
</tr>
</table>`,
className: 'unit-marker'});
this.setIcon(icon);
</table>`,
className: 'unit-marker'
});
this.setIcon(icon);
}
onAdd(map: L.Map): this
{
onAdd(map: L.Map): this {
super.onAdd(map);
var element = <HTMLElement>this.getElement();
this.addEventListener('mouseover', function(e: any) { e.target?.setHovered(true);});
this.addEventListener('mouseout', function(e: any) { e.target?.setHovered(false);});
this.addEventListener('mouseover', function (e: any) { e.target?.setHovered(true); });
this.addEventListener('mouseout', function (e: any) { e.target?.setHovered(false); });
return this
}
draw(data: MarkerData)
{
draw(data: MarkerData) {
this.#alive = data.alive;
var element = this.getElement();
if (element != null)
{
if (element != null) {
var nameDiv = <HTMLElement>element.querySelector("#name");
var unitNameDiv = <HTMLElement>element.querySelector("#unitName");
var container = <HTMLElement>element.querySelector("#container");
@@ -76,33 +68,70 @@ export class UnitMarker extends L.Marker
var altitudeDiv = <HTMLElement>element.querySelector("#altitude");
var speedDiv = <HTMLElement>element.querySelector("#speed");
/* If visibility is full show all labels */
nameDiv.style.display = '';
unitNameDiv.style.display = '';
altitudeDiv.style.display = '';
speedDiv.style.display = '';
/* If visibility is partial shown only icon and unit name. If none, shown only icon. */
if (this.getVisibility() === "partial" || this.getVisibility() === "none")
{
unitNameDiv.style.display = 'none';
altitudeDiv.style.display = 'none';
speedDiv.style.display = 'none';
}
if (this.getVisibility() === "none")
nameDiv.style.display = 'none';
nameDiv.style.left = (-(nameDiv.offsetWidth - container.offsetWidth) / 2) + "px";
unitNameDiv.style.left = (-(unitNameDiv.offsetWidth - container.offsetWidth) / 2) + "px";
icon.style.transform = "rotate(" + data.heading + "rad)";
altitudeDiv.innerHTML = String(Math.round(data.altitude / 0.3048 / 100) / 10);
speedDiv.innerHTML = String(Math.round(data.speed * 1.94384));
if (!this.#alive)
{
this.getElement()?.querySelector("#icon")?.classList.add("unit-marker-dead");
}
}
}
setSelected(selected: boolean)
{
setSelected(selected: boolean) {
this.#selected = selected;
this.getElement()?.querySelector("#icon")?.classList.remove("unit-marker-hovered");
this.getElement()?.querySelector("#icon")?.classList.toggle("unit-marker-selected", selected);
}
setHovered(hovered: boolean)
{
getSelected() {
return this.#selected;
}
setHovered(hovered: boolean) {
this.getElement()?.querySelector("#icon")?.classList.toggle("unit-marker-hovered", hovered && this.#alive);
}
#computeMarkerCode(options: MarkerOptions)
{
getHuman() {
return this.#human;
}
getAlive() {
return this.#alive;
}
getVisibility() {
return "full";
}
#computeMarkerCode(options: MarkerOptions) {
var identity = "00";
var set = "00";
var entity = "00";
var entityType = "00";
var entitySubtype = "00";
var sectorOneModifier = "00";
var sectorTwoModifier = "00";
/* Identity */
if (options.coalitionID == 1)
@@ -112,8 +141,8 @@ export class UnitMarker extends L.Marker
else
identity = "04" /* Neutral */
if (options.type.level1 == 1)
{
/* Air */
if (options.type.level1 == 1) {
set = "01"
entity = "11"
if (options.type.level2 == 1)
@@ -134,14 +163,103 @@ export class UnitMarker extends L.Marker
else if (options.type.level3 == 6)
entitySubtype = "00";
}
/* Ground */
else if (options.type.level1 == 2)
{
set = "10"
entity = "12"
entityType = "05"
}
/* Navy */
else if (options.type.level1 == 3)
set = "30"
else if (options.type.level1 == 2)
/* Weapon */
else if (options.type.level1 == 4)
{
set = "02"
return `10${identity}${set}0000${entity}${entityType}${entitySubtype}0000`
entity = "11"
if (options.type.level3 == 7)
{
sectorOneModifier = "01"
sectorTwoModifier = "01"
}
else if (options.type.level3 == 8)
{
sectorOneModifier = "01"
sectorTwoModifier = "02"
}
else if (options.type.level3 == 34)
{
sectorOneModifier = "02"
sectorTwoModifier = "01"
}
else if (options.type.level3 == 11)
{
sectorOneModifier = "02"
sectorTwoModifier = "02"
}
}
return `10${identity}${set}0000${entity}${entityType}${entitySubtype}${sectorOneModifier}${sectorTwoModifier}`
}
}
export class AirUnitMarker extends UnitMarker {
getVisibility() {
if (this.getSelected())
return "full";
if (this.getHuman())
return getVisibilitySettings().user;
else if (!this.getAlive())
return "none";
else
return getVisibilitySettings().ai;
}
}
export class AircraftMarker extends AirUnitMarker {
}
export class HelicopterMarker extends AirUnitMarker {
}
export class GroundUnitMarker extends UnitMarker {
/* Are user driven units recognized as human? */
getVisibility() {
if (this.getSelected())
return "full";
if (this.getHuman())
return getVisibilitySettings().user;
else if (!this.getAlive())
return "none";
else
return getVisibilitySettings().ai;
}
}
export class NavyUnitMarker extends UnitMarker {
getVisibility() {
if (this.getSelected())
return "full";
if (!this.getAlive())
return "none";
else
return getVisibilitySettings().ai;
}
}
export class WeaponMarker extends UnitMarker {
getVisibility() {
if (this.getSelected())
return "full";
if (!this.getAlive())
return "none";
else
return getVisibilitySettings().weapon;
}
}

View File

@@ -1,23 +1,20 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getMap, getUnitInfoPanel } from "..";
import { Unit, GroundUnit } from "./unit";
export class UnitsManager
{
#units: { [ID: number]: Unit};
export class UnitsManager {
#units: { [ID: number]: Unit };
#copiedUnits: Unit[];
constructor()
{
constructor() {
this.#units = {};
this.#copiedUnits = [];
}
addUnit(ID: number, data: any)
{
addUnit(ID: number, data: any) {
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.category);
if (constructor != undefined)
{
var constructor = Unit.getConstructor(data.category);
if (constructor != undefined) {
var options = {
unitName: data.unitName,
name: data.name,
@@ -29,92 +26,75 @@ export class UnitsManager
}
}
getUnitByID(ID: number)
{
getUnitByID(ID: number) {
return this.#units[ID];
}
removeUnit(ID: number)
{
removeUnit(ID: number) {
}
deselectAllUnits()
{
for (let ID in this.#units)
{
deselectAllUnits() {
for (let ID in this.#units) {
this.#units[ID].setSelected(false);
}
}
update(data: any)
{
for (let ID in data["units"])
{
update(data: any) {
for (let ID in data["units"]) {
/* Create the unit if missing from the local array, then update the data. Drawing is handled by leaflet. */
if (!(ID in this.#units))
{
if (!(ID in this.#units)) {
this.addUnit(parseInt(ID), data["units"][ID]);
}
this.#units[parseInt(ID)].update(data["units"][ID]);
}
if (this.getSelectedUnits().length == 1)
{
if (this.getSelectedUnits().length == 1) {
getUnitInfoPanel().show();
getUnitInfoPanel().update(this.getSelectedUnits()[0]);
}
else
{
else {
getUnitInfoPanel().hide();
}
}
onUnitSelection()
{
if (this.getSelectedUnits().length > 0)
{
onUnitSelection() {
if (this.getSelectedUnits().length > 0) {
getMap().setState("MOVE_UNIT");
//unitControlPanel.setEnabled(true);
}
else
{
else {
getMap().setState("IDLE");
//unitControlPanel.setEnabled(false);
}
}
// selectFromBounds(bounds)
// {
// this.deselectAllUnits();
// for (let ID in this.#units)
// {
// var latlng = new LatLng(this.#units[ID].latitude, this.#units[ID].longitude);
// if (bounds.contains(latlng))
// {
// this.#units[ID].setSelected(true);
// }
// }
// }
getSelectedUnits()
selectFromBounds(bounds: LatLngBounds)
{
var selectedUnits = [];
this.deselectAllUnits();
for (let ID in this.#units)
{
if (this.#units[ID].getSelected())
var latlng = new LatLng(this.#units[ID].latitude, this.#units[ID].longitude);
if (bounds.contains(latlng))
{
this.#units[ID].setSelected(true);
}
}
}
getSelectedUnits() {
var selectedUnits = [];
for (let ID in this.#units) {
if (this.#units[ID].getSelected()) {
selectedUnits.push(this.#units[ID]);
}
}
return selectedUnits;
}
addDestination(latlng: L.LatLng)
{
addDestination(latlng: L.LatLng) {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
//if (selectedUnits[idx].wingman)
//{
@@ -124,11 +104,9 @@ export class UnitsManager
}
}
clearDestinations()
{
clearDestinations() {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
//if (selectedUnits[idx].wingman)
//{
@@ -138,28 +116,23 @@ export class UnitsManager
}
}
// selectedUnitsMove()
// {
// }
selectedUnitsChangeSpeed(speedChange: string)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].changeSpeed(speedChange);
}
}
// selectedUnitsChangeSpeed(speedChange)
// {
// var selectedUnits = this.getSelectedUnits();
// for (let idx in selectedUnits)
// {
// selectedUnits[idx].changeSpeed(speedChange);
// }
// }
// selectedUnitsChangeAltitude(altitudeChange)
// {
// var selectedUnits = this.getSelectedUnits();
// for (let idx in selectedUnits)
// {
// selectedUnits[idx].changeAltitude(altitudeChange);
// }
// }
selectedUnitsChangeAltitude(altitudeChange: string)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].changeAltitude(altitudeChange);
}
}
// handleKeyEvent(e)
// {
@@ -187,11 +160,9 @@ export class UnitsManager
// }
// }
attackUnit(ID: number)
{
attackUnit(ID: number) {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
for (let idx in selectedUnits) {
/* If a unit is a wingman, send the command to its leader */
var commandedUnit = selectedUnits[idx];
//if (selectedUnits[idx].wingman)

View File

@@ -1,7 +1,6 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
@@ -9,9 +8,8 @@
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
@@ -23,26 +21,28 @@
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": ["./node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */
"types": ["leaflet", "geojson"], /* Specify type package names to be included without being referenced in a source file. */
"typeRoots": [
"./node_modules/@types"
], /* Specify multiple folders that act like './node_modules/@types'. */
"types": [
"leaflet",
"geojson"
], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
@@ -67,16 +67,14 @@
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
@@ -95,12 +93,11 @@
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src/**/*.ts"
]
}
}

View File

@@ -8,15 +8,15 @@
<body>
<div id="map-container"></div>
<%- include('unitinfo.ejs') %>
<%- include('selectionwheel.ejs') %>
<%- include('unitinfo.ejs') %>
<%- include('selectionwheel.ejs') %>
<%- include('selectionscroll.ejs') %>
<div class="olympus-dropdown" id="map-source-dropdown">Satellite</div>
<div class="olympus-dropdown" id="scenario-dropdown">Nevada</div>
<%- include('unitcontrol.ejs') %>
<%- include('visibilitycontrol.ejs') %>
<!-- ENABLE ME BACK <%- include('connectionstatus.ejs') %> -->
<%- include('connectionstatus.ejs') %>
<script src="javascripts/bundle.js"></script>
</body>
</html>
</html>

View File

@@ -2,6 +2,7 @@
<div class="olympus-wheel">
</div>
<label id="coalition-switch-container">
<input type="checkbox" id="coalition-switch"> <span class="olympus-selection-wheel-slider olympus-selection-wheel-switch"></span>
<input type="checkbox" id="coalition-switch"> <span
class="olympus-selection-wheel-slider olympus-selection-wheel-switch"></span>
</label>
</div>

View File

@@ -1,6 +1,6 @@
<div class="olympus-panel" id="visibility-control-panel">
<div class="olympus-button" ></div>
<div class="olympus-button" ></div>
<div class="olympus-button" ></div>
<div class="olympus-button" ></div>
<div class="olympus-button" id="user-visibility-button"></div>
<div class="olympus-button" id="ai-visibility-button"></div>
<div class="olympus-button" id="weapon-visibility-button"></div>
<div class="olympus-button" id="dead-visibility-button"></div>
</div>