Merge pull request #4 from Pax1601/node-migration

Node migration
This commit is contained in:
Pax1601 2023-01-27 08:19:05 +01:00 committed by GitHub
commit 3a15951267
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1845 changed files with 22572 additions and 137799 deletions

View File

@ -9,7 +9,11 @@
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}\\.."
"webRoot": "${workspaceFolder}/public/",
"sourceMapPathOverrides": {
"src/*": "${workspaceFolder}/src/*"
},
"preLaunchTask": "server"
}
]
}

View File

@ -1,41 +1,13 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"presentation": {
"focus": true,
"panel": "dedicated"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": {
"owner": "typescript",
"source": "ts",
"applyTo": "closedDocuments",
"fileLocation": [
"relative",
"${cwd}"
],
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "Compiled |Failed to compile."
}
}
},
"options": {
"cwd": "${workspaceFolder}"
}
}
]
}
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "server",
"type": "shell",
"command": "npm run start",
"isBackground": true
}
]
}

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

@ -17,4 +17,6 @@ app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.set('view engine', 'ejs');
module.exports = app;

View File

@ -1,2 +1,2 @@
start cmd /k "npm run start"
start cmd /k "watchify .\index.ts --debug -p [ tsify --noImplicitAny ] -o .\public\javascripts\bundle.js"
start cmd /k "watchify .\src\index.ts --debug -p [ tsify --noImplicitAny ] -o .\public\javascripts\bundle.js"

View File

@ -1 +0,0 @@
console.log("Hellorld!!!")

821
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,31 @@
{
"name": "client",
"name": "DCS Olympus",
"node-main": "./bin/www",
"main": "http://localhost:3000",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
"start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"",
"copy": "copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet.css",
"watch": "watchify .\\src\\index.ts --debug -p [ tsify --noImplicitAny ] -o .\\public\\javascripts\\bundle.js"
},
"dependencies": {
"@types/geojson": "^7946.0.10",
"@types/leaflet": "^1.9.0",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "^3.1.8",
"express": "~4.16.1",
"leaflet": "^1.9.3",
"morgan": "~1.9.1"
"milsymbol": "^2.0.0",
"morgan": "~1.9.1",
"save": "^2.9.0"
},
"devDependencies": {
"browserify": "^17.0.0",
"concurrently": "^7.6.0",
"tsify": "^5.0.4",
"typescript": "^4.9.4",
"watchify": "^4.0.0"
}
}
}

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 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="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

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,36 @@
<?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>

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

@ -0,0 +1,36 @@
<?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>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34zm192-34l-136-136c-9.4-9.4-24.6-9.4-33.9 0l-22.6 22.6c-9.4 9.4-9.4 24.6 0 33.9l96.4 96.4-96.4 96.4c-9.4 9.4-9.4 24.6 0 33.9l22.6 22.6c9.4 9.4 24.6 9.4 33.9 0l136-136c9.4-9.2 9.4-24.4 0-33.8z"/></svg>

After

Width:  |  Height:  |  Size: 595 B

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"/></svg>

After

Width:  |  Height:  |  Size: 406 B

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 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

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="altitude.svg"
id="svg4"
version="1.1"
viewBox="0 0 640 512">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
inkscape:current-layer="svg4"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="-8"
inkscape:cy="268.97402"
inkscape:cx="234.71843"
inkscape:zoom="0.81953125"
showgrid="false"
id="namedview6"
inkscape:window-height="1017"
inkscape:window-width="1920"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) -->
<path
style="stroke-width:0.671054"
id="path837"
d="m 132.70945,183.88092 c 1.69844,5.99526 5.81278,10.98349 11.34732,13.76423 l 78.2866,39.30502 a 44.034591,44.034591 0 0 0 19.91285,4.68197 l 218.9503,-0.63901 c 20.12229,-0.0591 40.33567,-4.43039 57.76997,-14.71021 19.57187,-11.5398 26.98708,-23.29049 27.05015,-34.19915 0.0781,-10.90862 -7.19705,-22.61379 -26.62131,-34.03766 -17.30329,-10.17501 -37.46953,-14.4286 -57.5888,-14.37553 L 387.68668,143.88712 281.39609,28.67846 a 11.944098,11.944098 0 0 0 -10.45221,-6.09982 l -49.47892,0.14718 c -8.02791,0.0207 -13.87346,7.77572 -11.71221,15.53701 l 64.00366,105.95731 -77.66813,0.22982 -32.34806,-43.70043 a 11.947453,11.947453 0 0 0 -9.63675,-4.8399 l -30.19994,0.0916 c -7.85685,0.0242 -13.67748,7.47621 -11.81606,15.14656 z" />
<path
id="path2"
d="M 624,448 H 16 c -8.84,0 -16,7.16 -16,16 v 32 c 0,8.84 7.16,16 16,16 h 608 c 8.84,0 16,-7.16 16,-16 v -32 c 0,-8.84 -7.16,-16 -16,-16 z" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M336 448H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h320c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm157.2-340.7l-81-81c-6.2-6.2-16.4-6.2-22.6 0l-11.3 11.3c-6.2 6.2-6.2 16.4 0 22.6L416 97.9V160c0 28.1 20.9 51.3 48 55.2V376c0 13.2-10.8 24-24 24s-24-10.8-24-24v-32c0-48.6-39.4-88-88-88h-8V64c0-35.3-28.7-64-64-64H96C60.7 0 32 28.7 32 64v352h288V304h8c22.1 0 40 17.9 40 40v27.8c0 37.7 27 72 64.5 75.9 43 4.3 79.5-29.5 79.5-71.7V152.6c0-17-6.8-33.3-18.8-45.3zM256 192H96V64h160v128z"/></svg>

After

Width:  |  Height:  |  Size: 695 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M347.94 129.86L203.6 195.83a31.938 31.938 0 0 0-15.77 15.77l-65.97 144.34c-7.61 16.65 9.54 33.81 26.2 26.2l144.34-65.97a31.938 31.938 0 0 0 15.77-15.77l65.97-144.34c7.61-16.66-9.54-33.81-26.2-26.2zm-77.36 148.72c-12.47 12.47-32.69 12.47-45.16 0-12.47-12.47-12.47-32.69 0-45.16 12.47-12.47 32.69-12.47 45.16 0 12.47 12.47 12.47 32.69 0 45.16zM248 8C111.03 8 0 119.03 0 256s111.03 248 248 248 248-111.03 248-248S384.97 8 248 8zm0 448c-110.28 0-200-89.72-200-200S137.72 56 248 56s200 89.72 200 200-89.72 200-200 200z"/></svg>

After

Width:  |  Height:  |  Size: 730 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M288 32C128.94 32 0 160.94 0 320c0 52.8 14.25 102.26 39.06 144.8 5.61 9.62 16.3 15.2 27.44 15.2h443c11.14 0 21.83-5.58 27.44-15.2C561.75 422.26 576 372.8 576 320c0-159.06-128.94-288-288-288zm0 64c14.71 0 26.58 10.13 30.32 23.65-1.11 2.26-2.64 4.23-3.45 6.67l-9.22 27.67c-5.13 3.49-10.97 6.01-17.64 6.01-17.67 0-32-14.33-32-32S270.33 96 288 96zM96 384c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32zm48-160c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32zm246.77-72.41l-61.33 184C343.13 347.33 352 364.54 352 384c0 11.72-3.38 22.55-8.88 32H232.88c-5.5-9.45-8.88-20.28-8.88-32 0-33.94 26.5-61.43 59.9-63.59l61.34-184.01c4.17-12.56 17.73-19.45 30.36-15.17 12.57 4.19 19.35 17.79 15.17 30.36zm14.66 57.2l15.52-46.55c3.47-1.29 7.13-2.23 11.05-2.23 17.67 0 32 14.33 32 32s-14.33 32-32 32c-11.38-.01-20.89-6.28-26.57-15.22zM480 384c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,14 +0,0 @@
<html>
<head>
<title>Express</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<h1>Express</h1>
<p>Welcome to Express</p>
<script src="javascripts/bundle.js"></script>
</body>
</html>

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

@ -0,0 +1,17 @@
.olympus-button {
width: 24px;
height: 24px;
background-color: transparent;
cursor: pointer;
display: flex;
align-items: center;
}
.olympus-button img {
width: 24px;
height: 24px;
}
.olympus-button:hover {}
.olympus-button:active {}

View File

@ -0,0 +1,33 @@
#connection-status-panel {
display: flex;
align-items: center;
padding-left: 15px;
padding-right: 4px;
}
#status-string {
font-size: 14px;
color: white;
}
.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

@ -0,0 +1,90 @@
.olympus-dropdown {
width: 100%;
min-width: 100px;
height: 30px;
position: relative;
background-color: #DDDD;
z-index: 1000;
border-radius: 15px;
font-family: Verdana, Geneva, Tahoma, sans-serif;
color: var(--background-color-dark);
padding-left: 5px;
align-items: center;
cursor: pointer;
font-size: 13px;
display: flex;
text-shadow: none;
box-shadow: 0px 2px 5px #000A;
padding-left: 15px;
}
.olympus-dropdown::before {
content: "";
position: absolute;
height: 30px;
width: 30px;
top: 0px;
right: 0px;
background-color: var(--background-color-dark);
z-index: 1000;
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.olympus-dropdown-open {
border-bottom-left-radius: 0px;
}
.olympus-dropdown-open::after {
content: "";
position: absolute;
top: 13px;
right: 11px;
height: 1px;
width: 1px;
border: solid white;
border-width: 0 3px 3px 0;
padding: 3px;
z-index: 1000;
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
}
.olympus-dropdown-closed::after {
content: "";
position: absolute;
top: 9px;
right: 11px;
height: 1px;
width: 1px;
border: solid white;
border-width: 0 3px 3px 0;
padding: 3px;
z-index: 1000;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
}
.olympus-dropdown-content {
position: fixed;
/*overflow: visible;
overflow-y: scroll;*/
background-color: #DDDD;
z-index: 2000;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.olympus-dropdown-element {
margin: 2px;
font-family: Verdana, Geneva, Tahoma, sans-serif;
color: var(--background-color-dark);
cursor: pointer;
opacity: 1;
font-size: 13px;
padding-left: 5px;
}
.olympus-dropdown-element:hover {
background-color: var(--highlight-color);
}

View File

@ -0,0 +1,14 @@
.olympus-element-1 {
background-color: #247be2;
height: 28;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
}
.bottom-separator {
border-bottom: 1px solid gray;
}

View File

@ -0,0 +1,94 @@
/* Page style */
body {
padding: 0;
margin: 0;
}
html,
body {
height: 100%;
width: 100%;
}
#map-container {
height: 100%;
width: 100%;
min-width: 820px;
}
#unit-info-panel {
position: fixed;
height: 160px;
width: 800px;
left: 10px;
bottom: 10px;
z-index: 1000;
}
#map-source-dropdown {
position: absolute;
left: 10px;
top: 10px;
width: 200px;
color: black;
}
#scenario-dropdown {
position: absolute;
left: 220px;
top: 10px;
width: 200px;
}
#visibility-control-panel {
position: absolute;
left: 430px;
height: 30px;
width: 110;
top: 10px;
z-index: 1000;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
#unit-control-buttons {
position: absolute;
top: 10px;
height: fit-content;
width: fit-content;
right: 10px;
z-index: 1000;
}
#unit-control-panel {
display: none;
position: absolute;
top: 10px;
height: fit-content;
width: 250px;
right: 10px;
z-index: 1000;
}
#connection-status-panel {
position: absolute;
height: 30px;
width: 140px;
bottom: 10px;
right: 10px;
z-index: 1000;
}
@media only screen and (max-width: 1000px) {
#unit-control-buttons {
top: 50px;
}
#unit-control-panel {
top: 50px;
}
}

View File

@ -0,0 +1,656 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

View File

@ -0,0 +1,8 @@
/* Panels style */
.olympus-panel {
background-color: var(--background-color-dark);
font-size: 12px;
transition: bottom 0.2s;
border-radius: 15px;
box-shadow: 0px 2px 5px #000A;
}

View File

@ -0,0 +1,91 @@
.olympus-selection-scroll-container {
position: fixed;
background-color: var(--background-color-dark);
font-size: 12px;
transition: bottom 0.2s;
border-radius: 15px;
box-shadow: 0px 2px 5px #000A;
width: 180px;
height: fit-content;
z-index: 1000;
max-height: 400px;
padding: 10px;
}
.olympus-selection-scroll {
overflow-y: auto;
max-height: calc(400px - 60px);
}
.olympus-selection-scroll::-webkit-scrollbar {
width: 10px;
}
.olympus-selection-scroll::-webkit-scrollbar-track {
background-color: transparent;
border-radius: 100px;
}
.olympus-selection-scroll::-webkit-scrollbar-thumb {
background-color: white;
border-radius: 100px;
opacity: 0.8;
margin-top: 10px;
}
.olympus-selection-scroll-element {
margin: 2px;
color: white;
cursor: pointer;
font-size: 14px;
opacity: 1;
border-radius: 5px;
margin: 5px;
padding: 5px;
padding-left: 15px;
}
.olympus-selection-scroll-element:hover {
background-color: var(--highlight-color);
}
.olympus-selection-scroll-container label {
width: 0;
height: 0;
}
.olympus-selection-scroll-container input {
width: 0;
height: 0;
}
.olympus-selection-scroll-switch {
position: relative;
display: inline-block;
left: calc(50% - 10px);
width: 60px;
height: 34px;
background-color: var(--active-coalition-color);
border-radius: 999px;
cursor: pointer;
margin-left: -30px;
}
.olympus-selection-scroll-switch:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: 0.2s;
transition: 0.2s;
border-radius: 999px;
}
input:checked+.olympus-selection-scroll-switch:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}

View File

@ -0,0 +1,102 @@
.olympus-selection-wheel {
margin: 0;
position: fixed;
z-index: 1000;
width: 220px;
height: 220px;
overflow: visible;
display: flex;
align-items: center;
justify-content: center;
}
.olympus-wheel {
width: 100%;
border-radius: 50%;
background-color: var(--background-color-dark);
-webkit-mask: radial-gradient(transparent 30%, #000 31%);
mask: radial-gradient(transparent 30%, #000 31%);
transition: background-color 0.2s;
}
.olympus-wheel:before {
content: "";
display: block;
padding-top: 100%;
}
.selection-wheel-button {
position: fixed;
z-index: 1000;
width: 50px;
height: 50px;
opacity: 0;
/*transition: opacity var(--animation_duration), left var(--animation_duration), top var(--animation_duration);*/
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.selection-wheel-image {
width: 45px;
height: 45px;
/*filter: invert(100%);*/
transition: width var(--animation_duration), height var(--animation_duration);
filter: invert(100%);
}
.selection-wheel-button:hover {
}
.selection-wheel-button:hover .selection-wheel-image {
width: 50px;
height: 50px;
/*filter: invert(21%) sepia(23%) saturate(775%) hue-rotate(170deg) brightness(92%) contrast(90%);*/
}
.olympus-selection-wheel label {
width: 0;
height: 0;
}
.olympus-selection-wheel input {
width: 0;
height: 0;
}
.olympus-selection-wheel-switch {
position: absolute;
top: 50%;
left: 50%;
display: inline-block;
width: 60px;
height: 34px;
background-color: var(--active-coalition-color);
border-radius: 17px;
margin-left: -30px;
margin-top: -17px;
cursor: pointer;
}
.olympus-selection-wheel-switch:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: 0.2s;
transition: 0.2s;
border-radius: 50%;
}
input:checked+.olympus-selection-wheel-switch:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}

View File

@ -1,8 +1,87 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
@import url("button.css");
@import url("dropdown.css");
@import url("selectionwheel.css");
@import url("selectionscroll.css");
@import url("unitmarker.css");
@import url("airbasemarker.css");
@import url("panels.css");
@import url("connectionstatuspanel.css");
@import url("unitcontrolpanel.css");
@import url("visibilitycontrolpanel.css");
@import url("unitinfopanel.css");
@import url("layout.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;
--neutral-coalition-color: #AAAAAA;
--active-coalition-color: var(--blue-coalition-color);
--highlight-color: #FFFFFFAA;
}
a {
color: #00B7FF;
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
html {
font-family: 'Open Sans', sans-serif;
}
.leaflet-container.crosshair-cursor-enabled {
cursor:crosshair;
}
.rectangular-container {
padding: 0.5em;
background-color: gray;
border-radius: 5px;
width: fit-content;
height: fit-content;
text-align: center;
color: white;
font-size: 12px;
}
.rectangular-container-dark {
padding-left: 0.5em;
padding-right: 0.5em;
padding-top: 0.2em;
padding-bottom: 0.2em;
background-color: #151b20;
border-radius: 5px;
width: fit-content;
height: fit-content;
text-align: center;
color: white;
font-size: 12px;
}
.rounded-container {
padding: 0.5em;
background-color: #247be2;
border-radius: 10px;
width: fit-content;
height: fit-content;
text-align: center;
color: white;
font-size: 12px;
border-radius: 9999px;
}
.vl {
border-left: 1px solid #555;
width: 1px !important;
display: inline-block;
}

View File

@ -0,0 +1,41 @@
#unit-control-buttons {
display: flex;
flex-direction: column;
row-gap: 5px;
padding: 10px;
border-radius: 999px;
}
#unit-control-buttons>div {
filter: invert(100%);
opacity: 0.8;
}
#unit-control-panel {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: flex-start;
row-gap: 5px;
padding-left: 30px;
padding-right: 30px;
padding-top: 20px;
padding-bottom: 20px;
}
/* Common */
#unit-info-panel>div {
height: 100%;
width: 100%;
}
#unit-control-panel .rounded-container {
width: 100%;
}
#unit-control-panel #title-label {
color: white;
font-size: 14px;
width: 100%;
font-weight: 600;
}

View File

@ -0,0 +1,121 @@
/* Panel properties */
#unit-info-panel {
display: flex;
flex-direction: row;
padding-left: 30px;
padding-right: 30px;
padding-top: 20px;
padding-bottom: 20px;
}
/* Common */
#unit-info-panel>div {
height: 100%;
width: 100%;
}
#unit-info-panel>.vl {
margin-left: 30px;
margin-right: 30px;
}
/* Sections */
#unit-info-panel #general {
display: flex;
flex-flow: row;
flex-wrap: wrap;
justify-content: flex-start;
column-gap: 5px;
row-gap: 5px;
align-content: flex-start;
}
#unit-info-panel #flight-data {
display: flex;
flex-flow: row;
flex-wrap: wrap;
justify-content: space-between;
align-content: center;
align-items: center;
align-content: space-between;
}
#unit-info-panel #loadout-data {
display: flex;
flex-flow: row;
flex-wrap: wrap;
align-content: flex-start;
justify-content: flex-start;
row-gap: 5px;
}
/* General section */
#general #unit-name {
color: white;
font-size: 18px;
width: 100%;
padding-bottom: 5px;
font-weight: 600;
}
#general #task {
width: 100%;
}
/* Flight data section */
#flight-data #flight-data-label {
color: white;
font-size: 14px;
width: 100%;
font-weight: 600;
}
#flight-data #latitude {
width: 50%;
color: white;
font-size: 13px;
text-align: center;
font-weight: 600;
color: #8bff63;
}
#flight-data #longitude {
width: 50%;
color: white;
font-size: 13px;
text-align: center;
font-weight: 600;
color: #5ca7ff;
}
.flight-data-label, .flight-data-value {
color: white;
font-size: 12px;
padding-left: 10px;
}
.flight-data-icon {
width: 10%;
padding: 2px;
filter: invert(100%);
}
.flight-data-label {
width: 58%;
}
.flight-data-value {
width: 30%;
font-weight: 600;
text-align: right;
}
/* Loadout section */
#loadout-data #loadout-label {
color: white;
font-size: 14px;
width: 100%;
font-weight: 600;
}

View File

@ -0,0 +1,108 @@
.unit-marker-container {
height: 60px;
width: 60px;
left: -30px;
top: -30px;
border: 0px black solid;
position: absolute;
padding: 0;
margin: 0;
border-collapse: collapse;
}
.unit-marker-icon {
height: 60px;
width: 60px;
left: 0px;
top: 0px;
display: flex;
position: absolute;
justify-content: center;
align-items: center;
}
.unit-marker-selected {
width: 100%;
height: 100%;
border-radius: 50%;
}
#ring {
top: 0px;
position: absolute;
display: inline-block;
-webkit-mask: radial-gradient(transparent 60%, #000 61%);
mask: radial-gradient(transparent 60%, #000 61%);
}
#background {
top: 0px;
position: absolute;
display: inline-block;
opacity: 0.2;
}
.blue.unit-marker-selected {
background-color: var(--blue-coalition-color);
}
.red.unit-marker-selected {
background-color: var(--red-coalition-color);
}
.unit-marker-hovered {
filter: brightness(130%);
}
.unit-marker-dead {
filter: brightness(50%);
}
.unit-marker-unitName {
top: -20px;
position: absolute;
text-align: center;
font: 800 16px Arial;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}
.unit-marker-name {
bottom: -20px;
position: absolute;
text-align: center;
font: 800 14px Arial;
white-space: nowrap;
-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;
white-space: nowrap;
-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;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}
.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

@ -0,0 +1,38 @@
export class Button {
#container: HTMLElement | null;
#srcs: string[];
#callback: CallableFunction;
#img: any;
#state: number = 0;
constructor(ID: string, srcs: string[], callback: CallableFunction) {
this.#container = document.getElementById(ID);
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

@ -0,0 +1,62 @@
export class Dropdown {
#container: HTMLElement | null;
#options: string[];
#open?: boolean;
#content?: HTMLElement;
#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();
})
if (this.#container != null && this.#options.length > 0)
this.#container.innerHTML = this.#options[0];
}
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) {
var node = document.createElement("div");
node.classList.add("olympus-dropdown-element");
node.appendChild(document.createTextNode(this.#options[optionID]));
this.#content.appendChild(node);
height += node.offsetHeight + 2;
node.addEventListener('click', () => {
this.close();
if (this.#container != null)
this.#container.innerHTML = this.#options[optionID];
if (this.#callback != null)
this.#callback(this.#options[optionID])
})
}
this.#content.style.height = height + "px";
}
}
close() {
if (this.#container != null) {
this.#open = false;
this.#container?.classList.remove("olympus-dropdown-open");
this.#container?.classList.add("olympus-dropdown-closed");
if (this.#content != null)
document.body.removeChild(this.#content);
}
}
}

View File

@ -0,0 +1,72 @@
import { LatLng } from "leaflet";
import { setActiveCoalition } from "..";
export class SelectionScroll {
#container: HTMLElement | null;
#display: string;
constructor(id: string,) {
this.#container = document.getElementById(id);
this.#display = '';
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, callback: CallableFunction, showCoalition: boolean) {
/* Hide to remove buttons, if present */
this.hide();
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";
var scroll = this.#container.querySelector(".olympus-selection-scroll");
if (scroll != null)
{
for (let optionID in options) {
var node = document.createElement("div");
node.classList.add("olympus-selection-scroll-element");
if (typeof options[optionID] === 'string' || options[optionID] instanceof String){
node.appendChild(document.createTextNode(options[optionID]));
node.addEventListener('click', () => callback(options[optionID]));
}
else {
node.appendChild(document.createTextNode(options[optionID].tooltip));
node.addEventListener('click', () => options[optionID].callback());
}
scroll.appendChild(node);
}
}
}
}
hide() {
if (this.#container != null) {
this.#container.style.display = "none";
var buttons = this.#container.querySelectorAll(".olympus-selection-scroll-element");
var scroll = this.#container.querySelector(".olympus-selection-scroll");
if (scroll != null)
{
for (let child of buttons) {
scroll.removeChild(child);
}
}
}
}
#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 {
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
setActiveCoalition("blue");
}
}
}
}

View File

@ -0,0 +1,92 @@
import { getActiveCoalition, setActiveCoalition } from "..";
import { deg2rad } from "../other/utils";
export class SelectionWheel {
#container: HTMLElement | null;
#display: string;
constructor(id: string) {
this.#container = document.getElementById(id);
this.#display = '';
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) {
/* Hide to remove buttons, if present */
this.hide();
if (this.#container != null) {
this.#container.style.display = this.#display;
this.#container.style.left = x - 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) {
var button = document.createElement("div");
button.classList.add("selection-wheel-button");
button.style.left = x - 25 + "px";
button.style.top = y - 25 + "px";
button.addEventListener('click', (e) => options[id].callback(e));
this.#container.appendChild(button);
var angle = parseInt(id) * angularSize;
button.style.opacity = "1";
button.style.left = x + r * Math.sin(deg2rad(angle)) - 25 + "px";
button.style.top = y - r * Math.cos(deg2rad(angle)) - 25 + "px";
var image = document.createElement("img");
image.classList.add("selection-wheel-image");
image.src = `images/buttons/${options[id].src}`
image.title = options[id].tooltip;
if ('tint' in options[id]) {
button.style.setProperty('background-color', options[id].tint);
image.style.opacity = "0";
}
button.appendChild(image);
}
/* Hide the coalition switch if required */
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 {
switchContainer.style.display = "block";
if (getActiveCoalition() == "blue")
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
else
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--red-coalition-color"));
}
}
}
hide() {
if (this.#container != null) {
this.#container.style.display = "none";
var buttons = this.#container.querySelectorAll(".selection-wheel-button");
for (let child of buttons) {
this.#container.removeChild(child);
}
}
}
#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 {
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
setActiveCoalition("blue");
}
}
}
}

148
client/src/dcs/dcs.ts Normal file
View File

@ -0,0 +1,148 @@
import * as L from 'leaflet'
import { getUnitsManager, setConnected } from '..';
import { ConvertDDToDMS } from '../other/utils';
/* Edit here to change server address */
var RESTaddress = "http://localhost:30000/restdemo";
export function getDataFromDCS(callback: CallableFunction) {
/* Request the updated unit data from the server */
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", RESTaddress, true);
xmlHttp.onload = function (e) {
var data = JSON.parse(xmlHttp.responseText);
callback(data);
setConnected(true);
};
xmlHttp.onerror = function () {
console.error("An error occurred during the XMLHttpRequest");
setConnected(false);
};
xmlHttp.send(null);
}
export function addDestination(ID: number, path: any) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => { };
var command = { "ID": ID, "path": path }
var data = { "setPath": command }
xhr.send(JSON.stringify(data));
}
export function spawnSmoke(color: string, latlng: L.LatLng) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Added " + color + " smoke at " + ConvertDDToDMS(latlng.lat, false) + " " + ConvertDDToDMS(latlng.lng, true));
}
};
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) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Added " + coalition + " " + type + " at " + ConvertDDToDMS(latlng.lat, false) + " " + ConvertDDToDMS(latlng.lng, true));
}
};
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: string | null = null, airbaseName: string | null = null) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Added " + coalition + " " + type + " at " + ConvertDDToDMS(latlng.lat, false) + " " + ConvertDDToDMS(latlng.lng, true));
}
};
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) {
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);
}
};
var command = { "ID": ID, "targetID": targetID };
var data = { "attackUnit": command }
xhr.send(JSON.stringify(data));
}
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");
}
};
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));
}

165
client/src/index.ts Normal file
View File

@ -0,0 +1,165 @@
import { Map } from "./map/map"
import { getDataFromDCS } from "./dcs/dcs"
import { SelectionWheel } from "./controls/selectionwheel";
import { UnitsManager } from "./units/unitsmanager";
import { UnitInfoPanel } from "./panels/unitinfopanel";
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;
var selectionWheel: SelectionWheel;
var selectionScroll: SelectionScroll;
var unitsManager: UnitsManager;
var unitInfoPanel: UnitInfoPanel;
var activeCoalition: string;
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() {
/* 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", "Marianas", "Nevada", "South Atlantic", "The channel"], () => { });
mapSourceDropdown = new Dropdown("map-source-dropdown", map.getLayers(), (option: string) => map.setLayer(option));
connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel");
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";
connected = false;
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());
}
export function update(data: JSON) {
unitsManager.update(data);
missionData.update(data);
}
export function getMap() {
return map;
}
export function getSelectionWheel() {
return selectionWheel;
}
export function getSelectionScroll() {
return selectionScroll;
}
export function getUnitsManager() {
return unitsManager;
}
export function getUnitInfoPanel() {
return unitInfoPanel;
}
export function setActiveCoalition(newActiveCoalition: string) {
activeCoalition = newActiveCoalition;
}
export function getActiveCoalition() {
return activeCoalition;
}
export function setConnected(newConnected: boolean) {
connected = newConnected
}
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();
}
}
});

282
client/src/map/map.ts Normal file
View File

@ -0,0 +1,282 @@
import * as L from "leaflet"
import { getSelectionWheel, getSelectionScroll, getUnitsManager, getActiveCoalition } from "..";
import { spawnAircraft, spawnGroundUnit, spawnSmoke } from "../dcs/dcs";
import { payloadNames } from "../units/payloadNames";
import { unitTypes } from "../units/unitTypes";
import { BoxSelect } from "./boxselect";
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) {
/* Init the leaflet map */
//@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("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) {
this.removeLayer(this.#layer)
}
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") {
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") {
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") {
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") {
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") {
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'
});
}
this.#layer?.addTo(this);
}
getLayers() {
return ["ArcGIS Satellite", "USGS Topo", "OpenStreetMap Mapnik", "OPENVKarte", "Esri.DeLorme", "CyclOSM"]
}
/* State machine */
setState(state: string) {
this.#state = state;
if (this.#state === "IDLE") {
L.DomUtil.removeClass(this.getContainer(),'crosshair-cursor-enabled');
}
else if (this.#state === "MOVE_UNIT") {
L.DomUtil.addClass(this.getContainer(),'crosshair-cursor-enabled');
}
else if (this.#state === "ATTACK") {
}
else if (this.#state === "FORMATION") {
}
}
getState() {
return this.#state;
}
/* Selection wheel */
showSelectionWheel(e: ClickEvent | SpawnEvent, options: any, showCoalition: boolean) {
var x = e.x;
var y = e.y;
getSelectionWheel().show(x, y, options, showCoalition);
}
hideSelectionWheel() {
getSelectionWheel().hide();
}
/* Selection scroll */
showSelectionScroll(e: ClickEvent | SpawnEvent, options: any, callback: CallableFunction, showCoalition: boolean = false) {
var x = e.x;
var y = e.y;
getSelectionScroll().show(x, y, options, callback, showCoalition);
}
hideSelectionScroll() {
getSelectionScroll().hide();
}
/* Event handlers */
#onClick(e: any) {
this.hideSelectionWheel();
this.hideSelectionScroll();
if (this.#state === "IDLE") {
}
else if (this.#state === "MOVE_UNIT") {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().clearDestinations();
}
getUnitsManager().addDestination(e.latlng)
}
}
#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": "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.showSelectionScroll(spawnEvent, options, () => {}, true);
}
}
#onContextMenu(e: any) {
this.#rightClickTimer = setTimeout(() => {
if (!this.#preventRightClick) {
this.setState("IDLE");
getUnitsManager().deselectAllUnits();
this.hideSelectionWheel();
this.hideSelectionScroll();
}
this.#preventRightClick = false;
}, 200);
}
#onSelectionEnd(e: any)
{
clearTimeout(this.#rightClickTimer);
this.#preventRightClick = true;
getUnitsManager().selectFromBounds(e.selectionBounds);
}
/* Spawn from air base */
spawnFromAirbase(e: SpawnEvent)
{
this.#aircraftSpawnMenu(e);
}
/* Spawning menus */
#groundUnitSpawnMenu(e: SpawnEvent) {
var options = [
{'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.showSelectionScroll(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.showSelectionScroll(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.showSelectionScroll(e, options, () => {}, true);
}
/* Show unit selection for air units */
#selectAircraft(e: SpawnEvent, group: string) {
this.hideSelectionWheel();
this.hideSelectionScroll();
var options = unitTypes.air[group];
if (options != undefined)
options.sort();
else
options = [];
this.showSelectionScroll(e, options, (unitType: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
this.#unitSelectPayload(e, unitType);
});
}
/* Show weapon selection for air units */
#unitSelectPayload(e: SpawnEvent, unitType: string) {
this.hideSelectionWheel();
this.hideSelectionScroll();
var options = [];
options = payloadNames[unitType]
if (options != undefined && options.length > 0) {
options.sort();
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 {
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,37 +1,40 @@
export function distance(lat1, lon1, lat2, lon2)
{
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, lon1, lat2, lon2)
{
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 = (num, places) => String(num).padStart(places, '0')
const zeroPad = function (num: number, places: number) {
var string = String(num);
while (string.length < places) {
string += "0";
}
return string;
}
export function ConvertDDToDMS(D, lng)
{
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);
@ -44,14 +47,12 @@ export function ConvertDDToDMS(D, lng)
return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
}
export function deg2rad(deg)
{
export function deg2rad(deg: number) {
var pi = Math.PI;
return deg * (pi/180);
return deg * (pi / 180);
}
export function rad2deg(rad)
{
export function rad2deg(rad: number) {
var pi = Math.PI;
return rad / (pi/180);
return rad / (pi / 180);
}

View File

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

View File

@ -0,0 +1,53 @@
import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { Unit } from "../units/unit";
export class UnitInfoPanel {
#element: HTMLElement
#display: string;
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
this.#display = '';
if (this.#element != null) {
this.#display = this.#element.style.display;
this.hide();
}
}
show() {
this.#element.style.display = this.#display;
}
hide() {
this.#element.style.display = "none";
}
update(unit: Unit) {
if (this.#element != null) {
var els = this.#element.getElementsByClassName("js-loadout-element");
while (els.length > 0)
this.#element.querySelector("#loadout-data")?.removeChild(els[0]);
for (let index in unit.ammo) {
var ammo = unit.ammo[index];
var displayName = ammo.desc.displayName;
var amount = ammo.count;
var el = document.createElement("div")
el.classList.add("js-loadout-element", "rectangular-container-dark")
el.innerHTML = amount + "x" + displayName;
this.#element.querySelector("#loadout-data")?.appendChild(el);
}
this.#element.querySelector("#unit-name")!.innerHTML = unit.unitName;
this.#element.querySelector("#group-name")!.innerHTML = unit.groupName;
this.#element.querySelector("#name")!.innerHTML = unit.name;
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("#ground-speed")!.innerHTML = String(Math.floor(unit.speed * 1.94384) + " kts");
this.#element.querySelector("#fuel")!.innerHTML = String(unit.fuel + "%");
this.#element.querySelector("#latitude")!.innerHTML = ConvertDDToDMS(unit.latitude, false);
this.#element.querySelector("#longitude")!.innerHTML = ConvertDDToDMS(unit.longitude, true);
this.#element.querySelector("#task")!.innerHTML = unit.currentTask !== ""? unit.currentTask: "Not controlled";
}
}
}

File diff suppressed because one or more lines are too long

442
client/src/units/unit.ts Normal file
View File

@ -0,0 +1,442 @@
import { Marker, LatLng, Polyline, Icon } from 'leaflet';
import { ConvertDDToDMS } from '../other/utils';
import { getMap, getUnitsManager, getVisibilitySettings } from '..';
import { UnitMarker, MarkerOptions, AircraftMarker, HelicopterMarker, GroundUnitMarker, NavyUnitMarker, WeaponMarker } from './unitmarker';
import { addDestination, attackUnit, changeAltitude, changeSpeed } from '../dcs/dcs';
var pathIcon = new Icon({
iconUrl: 'images/marker-icon.png',
shadowUrl: 'images/marker-shadow.png',
iconAnchor: [13, 41]
});
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;
#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;
if (name === "Missile") return Missile;
if (name === "Bomb") return Bomb;
if (name === "NavyUnit") return NavyUnit;
}
constructor(ID: number, marker: UnitMarker) {
this.ID = ID;
this.#selectable = true;
/* 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.#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) {
// @ts-ignore
this[entry] = response[entry];
}
/* Dead units can't be selected */
this.setSelected(this.getSelected() && this.alive)
this.#updateMarker();
this.#clearTargets();
if (this.getSelected())
{
this.#drawPath();
this.#drawTargets();
}
else
this.#clearPath();
/*
this.wingmen = [];
if (response["wingmenIDs"] != null)
{
for (let ID of response["wingmenIDs"])
{
this.wingmen.push(unitsManager.getUnitByID(ID));
}
}
*/
}
setSelected(selected: boolean) {
/* Only alive units can be selected. Some units are not selectable (weapons) */
if ((this.alive || !selected) && this.#selectable && this.#selected != selected) {
this.#selected = selected;
this.#marker.setSelected(selected);
getUnitsManager().onUnitSelection();
}
}
getSelected() {
return this.#selected;
}
setSelectable(selectable: boolean) {
this.#selectable = selectable;
}
getSelectable() {
return this.#selectable;
}
addDestination(latlng: L.LatLng) {
var path: any = {};
if (this.activePath != null) {
path = this.activePath;
path[(Object.keys(path).length + 1).toString()] = latlng;
}
else {
path = { "1": latlng };
}
addDestination(this.ID, path);
}
clearDestinations() {
this.activePath = null;
}
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) {
getUnitsManager().deselectAllUnits();
}
this.setSelected(true);
}
}
this.#preventClick = false;
}, 200);
}
#onDoubleClick(e: any) {
clearTimeout(this.#timer);
this.#preventClick = true;
var options = [
'Attack',
]
//if (!this.leader && !this.wingman) {
// options.push({ 'tooltip': 'Create formation', 'src': 'formation.png', 'callback': () => { getMap().hideSelectionWheel(); /*unitsManager.createFormation(this.ID);*/ } });
//}
getMap().showSelectionScroll(e.originalEvent, options, (action: string) => this.#executeAction(action));
}
#executeAction(action: string) {
getMap().hideSelectionScroll();
if (action === "Attack")
getUnitsManager().attackUnit(this.ID);
}
#updateMarker() {
/* Add the marker if not present */
if (!getMap().hasLayer(this.#marker) && !this.getHidden()) {
this.#marker.addTo(getMap());
}
/* 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) {
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], { icon: pathIcon }).addTo(getMap());
this.#pathMarkers.push(marker);
}
/* Remove markers if too many */
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) {
var destination = this.activePath[WP];
this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]);
_points.push(new LatLng(destination.lat, destination.lng));
this.#pathPolyline.setLatLngs(_points);
}
}
}
#clearPath() {
for (let WP in this.#pathMarkers) {
getMap().removeLayer(this.#pathMarkers[WP]);
}
this.#pathMarkers = [];
this.#pathPolyline.setLatLngs([]);
}
#drawTargets()
{
for (let typeIndex in this.targets)
{
for (let index in this.targets[typeIndex])
{
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)
var color;
if (typeIndex === "radar")
{
color = "#FFFF00";
}
else if (typeIndex === "visual")
{
color = "#FF00FF";
}
else if (typeIndex === "rwr")
{
color = "#00FF00";
}
else
{
color = "#FFFFFF";
}
var targetPolyline = new Polyline([startLatLng, endLatLng], {color: color, weight: 3, opacity: 1, smoothFactor: 1});
targetPolyline.addTo(getMap());
this.#targetsPolylines.push(targetPolyline)
}
}
}
}
#clearTargets()
{
for (let index in this.#targetsPolylines)
{
getMap().removeLayer(this.#targetsPolylines[index])
}
}
attackUnit(targetID: number) {
/* Call DCS attackUnit function */
if (this.ID != targetID) {
attackUnit(this.ID, targetID);
}
else {
// TODO: show a message
}
}
changeSpeed(speedChange: string)
{
changeSpeed(this.ID, speedChange);
}
changeAltitude(altitudeChange: string)
{
changeAltitude(this.ID, altitudeChange);
}
/*
setformation(formation)
{
// 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 + " formation change: " + formation);
}
};
var command = {"ID": this.ID, "formation": formation}
var data = {"setFormation": command}
xhr.send(JSON.stringify(data));
}
setLeader(wingmenIDs)
{
// 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 + " created formation with: " + wingmenIDs);
}
};
var command = {"ID": this.ID, "wingmenIDs": wingmenIDs}
var data = {"setLeader": command}
xhr.send(JSON.stringify(data));
}
*/
}
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 AircraftMarker(options);
super(ID, marker);
}
}
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 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 {
constructor(ID: number, marker: UnitMarker)
{
super(ID, marker);
this.setSelectable(false);
}
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 Bomb extends Weapon {
constructor(ID: number, options: MarkerOptions) {
var marker = new WeaponMarker(options);
super(ID, marker);
}
}

View File

@ -1,6 +1,6 @@
var unitTypes: any = {};
export var unitTypes: any = {};
/* NAVY */
unitTypes.navy = {};
unitTypes.navy.blue = [
@ -250,7 +250,7 @@ unitTypes.air.strike = [
"Tu-142",
]
unitTypes.air.tank = [
unitTypes.air.tanker = [
"S-3B Tanker",
"KC-135",
"IL-78M",

View File

@ -0,0 +1,276 @@
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 MarkerData {
heading: number
speed: number
altitude: 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;
var symbol = new Symbol(this.#computeMarkerCode(options), { size: 25 });
var img = symbol.asCanvas().toDataURL('image/png');
var coalition = "";
if (options.coalitionID == 1)
coalition = "red"
else if (options.coalitionID == 2)
coalition = "blue"
else
coalition = "neutral"
var icon = new L.DivIcon({
html: `<table class="unit-marker-container" id="container">
<tr>
<td>
<div class="${coalition}" id="background"></div>
<div class="${coalition}" id="ring"></div>
<div class="unit-marker-icon" id="icon"><img src="${img}"></div>
<div class="unit-marker-unitName" id="unitName">${this.#unitName}</div>
<div class="unit-marker-altitude" id="altitude"></div>
<div class="unit-marker-speed" id="speed"></div>
<div class="unit-marker-name" id="name">${this.#name}</div>
</td>
</tr>
</table>`,
className: 'unit-marker'
});
this.setIcon(icon);
}
onAdd(map: L.Map): this {
super.onAdd(map);
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) {
this.#alive = data.alive;
var element = this.getElement();
if (element != null) {
var nameDiv = <HTMLElement>element.querySelector("#name");
var unitNameDiv = <HTMLElement>element.querySelector("#unitName");
var container = <HTMLElement>element.querySelector("#container");
var icon = <HTMLElement>element.querySelector("#icon");
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) {
this.#selected = selected;
this.getElement()?.querySelector("#icon")?.classList.remove("unit-marker-hovered");
this.getElement()?.querySelector("#ring")?.classList.toggle("unit-marker-selected", selected);
this.getElement()?.querySelector("#background")?.classList.toggle("unit-marker-selected", selected);
}
getSelected() {
return this.#selected;
}
setHovered(hovered: boolean) {
this.getElement()?.querySelector("#icon")?.classList.toggle("unit-marker-hovered", hovered && this.#alive);
}
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)
identity = "06" /* Hostile */
else if (options.coalitionID == 2)
identity = "03" /* Friendly */
else
identity = "04" /* Neutral */
/* Air */
if (options.type.level1 == 1) {
set = "01"
entity = "11"
if (options.type.level2 == 1)
entityType = "01"
else if (options.type.level2 == 1)
entityType = "02"
if (options.type.level3 == 1)
entitySubtype = "04";
else if (options.type.level3 == 2)
entitySubtype = "05";
else if (options.type.level3 == 3)
entitySubtype = "04";
else if (options.type.level3 == 4)
entitySubtype = "02";
else if (options.type.level3 == 5)
entitySubtype = "00";
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"
/* Weapon */
else if (options.type.level1 == 4)
{
set = "02"
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

@ -0,0 +1,231 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getMap, getUnitInfoPanel } from "..";
import { Unit, GroundUnit } from "./unit";
export class UnitsManager {
#units: { [ID: number]: Unit };
#copiedUnits: Unit[];
constructor() {
this.#units = {};
this.#copiedUnits = [];
}
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 options = {
unitName: data.unitName,
name: data.name,
human: data.human,
coalitionID: data.coalitionID,
type: data.type
}
this.#units[ID] = new constructor(ID, options);
}
}
getUnitByID(ID: number) {
return this.#units[ID];
}
removeUnit(ID: number) {
}
deselectAllUnits() {
for (let ID in this.#units) {
this.#units[ID].setSelected(false);
}
}
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)) {
this.addUnit(parseInt(ID), data["units"][ID]);
}
this.#units[parseInt(ID)].update(data["units"][ID]);
}
if (this.getSelectedUnits().length == 1) {
getUnitInfoPanel().show();
getUnitInfoPanel().update(this.getSelectedUnits()[0]);
}
else {
getUnitInfoPanel().hide();
}
}
onUnitSelection() {
if (this.getSelectedUnits().length > 0) {
getMap().setState("MOVE_UNIT");
//unitControlPanel.setEnabled(true);
}
else {
getMap().setState("IDLE");
//unitControlPanel.setEnabled(false);
}
}
selectFromBounds(bounds: LatLngBounds)
{
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() {
var selectedUnits = [];
for (let ID in this.#units) {
if (this.#units[ID].getSelected()) {
selectedUnits.push(this.#units[ID]);
}
}
return selectedUnits;
}
addDestination(latlng: L.LatLng) {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
//if (selectedUnits[idx].wingman)
//{
// commandedUnit = this.getLeader(selectedUnits[idx].ID);
//}
commandedUnit.addDestination(latlng);
}
}
clearDestinations() {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
//if (selectedUnits[idx].wingman)
//{
// commandedUnit = this.getLeader(selectedUnits[idx].ID);
//}
commandedUnit.clearDestinations();
}
}
selectedUnitsChangeSpeed(speedChange: string)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].changeSpeed(speedChange);
}
}
selectedUnitsChangeAltitude(altitudeChange: string)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].changeAltitude(altitudeChange);
}
}
// handleKeyEvent(e)
// {
// if (e.originalEvent.code === 'KeyC' && e.originalEvent.ctrlKey)
// {
// this.copyUnits();
// }
// else if (e.originalEvent.code === 'KeyV' && e.originalEvent.ctrlKey)
// {
// this.pasteUnits();
// }
// }
// copyUnits()
// {
// this.#copiedUnits = this.getSelectedUnits();
// }
// pasteUnits()
// {
// for (let idx in this.#copiedUnits)
// {
// var unit = this.#copiedUnits[idx];
// cloneUnit(unit.ID);
// }
// }
attackUnit(ID: number) {
var selectedUnits = this.getSelectedUnits();
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)
//{
// commandedUnit = this.getLeader(selectedUnits[idx].ID);
//}
commandedUnit.attackUnit(ID);
}
}
// createFormation(ID)
// {
// var selectedUnits = this.getSelectedUnits();
// var wingmenIDs = [];
// for (let idx in selectedUnits)
// {
// if (selectedUnits[idx].wingman)
// {
// showMessage(selectedUnits[idx].unitName + " is already in a formation.");
// return;
// }
// else if (selectedUnits[idx].leader)
// {
// showMessage(selectedUnits[idx].unitName + " is already in a formation.");
// return;
// }
// else
// {
// /* TODO
// if (selectedUnits[idx].category !== this.getUnitByID(ID).category)
// {
// showMessage("All units must be of the same category to create a formation.");
// }
// */
// if (selectedUnits[idx].ID != ID)
// {
// wingmenIDs.push(selectedUnits[idx].ID);
// }
// }
// }
// if (wingmenIDs.length > 0)
// {
// this.getUnitByID(ID).setLeader(wingmenIDs);
// }
// else
// {
// showMessage("At least 2 units must be selected to create a formation.");
// }
// }
// getLeader(ID)
// {
// for (let idx in this.#units)
// {
// var unit = this.#units[idx];
// if (unit.leader)
// {
// if (unit.wingmen.includes(this.getUnitByID(ID)))
// {
// return unit;
// }
// }
// }
// showMessage("Error: no leader found for this unit")
// }
}

103
client/tsconfig.json Normal file
View File

@ -0,0 +1,103 @@
{
"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. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "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. */
// "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. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "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. */
// "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. */
// "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. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "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. */
// "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. */
/* Type Checking */
"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. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "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. */
},
"include": [
"src/**/*.ts"
]
}

Some files were not shown because too many files have changed in this diff Show More