70 Commits

Author SHA1 Message Date
Pax1601
3fac8a5663 Fixed missing isAlive value 2023-07-26 10:58:39 +02:00
Pax1601
3c11e1c2a1 Front end tweaks 2023-07-25 17:54:26 +02:00
Pax1601
81871b596b Completed backend for advanced RTS functions 2023-07-23 22:30:25 +02:00
Pax1601
8ffd5ef972 Started to add advanced RTS options to backend 2023-07-21 19:51:23 +02:00
Pax1601
4ae72b7c0b Front end tweaks
Added visibility options and server log panel
2023-07-21 17:33:24 +02:00
Pax1601
5613394a2c Completed RTS front end 2023-07-20 17:49:41 +02:00
Pax1601
2e113b468a Merge branch 'performance-optimization' into 326-add-advanced-rts-options 2023-07-20 11:02:26 +02:00
Pax1601
018e37cd2a Minor graphic tweaks 2023-07-20 10:59:48 +02:00
Pax1601
1d5f24dc1a Added check on repeated commands on scheduler 2023-07-19 20:56:19 +02:00
Pax1601
cbb8920de7 Styled the server load panel 2023-07-19 17:41:05 +02:00
Pax1601
7eb6cadc4e Merge remote-tracking branch 'origin/performance-optimization' into 326-add-advanced-rts-options 2023-07-19 17:03:35 +02:00
Pax1601
a4db569fbd Performance optimizations for large unit counts 2023-07-18 21:56:56 +02:00
Pax1601
e9a3ecb9eb Started to implement RTS advanced options 2023-07-13 17:52:05 +02:00
Pax1601
785647ad24 Merge branch 'pr/325' 2023-07-13 11:28:43 +02:00
WoodyXP
72cd4de55f Added the correct name for every naval unit 2023-07-12 19:08:24 +02:00
WoodyXP
e882123692 Removed naval units mods 2023-07-12 17:08:08 +02:00
Pax1601
a949a9bf22 Tweaks and implemented workaround to MIST bug 2023-07-12 17:01:03 +02:00
WoodyXP
75c897eb5b Change function to filter by era due to change of value type 2023-07-12 16:41:55 +02:00
WoodyXP
b48f74f879 Added coalition to naval units 2023-07-12 16:31:46 +02:00
WoodyXP
c6eeeac7c1 Added coaltion to helicopter units 2023-07-12 15:50:14 +02:00
WoodyXP
9c1edd2a86 Added coalition to groundunits and fixed some eras and reformatted aircraft db 2023-07-12 15:43:13 +02:00
WoodyXP
133c447d5d Added coalition to aircraftdatabase.ts and switched era list to string 2023-07-12 14:57:15 +02:00
Pax1601
b78cd27e4e Merge pull request #323 from Pax1601/319-add-basic-rts-functions
319 add basic rts functions
2023-07-11 18:56:57 +02:00
Pax1601
5d29c82244 Commented not implemented functions 2023-07-11 18:56:37 +02:00
Pax1601
f379f6b998 Tweaks to IADS creation and menus 2023-07-11 17:30:05 +02:00
Pax1601
439111f7a0 Merge branch 'main' into 319-add-basic-rts-functions 2023-07-11 14:34:59 +02:00
Pax1601
55372d5a5b Merge pull request #322 from WoodyXP/321-Adding-Era-To-Helicopter-DB
Added era to all helicopter units
2023-07-11 14:30:09 +02:00
Pax1601
13e3b93e93 More tweaks on frontend 2023-07-10 16:58:54 +02:00
WoodyXP
7a6929e34b Added era to all helicopter units 2023-07-10 14:07:12 +02:00
Pax1601
a0763a6450 Minor tweaks and fixes 2023-07-09 18:48:21 +02:00
Pax1601
2d4202979f Merge branch '312-add-ability-to-draw-areas-and-fill-with-iads' into 319-add-basic-rts-functions 2023-07-07 17:27:20 +02:00
Pax1601
327d5c74d9 Add basic visibility functions and ability to spawn multiple units in group 2023-07-07 17:26:41 +02:00
Pax1601
3bad83a02f Merge pull request #316 from WoodyXP/313-fix-getByRange-function
Changed the function that it gets all the units with a specified range
2023-07-06 13:10:17 +02:00
Pax1601
eee97294df Merge branch 'main' into 313-fix-getByRange-function 2023-07-06 13:10:10 +02:00
Pax1601
56427d84ad Merge pull request #317 from WoodyXP/131-add-navalunitdb
131 add navalunitdb
2023-07-06 13:08:48 +02:00
Pax1601
fc1983d554 Merge pull request #318 from Pax1601/312-add-ability-to-draw-areas-and-fill-with-iads
312 add ability to draw areas and fill with iads
2023-07-06 13:00:14 +02:00
Pax1601
30568e54f7 Converted demo data creation to binary data 2023-07-06 12:59:08 +02:00
Pax1601
1af98cc54f Tweaks and optimizations of front end 2023-07-05 20:14:48 +02:00
Stefan Arsic
fb3a628ad0 Update unitPayloads.lua 2023-07-05 18:55:47 +02:00
Stefan Arsic
79b0eeae9e Update unitPayloads.lua 2023-07-05 18:49:53 +02:00
WoodyXP
3b2757a022 Added all ship to the naval database and made a function to filter by ship type 2023-07-05 18:47:02 +02:00
WoodyXP
fa9f1e05ea Updated the navalunitdb and added new type range into unit types 2023-07-04 17:36:23 +02:00
WoodyXP
fff0c8244e Merge branch 'main' into 131-add-navalunitdb 2023-07-04 16:32:54 +02:00
Stefan Arsic
5777632114 Create navalunitdatabase.ts 2023-07-04 14:04:08 +02:00
WoodyXP
d013337c5e Changed the function that it gets all the units with a specified range 2023-07-03 19:06:40 +02:00
Pax1601
ec91376da2 More fixes in binary data transmission 2023-07-02 22:13:53 +02:00
Pax1601
807cdfeb5b Added code to update ammo and contacts 2023-07-01 12:31:30 +02:00
Pax1601
9f6da4845a Merge pull request #315 from WoodyXP/313-add-sam-information-V2
Added Era and Range to Sams and Ground Units V2
2023-06-29 17:16:45 +02:00
Pax1601
211cf48681 More fixes on binary data transmission 2023-06-29 08:10:14 +02:00
Pax1601
4d9dd364b6 Converted data transfer to binary key-value method 2023-06-26 18:53:04 +02:00
Stefan Arsic
52dfcebc14 Update groundunitsdatabase.ts 2023-06-26 14:14:53 +02:00
Stefan Arsic
2bce51fca6 Update groundunitsdatabase.ts 2023-06-26 14:14:21 +02:00
Stefan Arsic
3a2048f3ec Updated Era 2023-06-26 14:12:57 +02:00
Stefan Arsic
63e602afb1 Fixed some more era types 2023-06-26 09:24:02 +02:00
Stefan Arsic
7a71065b76 Fixed Typo and Era type 2023-06-26 09:21:11 +02:00
Pax1601
1989219579 Fixed update error and added update trigger 2023-06-25 19:04:12 +02:00
Pax1601
dd2e858db4 More work on client conversion to binary data 2023-06-23 17:32:38 +02:00
Pax1601
916752301a Started transition to binary data on client side 2023-06-22 22:17:00 +02:00
Pax1601
1d62b4c115 Transition to json to binary data transfers 2023-06-22 17:28:40 +02:00
WoodyXP
258f1136e2 Added Era and Range to Sams and Ground Units V2 2023-06-22 16:10:48 +02:00
Pax1601
9d0e2239e4 Added new method for handling data 2023-06-21 20:05:41 +02:00
WoodyXP
5555c44c4e Merging pax main 2023-06-21 18:26:12 +02:00
WoodyXP
f80a91ca1e Fixing Merge conflict 2023-06-21 18:20:01 +02:00
Pax1601
61d6d9f16c Added better temporary markers and more map functions 2023-06-20 17:36:21 +02:00
Pax1601
635c487c2b Added area editing functions 2023-06-19 17:14:53 +02:00
Pax1601
f9f02c3eb0 Added more functions to edit Coalition Areas 2023-06-18 19:55:01 +02:00
Pax1601
ad3b1cb167 Added basic ability to draw poligons 2023-06-16 17:32:09 +02:00
WoodyXP
880ce3919d Added emtpy Loadouts for all aircraft 2023-02-14 15:27:35 +01:00
WoodyXP
b489659ddd Merge branch 'main' of https://github.com/WoodyXP/DCSOlympus 2023-02-14 01:39:40 +01:00
WoodyXP
f8344b1662 Added CLSID to payloadNames.js, Updated the payLoadConverter.py so the CLSID can be added, Added 1 custom Loadout in unitPayloads.lue 2023-02-14 01:36:36 +01:00
143 changed files with 37272 additions and 24241 deletions

View File

@@ -3,7 +3,6 @@ var path = require('path');
var cookieParser = require('cookie-parser'); var cookieParser = require('cookie-parser');
var logger = require('morgan'); var logger = require('morgan');
var fs = require('fs'); var fs = require('fs');
var basicAuth = require('express-basic-auth')
var atcRouter = require('./routes/api/atc'); var atcRouter = require('./routes/api/atc');
var airbasesRouter = require('./routes/api/airbases'); var airbasesRouter = require('./routes/api/airbases');
@@ -37,15 +36,7 @@ if (config["server"] != undefined)
module.exports = app; module.exports = app;
const DemoDataGenerator = require('./demo.js'); const DemoDataGenerator = require('./demo.js');
var demoDataGenerator = new DemoDataGenerator(10); var demoDataGenerator = new DemoDataGenerator(app);
app.get('/demo/units', (req, res) => demoDataGenerator.units(req, res));
app.get('/demo/logs', (req, res) => demoDataGenerator.logs(req, res));
app.get('/demo/bullseyes', (req, res) => demoDataGenerator.bullseyes(req, res));
app.get('/demo/airbases', (req, res) => demoDataGenerator.airbases(req, res));
app.get('/demo/mission', (req, res) => demoDataGenerator.mission(req, res));
app.use('/demo', basicAuth({
users: { 'admin': 'socks' }
}))

View File

@@ -1,2 +1,3 @@
copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css
copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js
copy .\\node_modules\\leaflet-path-drag\\dist\\L.Path.Drag.js .\\public\\javascripts\\L.Path.Drag.js

File diff suppressed because it is too large Load Diff

3377
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "DCSOlympus", "name": "DCSOlympus",
"node-main": "./bin/www", "node-main": "./bin/www",
"main": "http://localhost:3000", "main": "http://localhost:3000",
"version": "v0.3.0-alpha", "version": "v0.4.0-alpha",
"private": true, "private": true,
"scripts": { "scripts": {
"copy": "copy.bat", "copy": "copy.bat",
@@ -10,6 +10,7 @@
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]" "watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]"
}, },
"dependencies": { "dependencies": {
"@turf/turf": "^6.5.0",
"@types/formatcoords": "^1.1.0", "@types/formatcoords": "^1.1.0",
"@types/geojson": "^7946.0.10", "@types/geojson": "^7946.0.10",
"@types/leaflet": "^1.9.0", "@types/leaflet": "^1.9.0",
@@ -21,6 +22,7 @@
"formatcoords": "^1.1.3", "formatcoords": "^1.1.3",
"leaflet": "^1.9.3", "leaflet": "^1.9.3",
"leaflet-control-mini-map": "^0.4.0", "leaflet-control-mini-map": "^0.4.0",
"leaflet-path-drag": "*",
"leaflet.nauticscale": "^1.1.0", "leaflet.nauticscale": "^1.1.0",
"morgan": "~1.9.1", "morgan": "~1.9.1",
"save": "^2.9.0" "save": "^2.9.0"

File diff suppressed because one or more lines are too long

View File

@@ -5,13 +5,24 @@
width: 100%; width: 100%;
} }
#primary-toolbar { #toolbar-container {
align-items: center; align-items: center;
display: flex; display: flex;
left: 10px; left: 10px;
position: absolute; position: absolute;
top: 10px; top: 10px;
z-index: 9999; z-index: 9999;
column-gap: 10px;
}
#primary-toolbar {
align-items: center;
display: flex;
}
#command-mode-toolbar {
align-items: center;
display: flex;
} }
#app-icon>.ol-select-options { #app-icon>.ol-select-options {
@@ -42,6 +53,15 @@
z-index: 9999; z-index: 9999;
} }
#server-status-panel {
bottom: 20px;
font-size: 12px;
position: absolute;
right: 200px;
width: 300px;
z-index: 9999;
}
#mouse-info-panel { #mouse-info-panel {
bottom: 60px; bottom: 60px;
display: flex; display: flex;
@@ -81,4 +101,17 @@
left: 50%; left: 50%;
translate: -50% 0%; translate: -50% 0%;
z-index: 9999; z-index: 9999;
display: flex;
align-items: center;
}
#log-panel {
position: absolute;
right: 0px;
top: 220px;
width: 310px;
height: fit-content;
z-index: 9990;
overflow: hidden;
padding: 10px;
} }

View File

@@ -14,11 +14,11 @@
background: var(--secondary-gunmetal-grey); background: var(--secondary-gunmetal-grey);
display: flex; display: flex;
justify-self: center; justify-self: center;
padding-bottom: calc((var(--unit-aircraft-width) / 2) + var(--unit-stroke-width)); padding-bottom: calc((var(--unit-width) / 2) + var(--unit-stroke-width));
position: absolute; position: absolute;
transform-origin: bottom; transform-origin: bottom;
translate: 0 -50%; translate: 0 -50%;
width: var(--unit-aircraft-vvi-width); width: var(--unit-vvi-width);
} }
.unit-hotgroup { .unit-hotgroup {
@@ -100,13 +100,13 @@
/*** Fuel indicator ***/ /*** Fuel indicator ***/
[data-object|="unit"] .unit-fuel { [data-object|="unit"] .unit-fuel {
background: white; background: white;
border: var(--unit-aircraft-fuel-border-width) solid var(--secondary-dark-steel); border: var(--unit-fuel-border-width) solid var(--secondary-dark-steel);
border-radius: var(--border-radius-sm); border-radius: var(--border-radius-sm);
display: none; display: none;
height: var(--unit-aircraft-fuel-height); height: var(--unit-fuel-height);
position: absolute; position: absolute;
translate: var(--unit-aircraft-fuel-x) var(--unit-aircraft-fuel-y); translate: var(--unit-fuel-x) var(--unit-fuel-y);
width: var(--unit-aircraft-fuel-width); width: var(--unit-fuel-width);
} }
[data-object|="unit"] .unit-fuel-level { [data-object|="unit"] .unit-fuel-level {
@@ -117,19 +117,19 @@
/*** Ammo indicator ***/ /*** Ammo indicator ***/
[data-object|="unit"] .unit-ammo { [data-object|="unit"] .unit-ammo {
column-gap: var(--unit-aircraft-ammo-spacing); column-gap: var(--unit-ammo-spacing);
display: none; display: none;
height: fit-content; height: fit-content;
position: absolute; position: absolute;
translate: var(--unit-aircraft-ammo-x) var(--unit-aircraft-ammo-y); translate: var(--unit-ammo-x) var(--unit-ammo-y);
width: fit-content; width: fit-content;
} }
[data-object|="unit"] .unit-ammo>* { [data-object|="unit"] .unit-ammo>* {
background-color: white; background-color: white;
border: var(--unit-aircraft-ammo-border-width) solid var(--secondary-dark-steel); border: var(--unit-ammo-border-width) solid var(--secondary-dark-steel);
border-radius: 50%; border-radius: 50%;
padding: var(--unit-aircraft-ammo-radius); padding: var(--unit-ammo-radius);
} }
/*** Unit summary ***/ /*** Unit summary ***/
@@ -150,7 +150,7 @@
1px -1px 0 #000, 1px -1px 0 #000,
-1px 1px 0 #000, -1px 1px 0 #000,
1px 1px 0 #000; 1px 1px 0 #000;
translate: -60px 0; right: 100%;
width: fit-content; width: fit-content;
} }
@@ -171,7 +171,7 @@
width: 80px; width: 80px;
} }
[data-object|="unit"] .unit-summary .unit-callsign:hover { [data-object|="unit"]:hover .unit-summary .unit-callsign{
direction: rtl; direction: rtl;
overflow: visible; overflow: visible;
} }
@@ -292,23 +292,27 @@
} }
/*** Dead unit ***/ /*** Dead unit ***/
[data-object|="unit-aircraft"][data-is-dead] .unit-selected-spotlight, [data-object|="unit"][data-is-dead] .unit-selected-spotlight,
[data-object|="unit-aircraft"][data-is-dead] .unit-short-label, [data-object|="unit"][data-is-dead] .unit-short-label,
[data-object|="unit-aircraft"][data-is-dead] .unit-vvi, [data-object|="unit"][data-is-dead] .unit-vvi,
[data-object|="unit-aircraft"][data-is-dead] .unit-hotgroup, [data-object|="unit"][data-is-dead] .unit-hotgroup,
[data-object|="unit-aircraft"][data-is-dead] .unit-hotgroup-id, [data-object|="unit"][data-is-dead] .unit-hotgroup-id,
[data-object|="unit-aircraft"][data-is-dead] .unit-state, [data-object|="unit"][data-is-dead] .unit-state,
[data-object|="unit-aircraft"][data-is-dead] .unit-fuel, [data-object|="unit"][data-is-dead] .unit-fuel,
[data-object|="unit-aircraft"][data-is-dead] .unit-ammo, [data-object|="unit"][data-is-dead] .unit-ammo,
[data-object|="unit-aircraft"][data-is-dead]:hover .unit-fuel, [data-object|="unit"][data-is-dead]:hover .unit-fuel,
[data-object|="unit-aircraft"][data-is-dead]:hover .unit-ammo { [data-object|="unit"][data-is-dead]:hover .unit-ammo {
display: none; display: none;
} }
[data-object|="unit-aircraft"][data-is-dead] .unit-summary>* { [data-object|="unit"][data-is-dead] .unit-summary>* {
display: none; display: none;
} }
[data-object|="unit-aircraft"][data-is-dead] .unit-summary .unit-callsign { [data-object|="unit"][data-is-dead] .unit-summary .unit-callsign {
display: block; display: block;
} }
.ol-temporary-marker {
opacity: 0.5;
}

View File

@@ -3,9 +3,11 @@
@import url("atc/unitdatatable.css"); @import url("atc/unitdatatable.css");
@import url("aic/aic.css"); @import url("aic/aic.css");
@import url("panels/connectionstatus.css"); @import url("panels/connectionstatus.css");
@import url("panels/serverstatus.css");
@import url("panels/mouseinfo.css"); @import url("panels/mouseinfo.css");
@import url("panels/unitcontrol.css"); @import url("panels/unitcontrol.css");
@import url("panels/unitinfo.css"); @import url("panels/unitinfo.css");
@import url("panels/logpanel.css");
@import url("other/contextmenus.css"); @import url("other/contextmenus.css");
@import url("other/popup.css"); @import url("other/popup.css");
@import url("markers/airbase.css"); @import url("markers/airbase.css");
@@ -20,6 +22,7 @@
html * { html * {
font-family: 'Open Sans', sans-serif !important; font-family: 'Open Sans', sans-serif !important;
user-select: none;
} }
body { body {
@@ -38,6 +41,11 @@ body {
cursor: none !important; cursor: none !important;
} }
.hidden-cursor * {
cursor: none !important;
pointer-events: none !important;
}
a { a {
text-decoration: none; text-decoration: none;
} }
@@ -124,7 +132,7 @@ form>div {
.ol-panel { .ol-panel {
background-color: var(--background-steel); background-color: var(--background-steel);
border-radius: 15px; border-radius: var(--border-radius-md);;
box-shadow: 0px 2px 5px #000A; box-shadow: 0px 2px 5px #000A;
color: white; color: white;
font-size: 12px; font-size: 12px;
@@ -408,7 +416,6 @@ nav.ol-panel> :last-child {
.ol-panel .ol-group-button-toggle { .ol-panel .ol-group-button-toggle {
align-items: center; align-items: center;
column-gap: 15px;
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
white-space: nowrap; white-space: nowrap;
@@ -421,7 +428,7 @@ nav.ol-panel> :last-child {
border: 0; border: 0;
display: flex; display: flex;
justify-items: left; justify-items: left;
text-indent: 5px; text-indent: 2px;
} }
.ol-panel .ol-group-button-toggle button::before { .ol-panel .ol-group-button-toggle button::before {
@@ -624,39 +631,39 @@ nav.ol-panel> :last-child {
width: 28px; width: 28px;
} }
#unit-visibility-control { .ol-navbar-buttons-group {
align-items: center; align-items: center;
} }
#unit-visibility-control button { .ol-navbar-buttons-group button {
border: none; border: none;
height: 32px; height: 32px;
padding: 0px; padding: 0px;
width: 32px; width: 32px;
} }
#unit-visibility-control button svg { .ol-navbar-buttons-group button svg {
height: 16px; height: 16px;
pointer-events: none; pointer-events: none;
width: 16px; width: 16px;
} }
#unit-visibility-control button { .ol-navbar-buttons-group button {
background-color: white; background-color: white;
border: 1px solid transparent; border: 1px solid transparent;
} }
#unit-visibility-control button.off { .ol-navbar-buttons-group button.off {
background-color: transparent; background-color: transparent;
border: 1px solid white; border: 1px solid white;
} }
#unit-visibility-control button.off svg * { .ol-navbar-buttons-group button.off svg * {
fill: white !important; fill: white !important;
stroke: white !important; stroke: white !important;
} }
#unit-visibility-control button svg * { .ol-navbar-buttons-group button svg * {
fill: var(--background-steel) !important; fill: var(--background-steel) !important;
stroke: var(--background-steel) !important; stroke: var(--background-steel) !important;
} }
@@ -667,10 +674,9 @@ nav.ol-panel> :last-child {
flex-direction: column; flex-direction: column;
} }
#atc-navbar-control button { #atc-navbar-control button svg {
background: #ffffff20; height: 24px;
border-radius: var(--border-radius-sm); width: 24px;
padding: 4px;
} }
#roe-buttons-container button, #roe-buttons-container button,
@@ -699,7 +705,7 @@ nav.ol-panel> :last-child {
background-image: url("/resources/theme/images/splash/1.png"); background-image: url("/resources/theme/images/splash/1.png");
background-position: 100% 50%; background-position: 100% 50%;
background-size: 60%; background-size: 60%;
border-radius: var(--border-radius-lg); border-radius: var(--border-radius-md);
overflow: hidden; overflow: hidden;
width: 1200px; width: 1200px;
z-index: 99999; z-index: 99999;
@@ -809,16 +815,16 @@ nav.ol-panel> :last-child {
font-weight: bold; font-weight: bold;
} }
#connection-status { #login-status {
margin-bottom: 5px; margin-bottom: 5px;
} }
#connection-status[data-status="connecting"]::before { #login-status[data-status="connecting"]::before {
animation: blinker 1s linear infinite; animation: blinker 1s linear infinite;
content: "Connecting..."; content: "Connecting...";
} }
#connection-status[data-status="failed"]::before { #login-status[data-status="failed"]::before {
color: var(--primary-red); color: var(--primary-red);
content: "Incorrect username/password!"; content: "Incorrect username/password!";
} }
@@ -862,6 +868,130 @@ nav.ol-panel> :last-child {
translate: 0% -300%; translate: 0% -300%;
} }
#command-mode {
display: flex;
font-size: 14px;
font-weight: bolder;
padding-left: 10px;
margin-left: -11px;
margin-top: -0px;
margin-bottom: -0px;
height: 58px;
padding: 10px;
border-top-left-radius: var(--border-radius-md);
border-bottom-left-radius: var(--border-radius-md);
align-items: center;
}
#command-mode[data-mode="Game master"] {
background-color: lightgray;
color: var(--secondary-gunmetal-grey);
}
#command-mode[data-mode="Blue commander"] {
background-color: var(--primary-blue);
}
#command-mode[data-mode="Red commander"] {
background-color: var(--primary-red);
}
#spawn-points-container {
font-size: 14px;
}
#spawn-points {
background-color: var(--background-grey);
padding: 5px 15px;
margin-left: 15px;
border: 1px white solid;
font-size: 14px;
border-radius: var(--border-radius-md);
}
#spawn-points-container {
height: 100%;
border-right: 1px solid gray;
display: flex;
align-items: center;
padding-right: 20px;
}
#command-mode-phase::before {
content: "Time to start";
font-size: 14px;
}
#command-mode-phase.setup-phase::after {
color: orange;
border: 1px solid orange;
border-radius: 999px;
padding: 5px 10px;
background-color: var(--background-grey);
margin-left: 15px;
content: attr(data-remaining-time);
font-size: 14px;
}
#command-mode-phase.game-commenced {
background-color: var(--background-grey);
color: lightgreen;
border: 1px solid lightgreen;
padding: 5px 15px;
border-radius: var(--border-radius-md);
display: flex;
flex-direction: column;
align-items: center;
}
#command-mode-phase.game-commenced::before {
content: "Game commenced";
font-weight: bold;
}
#command-mode-phase.game-commenced::after {
content: "Spawn restrictions are being enforced";
font-size: 10px;
}
#command-mode-phase.no-restrictions::after {
content: "No spawn restrictions";
font-size: 10px;
}
#command-mode-toolbar .ol-button {
border: 1px solid white;
}
#command-mode-toolbar .ol-button>svg {
width: 20px;
height: 20px;
fill: white;
}
#command-mode-settings-dialog {
width: 400px;
}
#command-mode-settings-dialog>.ol-dialog-content {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
margin-bottom: 10px;
margin-top: 10px;
row-gap: 10px;
width: 100%;
}
#command-mode-settings-dialog>.ol-dialog-content .ol-group {
justify-content: space-between;
}
#command-mode-settings-dialog h4 {
white-space: nowrap;
width: fit-content;
}
.ol-destination-preview-icon { .ol-destination-preview-icon {
background-image: url("/resources/theme/images/markers/move.svg"); background-image: url("/resources/theme/images/markers/move.svg");
height: 52px; height: 52px;
@@ -877,6 +1007,33 @@ nav.ol-panel> :last-child {
z-index: 9999; z-index: 9999;
} }
.ol-draw-icon {
background-image: url("/resources/theme/images/markers/draw.svg");
height: 24px;
pointer-events: none;
width: 24px;
z-index: 9999;
}
.ol-coalitionarea-handle-icon,
.ol-coalitionarea-middle-handle-icon {
pointer-events: none;
z-index: 9999;
border-radius: 999px;
}
.ol-coalitionarea-handle-icon {
background-color: #FFFFFFEE;
width: 24px;
height: 24px;
}
.ol-coalitionarea-middle-handle-icon {
background-color: #FFFFFFAA;
width: 16px;
height: 16px;
}
dl.ol-data-grid { dl.ol-data-grid {
align-items: center; align-items: center;
display: flex; display: flex;
@@ -1135,3 +1292,84 @@ input[type=number]::-webkit-outer-spin-button {
.ol-switch[data-value="undefined"]>.ol-switch-fill::after { .ol-switch[data-value="undefined"]>.ol-switch-fill::after {
transform: translateX(calc((var(--width) - var(--height)) * 0.5)); transform: translateX(calc((var(--width) - var(--height)) * 0.5));
} }
.ol-contexmenu-panel {
padding: 20px;
}
.ol-coalition-switch[data-value="false"]>.ol-switch-fill {
background-color: var(--primary-blue);
}
.ol-coalition-switch[data-value="true"]>.ol-switch-fill {
background-color: var(--primary-red);
}
.ol-coalition-switch[data-value="undefined"]>.ol-switch-fill {
background-color: var(--primary-neutral);
}
.ol-context-menu>ul {
max-height: 200px;
overflow-x: hidden;
overflow-y: auto;
}
.ol-context-menu .ol-panel {
border-radius: var(--border-radius-sm);
width: 100%;
}
.ol-context-menu ul {
margin: 0px;
}
.ol-context-menu .ol-select-container {
align-self: stretch;
flex: 0 0 auto;
width: 100%;
}
.ol-contexmenu-button {
border: none;
border-radius: 0px;
height: 48px;
margin-bottom: -10px;
margin-top: -10px;
width: 48px;
}
.ol-contexmenu-button:last-of-type {
border-bottom-right-radius: var(--border-radius-sm);
border-top-right-radius: var(--border-radius-sm);
}
[data-coalition="blue"].ol-contexmenu-button:hover,
[data-coalition="blue"].ol-contexmenu-button.is-open {
background-color: var(--primary-blue)
}
[data-coalition="red"].ol-contexmenu-button:hover,
[data-coalition="red"].ol-contexmenu-button.is-open {
background-color: var(--primary-red)
}
[data-coalition="neutral"].ol-contexmenu-button:hover,
[data-coalition="neutral"].ol-contexmenu-button.is-open {
background-color: var(--primary-neutral)
}
#map-type svg,
#map-visibility-options svg {
height: 20px;
width: 20px;
fill: lightgray;
}
.ol-log-entry:first-of-type {
border-top: 1px solid #FFFFFF44;
}
.ol-log-entry {
border-bottom: 1px solid #FFFFFF44;
}

View File

@@ -4,15 +4,38 @@
height: fit-content; height: fit-content;
position: absolute; position: absolute;
row-gap: 5px; row-gap: 5px;
width: 280px; width: 300px;
z-index: 9999; z-index: 9999;
} }
#aircraft-spawn-menu { #map-contextmenu>div:nth-child(2) {
height: fit-content; align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-right: 0px;
} }
#ground-unit-spawn-menu { #map-contextmenu>div:nth-child(3) {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-right: 0px;
}
#map-contextmenu>div:nth-child(n+4) {
align-items: center;
display: flex;
flex-direction: column;
justify-content: space-between;
row-gap: 5px;
}
#aircraft-spawn-menu,
#helicopter-spawn-menu,
#groundunit-spawn-menu,
#navyunit-spawn-menu {
height: fit-content; height: fit-content;
} }
@@ -35,154 +58,87 @@
width: 50px; width: 50px;
} }
#coalition-switch[data-value="false"]>.ol-switch-fill { #aircraft-spawn-menu .ol-select.is-open .ol-select-options,
background-color: var(--primary-blue); #helicopter-spawn-menu .ol-select.is-open .ol-select-options {
}
#coalition-switch[data-value="true"]>.ol-switch-fill {
background-color: var(--primary-red);
}
#coalition-switch[data-value="undefined"]>.ol-switch-fill {
background-color: var(--primary-neutral);
}
#map-contextmenu>div:nth-child(2) {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-right: 0px;
}
#map-contextmenu>ul {
max-height: 200px;
overflow-x: hidden;
overflow-y: auto;
}
#map-contextmenu .ol-panel {
border-radius: var(--border-radius-sm);
width: 100%;
}
#map-contextmenu ul {
margin: 0px;
}
#map-contextmenu>div:nth-child(n+3) {
align-items: center;
display: flex;
flex-direction: column;
justify-content: space-between;
row-gap: 5px;
}
#map-contextmenu .ol-select-container {
align-self: stretch;
flex: 0 0 auto;
width: 100%;
}
#aircraft-spawn-menu .ol-select.is-open .ol-select-options {
max-height: 300px; max-height: 300px;
} }
#aircraft-spawn-menu>button, .deploy-unit-button {
#ground-unit-spawn-menu>button { margin-top: 5px;
text-align: center; text-align: center;
width: 100%; width: 100%;
} }
#aircraft-spawn-button { .deploy-unit-button[data-points]:not([data-points='']):not([data-points='0']):not([data-points='Infinity'])::after {
background-image: url("/resources/theme/images/buttons/spawn/aircraft.svg"); content: " (" attr(data-points) " points)";
background-size: 48px;
} }
#ground-unit-spawn-button { .upper-bar svg>* {
background-image: url("/resources/theme/images/buttons/spawn/ground.svg"); fill: white;
background-size: 48px;
} }
#smoke-spawn-button { .upper-bar svg {
background-image: url("/resources/theme/images/buttons/spawn/smoke.svg"); width: 22px;
background-size: 48px; margin: 0px 5px;
} }
#explosion-spawn-button { .upper-bar button:nth-child(2) {
background-image: url("/resources/theme/images/buttons/spawn/explosion.svg"); margin-left: auto;
background-size: 48px;
} }
.unit-spawn-button { [data-coalition="blue"]#active-coalition-label,
border: none; [data-coalition="blue"].deploy-unit-button,
border-radius: 0px; [data-coalition="blue"]#spawn-airbase-aircraft-button,
height: 48px; [data-coalition="blue"].create-iads-button {
margin-bottom: -10px;
margin-top: -10px;
width: 48px;
}
.unit-spawn-button:last-of-type {
border-bottom-right-radius: var(--border-radius-sm);
border-top-right-radius: var(--border-radius-sm);
}
[data-active-coalition="blue"].unit-spawn-button:hover,
[data-active-coalition="blue"].unit-spawn-button.is-open,
[data-active-coalition="blue"]#active-coalition-label,
[data-active-coalition="blue"].deploy-unit-button,
[data-active-coalition="blue"]#spawn-airbase-aircraft-button {
background-color: var(--primary-blue) background-color: var(--primary-blue)
} }
[data-active-coalition="red"].unit-spawn-button:hover, [data-coalition="red"]#active-coalition-label,
[data-active-coalition="red"].unit-spawn-button.is-open, [data-coalition="red"].deploy-unit-button,
[data-active-coalition="red"]#active-coalition-label, [data-coalition="red"]#spawn-airbase-aircraft-button,
[data-active-coalition="red"].deploy-unit-button, [data-coalition="red"].create-iads-button {
[data-active-coalition="red"]#spawn-airbase-aircraft-button {
background-color: var(--primary-red) background-color: var(--primary-red)
} }
[data-active-coalition="neutral"].unit-spawn-button:hover, [data-coalition="neutral"]#active-coalition-label,
[data-active-coalition="neutral"].unit-spawn-button.is-open, [data-coalition="neutral"].deploy-unit-button,
[data-active-coalition="neutral"]#active-coalition-label, [data-coalition="neutral"]#spawn-airbase-aircraft-button,
[data-active-coalition="neutral"].deploy-unit-button, [data-coalition="neutral"].create-iads-button {
[data-active-coalition="neutral"]#spawn-airbase-aircraft-button {
background-color: var(--primary-neutral) background-color: var(--primary-neutral)
} }
[data-active-coalition="blue"].deploy-unit-button:disabled { [data-coalition="blue"].deploy-unit-button:disabled {
background-color: transparent; background-color: transparent;
border: 1px solid var(--primary-blue); border: 1px solid var(--primary-blue);
cursor: default; cursor: default;
} }
[data-active-coalition="red"].deploy-unit-button:disabled { [data-coalition="red"].deploy-unit-button:disabled {
background-color: transparent; background-color: transparent;
border: 1px solid var(--primary-red); border: 1px solid var(--primary-red);
cursor: default; cursor: default;
} }
[data-active-coalition="neutral"].deploy-unit-button:disabled { [data-coalition="neutral"].deploy-unit-button:disabled {
background-color: transparent; background-color: transparent;
border: 1px solid var(--primary-neutral); border: 1px solid var(--primary-neutral);
cursor: default; cursor: default;
} }
[data-active-coalition="blue"]#active-coalition-label::after { [data-coalition="blue"]#active-coalition-label::after {
content: "Create blue unit"; content: "Create blue unit";
} }
[data-active-coalition="red"]#active-coalition-label::after { [data-coalition="red"]#active-coalition-label::after {
content: "Create red unit"; content: "Create red unit";
} }
[data-active-coalition="neutral"]#active-coalition-label::after { [data-coalition="neutral"]#active-coalition-label::after {
content: "Create neutral unit"; content: "Create neutral unit";
} }
#loadout-preview { #aircraft-loadout-preview,
#helicopter-loadout-preview {
align-content: space-between; align-content: space-between;
align-items: center; align-items: center;
column-gap: 20px; column-gap: 20px;
@@ -191,14 +147,16 @@
width: 100%; width: 100%;
} }
#loadout-list { #aircaft-loadout-list,
#helicopter-loadout-list {
align-content: center; align-content: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
#unit-image { #aircraft-unit-image,
#helicopter-unit-image {
filter: invert(100%); filter: invert(100%);
height: 100px; height: 100px;
margin-bottom: 10px; margin-bottom: 10px;
@@ -251,17 +209,28 @@
background-color: orange; background-color: orange;
} }
#aircraft-spawn-menu .ol-slider-value { .ol-context-menu .ol-slider-value {
color: var(--accent-light-blue); color: var(--accent-light-blue);
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
} }
#aircraft-spawn-altitude-slider { .ol-context-menu .ol-slider-container {
padding: 0px 10px; padding: 0px 10px;
} }
.contextmenu-options-container {
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 10px;
}
.contextmenu-options-container>*:nth-child(2) {
width: 120px;
}
/* Unit context menu */ /* Unit context menu */
#unit-contextmenu { #unit-contextmenu {
display: flex; display: flex;
@@ -407,3 +376,71 @@
width: 180px; width: 180px;
z-index: 9999; z-index: 9999;
} }
/* Coalition area context menu */
#coalition-area-contextmenu {
display: flex;
flex-direction: column;
height: fit-content;
position: absolute;
row-gap: 5px;
width: 300px;
z-index: 9999;
}
#coalition-area-switch {
margin-right: 10px;
height: 25px;
width: 50px;
}
#coalition-area-contextmenu .ol-checkbox {
align-self: flex-start;
}
#coalition-units-checkbox {
padding: 10px 10px;
}
#iads-menu .ol-select-options>* {
padding-top: 8px;
padding-bottom: 8px;
}
#iads-menu .ol-select-options>*:first-child {
padding-top: 15px;
}
#iads-menu .ol-select-options>*:last-child {
padding-bottom: 15px;
}
#iads-menu .ol-select {
width: 100%;
}
#iads-menu {
row-gap: 10px;
}
#coalition-area-contextmenu>div:nth-child(2) {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-right: 0px;
}
#coalition-area-contextmenu>div:nth-child(n+3) {
align-items: center;
display: flex;
flex-direction: column;
justify-content: space-between;
row-gap: 5px;
}
.create-iads-button {
margin-top: 5px;
text-align: center;
width: 100%;
}

View File

@@ -0,0 +1,28 @@
#log-panel>div:first-child {
width: 100%;
height: 38px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
#log-panel svg {
pointer-events: none;
}
#log-panel>div:nth-child(2) {
display: none;
margin-top: 5px;
width: 100%;
height: calc(100% - 40px);
background-color: #00000055;
}
#log-panel.open {
height: 505px;
}
#log-panel.open>div:nth-child(2) {
display: block;
}

View File

@@ -0,0 +1,43 @@
#server-status-panel {
display: flex;
flex-direction: row;
justify-content: space-between;
column-gap: 10px;
}
#server-status-panel .ol-data-grid {
width: 100%;
}
#server-status-panel .ol-data-grid:first-of-type {
border-right: 1px solid gray;
padding-right: 10px;
}
#server-status-panel dd {
font-weight: bold;
}
.fps-low {
color: red;
}
.fps-medium {
color: orange;
}
.fps-high {
color: lightgreen;
}
.load-low {
color: lightgreen;
}
.load-medium {
color: orange;
}
.load-high {
color: red;
}

View File

@@ -25,7 +25,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
#unit-control-panel #selected-units-container button { #unit-control-panel #selected-units-container button {
align-items: center; align-items: center;
border-radius: var(--border-radius-lg); border-radius: var(--border-radius-md);
display: flex; display: flex;
font-size: 11px; font-size: 11px;
height: 32px; height: 32px;

View File

@@ -27,7 +27,7 @@
} }
#current-task { #current-task {
border-radius: var(--border-radius-lg); border-radius: var(--border-radius-md);
margin-top: auto; margin-top: auto;
padding: 6px 16px; padding: 6px 16px;
} }

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg4"
sodipodi:docname="back.svg"
width="32"
height="32"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="13.078125"
inkscape:cx="19.918757"
inkscape:cy="17.663082"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="M 29.911273,24.547406 H 2.1935652 c -0.9582092,0 -1.73235714,0.774146 -1.73235714,1.732357 0,0.958208 0.77414794,1.732356 1.73235714,1.732356 H 29.911273 c 0.958209,0 1.732357,-0.774148 1.732357,-1.732356 0,-0.958211 -0.774148,-1.732357 -1.732357,-1.732357 z m -7.215201,-9.890675 c -0.676702,-0.676702 -1.775666,-0.676702 -2.452368,0 l -2.235822,2.241238 V 5.4914811 c 0,-0.9582093 -0.774146,-1.7323555 -1.732357,-1.7323555 -0.958211,0 -1.732355,0.7741462 -1.732355,1.7323555 V 16.897969 l -2.241238,-2.241238 c -0.676702,-0.676702 -1.775666,-0.676702 -2.4523675,0 -0.6767003,0.676702 -0.6767003,1.775666 0,2.452368 l 5.1970695,5.197069 c 0.676701,0.676702 1.775668,0.676702 2.452368,0 l 5.19707,-5.197069 c 0.676703,-0.676702 0.676703,-1.775666 0,-2.452368 z"
id="path2"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.0541362"
sodipodi:nodetypes="ssssssssccssscssccccs" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg1940"
sodipodi:docname="delete.svg"
width="32"
height="32"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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="defs1944" />
<sodipodi:namedview
id="namedview1942"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="13.078125"
inkscape:cx="27.870968"
inkscape:cy="20.415771"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1940" />
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="m 13.120438,4.047182 -1.120204,1.6744053 h 8.548913 L 19.428943,4.047182 C 19.340506,3.9174745 19.193112,3.8349334 19.033925,3.8349334 h -5.524364 c -0.159185,0 -0.306581,0.076645 -0.39502,0.2122486 z m 8.666825,-1.5682824 2.163757,3.2426877 h 0.813623 2.829981 0.471664 c 0.784142,0 1.414993,0.6308515 1.414993,1.4149933 0,0.7841418 -0.630851,1.4149912 -1.414993,1.4149912 H 27.594624 V 26.474805 c 0,2.605944 -2.110695,4.716638 -4.716639,4.716638 H 9.6713954 c -2.6059417,0 -4.7166402,-2.110694 -4.7166402,-4.716638 V 8.5515718 H 4.4830908 c -0.7841402,0 -1.4149907,-0.6308494 -1.4149907,-1.4149912 0,-0.7841418 0.6308505,-1.4149933 1.4149907,-1.4149933 H 4.9547552 7.7847397 8.5983591 L 10.762118,2.4730039 C 11.375281,1.559155 12.407047,1.0049496 13.509561,1.0049496 h 5.524364 c 1.102514,0 2.13428,0.5542053 2.747441,1.4680543 z M 7.7847397,8.5515718 V 26.474805 c 0,1.043557 0.8430989,1.886656 1.8866557,1.886656 H 22.877985 c 1.043557,0 1.886658,-0.843099 1.886658,-1.886656 V 8.5515718 Z m 4.7166383,3.7733162 v 12.263261 c 0,0.51883 -0.424497,0.943327 -0.943327,0.943327 -0.51883,0 -0.943327,-0.424497 -0.943327,-0.943327 V 12.324888 c 0,-0.518832 0.424497,-0.943328 0.943327,-0.943328 0.51883,0 0.943327,0.424496 0.943327,0.943328 z m 4.71664,0 v 12.263261 c 0,0.51883 -0.424499,0.943327 -0.943327,0.943327 -0.518832,0 -0.943328,-0.424497 -0.943328,-0.943327 V 12.324888 c 0,-0.518832 0.424496,-0.943328 0.943328,-0.943328 0.518828,0 0.943327,0.424496 0.943327,0.943328 z m 4.71664,0 v 12.263261 c 0,0.51883 -0.424498,0.943327 -0.943328,0.943327 -0.518829,0 -0.943327,-0.424497 -0.943327,-0.943327 V 12.324888 c 0,-0.518832 0.424498,-0.943328 0.943327,-0.943328 0.51883,0 0.943328,0.424496 0.943328,0.943328 z"
id="path1938"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.0589579;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z"/></svg>

After

Width:  |  Height:  |  Size: 508 B

View File

@@ -1,20 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <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"
width="32" width="32"
height="32" height="32"
viewBox="0 0 32 32" viewBox="0 0 32 32"
fill="none"
version="1.1" version="1.1"
id="svg8" id="svg8"
sodipodi:docname="spawn_aircraft.svg" sodipodi:docname="aircraft.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"> inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata <metadata
id="metadata14"> id="metadata14">
<rdf:RDF> <rdf:RDF>
@@ -44,33 +43,18 @@
id="namedview10" id="namedview10"
showgrid="false" showgrid="false"
inkscape:zoom="18.782524" inkscape:zoom="18.782524"
inkscape:cx="26.073424" inkscape:cx="26.114701"
inkscape:cy="15.446316" inkscape:cy="15.493125"
inkscape:window-x="1912" inkscape:window-x="1912"
inkscape:window-y="-8" inkscape:window-y="-8"
inkscape:window-maximized="1" inkscape:window-maximized="1"
inkscape:current-layer="svg8" /> inkscape:current-layer="svg8"
<rect inkscape:showpageshadow="2"
x="0.5" inkscape:pagecheckerboard="0"
y="0.5" inkscape:deskcolor="#d1d1d1" />
width="31"
height="31"
rx="7.5"
fill="white"
id="rect2"
style="fill:none" />
<path <path
d="m 22.011184,13.780381 c 1.003947,0 2.775568,0.85633 2.775568,1.889757 0,1.063016 -1.771621,1.889759 -2.775568,1.889759 h -3.454693 l -2.952773,5.196833 c -0.177205,0.295233 -0.502027,0.472439 -0.826742,0.472439 h -1.653592 c -0.324822,0 -0.560988,-0.295235 -0.472439,-0.590577 l 1.446906,-5.078695 H 11.086009 L 9.786807,19.272448 C 9.698217,19.390585 9.58011,19.449654 9.432472,19.449654 H 8.192315 c -0.236219,0 -0.413381,-0.177206 -0.413381,-0.413371 0,-0.02959 0,-0.05907 0,-0.08855 L 8.723813,15.670138 7.778934,12.422131 c 0,-0.02959 0,-0.05907 0,-0.118137 0,-0.206685 0.177162,-0.413371 0.413381,-0.413371 h 1.240157 c 0.147638,0 0.265743,0.08855 0.354335,0.206686 l 1.299202,1.683072 h 3.011842 L 12.650945,8.7311863 C 12.562399,8.435909 12.798562,8.1111082 13.123384,8.1111082 h 1.653592 c 0.324715,0 0.649537,0.2066963 0.826742,0.5019629 l 2.952773,5.1673099 z" d="m 25.924821,12.489287 c 1.68985,0 4.671853,1.44138 4.671853,3.180851 0,1.789275 -2.982003,3.180853 -4.671853,3.180853 h -5.81496 l -4.970125,8.747341 c -0.298273,0.496938 -0.845015,0.795212 -1.391577,0.795212 h -2.783336 c -0.546743,0 -0.944259,-0.496941 -0.795213,-0.994063 l 2.435441,-8.54849 H 7.5355007 L 5.348676,21.733567 C 5.1995608,21.932416 5.0007624,22.031842 4.7522572,22.031842 H 2.6648175 c -0.3976052,0 -0.6958054,-0.298275 -0.6958054,-0.695789 0,-0.04981 0,-0.09943 0,-0.149048 l 1.590426,-5.516867 -1.590426,-5.467065 c 0,-0.04981 0,-0.09943 0,-0.198849 0,-0.3478937 0.2982002,-0.6957888 0.6958054,-0.6957888 h 2.0874397 c 0.2485052,0 0.4473003,0.1490479 0.5964188,0.3478951 L 7.5355007,12.489287 H 12.605051 L 10.16961,3.9904523 C 10.020569,3.4934397 10.41808,2.9467331 10.964823,2.9467331 h 2.783336 c 0.546562,0 1.093304,0.3479124 1.391577,0.8449069 l 4.970125,8.697647 z"
fill="#202831" fill="#202831"
id="path4" id="path4"
style="fill:#ffffff;fill-opacity:1;stroke-width:1.07987" /> style="fill-opacity:1;stroke-width:1.81764" />
<rect
x="0.5"
y="0.5"
width="31"
height="31"
rx="7.5"
stroke="white"
id="rect6"
style="fill:none;stroke:none" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -24,7 +24,7 @@
inkscape:deskcolor="#d1d1d1" inkscape:deskcolor="#d1d1d1"
showgrid="false" showgrid="false"
inkscape:zoom="13.078125" inkscape:zoom="13.078125"
inkscape:cx="19.53644" inkscape:cx="18.389486"
inkscape:cy="12.157706" inkscape:cy="12.157706"
inkscape:window-width="1920" inkscape:window-width="1920"
inkscape:window-height="1017" inkscape:window-height="1017"
@@ -34,9 +34,9 @@
inkscape:current-layer="svg4" /> inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --> <!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path <path
d="m 23.013304,7.2180185 c 0.209962,-0.3739974 0.64242,-0.5068189 0.993401,-0.2971008 0.350981,0.2097182 0.495133,0.6815841 0.332178,1.0835439 L 20.042501,18.658144 c 0.06894,0.08039 0.134753,0.164279 0.197427,0.248166 l 3.046013,-1.911931 c 0.329044,-0.206222 0.739567,-0.108354 0.968331,0.2237 0.228764,0.332054 0.197427,0.803919 -0.06894,1.10102 l -2.726369,3.040913 h -2.240637 c -0.413656,-1.303747 -1.52614,-2.236993 -2.836051,-2.236993 -1.309911,0 -2.425529,0.933246 -2.83605,2.236993 H 11.048617 L 8.6826296,19.535465 C 8.3786552,19.30128 8.2721074,18.850386 8.4350628,18.476388 8.5980181,18.102391 8.9803366,17.920635 9.3344512,18.042971 l 3.0460128,1.062571 c 0.09401,-0.136317 0.191159,-0.269138 0.294573,-0.394969 L 10.722706,15.08245 c -0.191159,-0.353027 -0.122217,-0.807416 0.159822,-1.073059 0.282038,-0.265643 0.695694,-0.262148 0.974598,0.0034 l 3.208969,3.072372 c 0.047,-0.01398 0.09401,-0.02796 0.141019,-0.03845 l 0.426191,-4.987745 c 0.0376,-0.429921 0.360382,-0.75848 0.748968,-0.75848 0.388586,0 0.711362,0.328559 0.748967,0.75848 l 0.423058,4.959835 z M 9.362655,22.482004 v 0 H 23.40189 v 0 h 1.002802 c 0.554675,0 1.002803,0.499829 1.002803,1.118498 0,0.618669 -0.448128,1.118496 -1.002803,1.118496 H 8.3598525 c -0.554675,0 -1.0028024,-0.499827 -1.0028024,-1.118496 0,-0.618669 0.4481274,-1.118498 1.0028024,-1.118498 z M 16.382273,6.8230493 c 0.416789,0 0.752101,0.3739974 0.752101,0.8388726 v 1.6777453 c 0,0.4648752 -0.335312,0.8388728 -0.752101,0.8388728 -0.41679,0 -0.752102,-0.3739976 -0.752102,-0.8388728 V 7.6619219 c 0,-0.4648752 0.335312,-0.8388726 0.752102,-0.8388726 z" d="m 26.759564,2.1225723 c 0.340688,-0.6068538 1.042401,-0.822372 1.611908,-0.4820802 0.569507,0.3402919 0.80341,1.1059486 0.538997,1.7581747 L 21.939094,20.685494 c 0.111864,0.130442 0.218653,0.266561 0.320348,0.402678 l 4.942507,-3.102328 c 0.533912,-0.334619 1.200033,-0.175817 1.571229,0.362979 0.371196,0.538795 0.320348,1.304451 -0.111863,1.786531 l -4.423848,4.934232 h -3.635691 c -0.671205,-2.11548 -2.476338,-3.629779 -4.601819,-3.629779 -2.125482,0 -3.9357,1.514299 -4.601818,3.629779 H 7.345485 L 3.506398,22.109048 C 3.013164,21.729056 2.840278,20.997429 3.104692,20.390574 3.369106,19.783721 3.989462,19.488801 4.564053,19.687305 L 9.50656,21.411449 C 9.659102,21.190258 9.8167362,20.974741 9.9845364,20.770566 L 6.816656,14.883519 c -0.310177,-0.572827 -0.198311,-1.310126 0.25933,-1.741162 0.457639,-0.431036 1.128844,-0.425365 1.581397,0.0055 l 5.20692,4.985277 c 0.07626,-0.02268 0.152542,-0.04537 0.22882,-0.06239 l 0.691544,-8.093192 c 0.06101,-0.6975963 0.584761,-1.2307211 1.215286,-1.2307211 0.630526,0 1.154267,0.5331248 1.215285,1.2307211 l 0.686461,8.047904 z M 4.609817,26.890147 v 0 H 27.39009 v 0 h 1.627161 c 0.900024,0 1.627164,0.81103 1.627164,1.814891 0,1.003862 -0.72714,1.814889 -1.627164,1.814889 H 2.982655 c -0.900024,0 -1.627163,-0.811027 -1.627163,-1.814889 0,-1.003861 0.727139,-1.814891 1.627163,-1.814891 z M 15.999953,1.4816894 c 0.676288,0 1.220371,0.6068538 1.220371,1.3611673 v 2.7223348 c 0,0.7543135 -0.544083,1.3611677 -1.220371,1.3611677 -0.676289,0 -1.220372,-0.6068542 -1.220372,-1.3611677 V 2.8428567 c 0,-0.7543135 0.544083,-1.3611673 1.220372,-1.3611673 z"
fill="#ffffff" fill="#ffffff"
stroke="#ffffff" stroke="#ffffff"
id="path2" id="path2"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.0330959" /> style="fill-opacity:1;stroke-width:0.0537019" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
version="1.1"
id="svg8"
sodipodi:docname="helicopter.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata14">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs12" />
<sodipodi:namedview
inkscape:document-rotation="0"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview10"
showgrid="false"
inkscape:zoom="13.28125"
inkscape:cx="11.858824"
inkscape:cy="13.101176"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg8"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<path
d="m 6.3544349,4.6930773 c 0,-0.8721964 0.7046585,-1.5768552 1.5768567,-1.5768552 H 26.853493 c 0.872197,0 1.576856,0.7046588 1.576856,1.5768552 0,0.8721978 -0.704659,1.5768478 -1.576856,1.5768478 h -7.884254 v 3.1537018 h 1.576855 c 4.356045,0 7.884255,3.5282031 7.884255,7.8842541 v 3.153697 c 0,0.872195 -0.704659,1.576855 -1.576856,1.576855 h -7.884254 -3.153695 c -0.990466,0 -1.926715,-0.468129 -2.522966,-1.26148 L 9.7742292,16.085817 c -0.1724691,-0.2316 -0.4089908,-0.408991 -0.67509,-0.517398 l -6.72625,-2.690505 C 1.90476,12.690662 1.5499693,12.286593 1.4267781,11.793826 L 0.29342123,7.25053 C 0.16529133,6.7528385 0.54472759,6.2699251 1.0572018,6.2699251 h 1.3551113 c 0.49769,0 0.9658193,0.2315996 1.2614793,0.6307441 L 5.5660161,9.4236269 H 15.815544 V 6.2699251 H 7.9312916 c -0.8721982,0 -1.5768567,-0.70465 -1.5768567,-1.5768478 z M 18.969239,18.884729 h 6.307407 v -1.576848 c 0,-2.611659 -2.118892,-4.73055 -4.730552,-4.73055 h -1.576855 z m 12.151606,5.19375 c 0.615961,0.615957 0.615961,1.616279 0,2.232229 l -0.192176,0.192183 c -1.182641,1.182634 -2.789057,1.84787 -4.459529,1.84787 h -13.8073 c -0.872195,0 -1.576855,-0.704658 -1.576855,-1.576847 0,-0.872197 0.70466,-1.576855 1.576855,-1.576855 h 13.8073 c 0.837698,0 1.640911,-0.330154 2.232231,-0.921474 l 0.192176,-0.192177 c 0.615959,-0.615959 1.61627,-0.615959 2.232229,0 z"
id="path1174"
style="fill-opacity:1;stroke-width:0.0492765" />
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
version="1.1"
id="svg8"
sodipodi:docname="unit.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata14">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs12" />
<sodipodi:namedview
inkscape:document-rotation="0"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview10"
showgrid="false"
inkscape:zoom="9.391262"
inkscape:cx="25.715394"
inkscape:cy="24.171405"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg8"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<rect
id="rect894"
width="5.696785"
height="22.699497"
x="13.203763"
y="4.5254831"
ry="1.6436043" />
<rect
id="rect894-7"
width="5.696785"
height="22.699497"
x="12.751214"
y="-27.588247"
ry="1.6436043"
transform="rotate(90)" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
version="1.1"
id="svg8"
sodipodi:docname="navyunit.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata14">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs12" />
<sodipodi:namedview
inkscape:document-rotation="0"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview10"
showgrid="false"
inkscape:zoom="18.782524"
inkscape:cx="35.857801"
inkscape:cy="16.398222"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg8"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<path
d="m 11.24439,3.5672501 c 0,-0.9200678 0.743331,-1.6634002 1.663401,-1.6634002 h 6.653596 c 0.920068,0 1.663398,0.7433324 1.663398,1.6634002 v 1.6633976 h 2.495098 c 1.377503,0 2.495102,1.1175989 2.495102,2.4951006 v 6.6535967 l 2.307964,0.769323 c 1.200768,0.400255 1.533447,1.949294 0.597785,2.801785 l -5.250104,4.813463 c -0.842097,0.488625 -1.803748,0.784917 -2.645845,0.784917 -1.018831,0 -2.120833,-0.400253 -3.077288,-1.055219 -1.148785,-0.805708 -2.682232,-0.805708 -3.831017,0 -0.88888,0.613379 -1.975286,1.055219 -3.077289,1.055219 -0.842094,0 -1.8037459,-0.296292 -2.6458445,-0.784917 L 3.3432428,17.950453 C 2.4075824,17.092766 2.7402625,15.548923 3.9410271,15.148668 L 6.25419,14.379345 V 7.7257483 c 0,-1.3775017 1.1175986,-2.4951006 2.4951017,-2.4951006 H 11.24439 Z M 9.5809881,13.272145 15.184569,11.406021 c 0.680952,-0.228708 1.419085,-0.228708 2.105238,0 l 5.59838,1.866124 V 8.5574463 H 9.5809881 Z m 7.6152529,10.562584 c 1.169577,0.805711 2.599061,1.356711 4.028544,1.356711 1.398295,0 2.879759,-0.561396 4.023347,-1.356711 v 0 c 0.618575,-0.44184 1.460674,-0.405453 2.037665,0.08836 0.748531,0.618578 1.689389,1.091607 2.630247,1.309929 0.894079,0.207933 1.45028,1.102 1.242353,1.996079 -0.207931,0.894078 -1.102001,1.450276 -1.99608,1.242351 -1.273537,-0.296292 -2.333955,-0.857691 -3.025306,-1.299534 -1.507454,0.81091 -3.196843,1.346319 -4.912226,1.346319 -1.6582,0 -3.150063,-0.514616 -4.17929,-0.982449 -0.301493,-0.140338 -0.576991,-0.275498 -0.810906,-0.400255 -0.233924,0.12477 -0.50422,0.265105 -0.810908,0.400255 -1.02923,0.467833 -2.521091,0.982449 -4.179291,0.982449 -1.7153815,0 -3.404772,-0.535409 -4.9122277,-1.341118 C 5.6356148,27.613758 4.5803957,28.180351 3.306857,28.476646 2.4127783,28.684579 1.5186998,28.128372 1.3107769,27.234294 1.1028445,26.340218 1.6590517,25.44614 2.5531281,25.238215 3.4939891,25.019896 4.4348476,24.546866 5.1833783,23.928289 5.7603695,23.439666 6.6024647,23.403277 7.2210429,23.83993 v 0 c 1.1487833,0.790114 2.6250524,1.35151 4.0233471,1.35151 1.429485,0 2.858967,-0.550998 4.028544,-1.356711 0.576995,-0.410651 1.346314,-0.410651 1.923307,3e-6 z"
id="path6267"
style="fill-opacity:1;stroke-width:0.0519812" />
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -1,20 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <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"
width="32" width="32"
height="32" height="32"
viewBox="0 0 32 32" viewBox="0 0 32 32"
fill="none"
version="1.1" version="1.1"
id="svg15" id="svg15"
sodipodi:docname="spawn_smoke.svg" sodipodi:docname="smoke.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"> inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata <metadata
id="metadata19"> id="metadata19">
<rdf:RDF> <rdf:RDF>
@@ -23,7 +22,7 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title> <dc:title />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
@@ -42,25 +41,19 @@
id="namedview17" id="namedview17"
showgrid="false" showgrid="false"
inkscape:zoom="18.782524" inkscape:zoom="18.782524"
inkscape:cx="15.515904" inkscape:cx="15.572987"
inkscape:cy="14.452888" inkscape:cy="14.481547"
inkscape:window-x="1912" inkscape:window-x="1912"
inkscape:window-y="-8" inkscape:window-y="-8"
inkscape:window-maximized="1" inkscape:window-maximized="1"
inkscape:current-layer="svg15" /> inkscape:current-layer="svg15"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<path <path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.0296135;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill-opacity:1;stroke:none;stroke-width:0.0484484;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 7.4791847,12.531786 c 0,2.38189 1.8897313,4.31437 4.2189373,4.31437 h 3.612465 c 0.662138,0.596221 1.529364,0.958747 2.481554,0.958747 0.952191,0 1.819416,-0.362526 2.481555,-0.958747 h 0.799841 c 1.813557,0 3.281394,-1.501042 3.281394,-3.355622 0,-1.85458 -1.467837,-3.355621 -3.281394,-3.355621 -0.313492,0 -0.615263,0.04495 -0.902385,0.128832 -0.626981,-1.2164128 -1.878013,-2.04633 -3.316553,-2.04633 -0.95512,0 -1.828206,0.3655228 -2.493274,0.9677373 C 13.634731,8.5799418 12.708908,8.217415 11.698122,8.217415 c -2.329206,0 -4.2189373,1.932479 -4.2189373,4.314371 z m 17.1101313,6.711242 h -9.844184 c -0.389665,0 -0.703157,0.320582 -0.703157,0.719061 0,0.398479 0.313492,0.719062 0.703157,0.719062 h 9.844184 c 0.389667,0 0.703157,-0.320583 0.703157,-0.719062 0,-0.398479 -0.31349,-0.719061 -0.703157,-0.719061 z m -1.875082,2.876246 H 19.43284 c -0.389666,0 -0.703158,0.320582 -0.703158,0.719063 0,0.398479 0.313492,0.71906 0.703158,0.71906 h 3.281394 c 0.389665,0 0.703156,-0.320581 0.703156,-0.71906 0,-0.398481 -0.313491,-0.719063 -0.703156,-0.719063 z m -5.625249,0 H 7.2447992 c -0.3896663,0 -0.7031566,0.320582 -0.7031566,0.719063 0,0.398479 0.3134903,0.71906 0.7031566,0.71906 h 9.8441858 c 0.389666,0 0.703156,-0.320581 0.703156,-0.71906 0,-0.398481 -0.31349,-0.719063 -0.703156,-0.719063 z m -3.984552,-2.157185 c 0,-0.398479 -0.313489,-0.719061 -0.703155,-0.719061 H 9.354267 c -0.389665,0 -0.703155,0.320582 -0.703155,0.719061 0,0.398479 0.31349,0.719062 0.703155,0.719062 h 3.047011 c 0.389666,0 0.703155,-0.320583 0.703155,-0.719062 z" d="m 2.1124942,10.397531 c 0,3.896829 3.0916459,7.058413 6.902283,7.058413 h 5.9100798 c 1.083274,0.975432 2.502076,1.568533 4.059882,1.568533 1.557807,0 2.976608,-0.593101 4.059884,-1.568533 h 1.308559 c 2.967023,0 5.36844,-2.455741 5.36844,-5.489878 0,-3.0341379 -2.401417,-5.4898771 -5.36844,-5.4898771 -0.512881,0 -1.006585,0.073539 -1.476324,0.2107723 -1.025756,-1.9900807 -3.072474,-3.3478452 -5.42596,-3.3478452 -1.5626,0 -2.990989,0.5980041 -4.079057,1.5832415 C 12.183116,3.9322186 10.668447,3.339116 9.0147772,3.339116 c -3.8106371,0 -6.902283,3.1615822 -6.902283,7.058415 z M 30.105081,21.377284 H 13.999759 c -0.637501,0 -1.150382,0.52448 -1.150382,1.176402 0,0.651921 0.512881,1.176402 1.150382,1.176402 h 16.105322 c 0.637505,0 1.150382,-0.524481 1.150382,-1.176402 0,-0.651922 -0.512877,-1.176402 -1.150382,-1.176402 z m -3.067679,4.705608 h -5.36844 c -0.637503,0 -1.150383,0.52448 -1.150383,1.176405 0,0.651921 0.51288,1.176399 1.150383,1.176399 h 5.36844 c 0.637501,0 1.15038,-0.524478 1.15038,-1.176399 0,-0.651925 -0.512879,-1.176405 -1.15038,-1.176405 z m -9.203043,0 H 1.7290339 c -0.6375035,0 -1.15038115,0.52448 -1.15038115,1.176405 0,0.651921 0.51287765,1.176399 1.15038115,1.176399 H 17.834359 c 0.637503,0 1.15038,-0.524478 1.15038,-1.176399 0,-0.651925 -0.512877,-1.176405 -1.15038,-1.176405 z m -6.518823,-3.529206 c 0,-0.651922 -0.512876,-1.176402 -1.150379,-1.176402 H 5.180174 c -0.6375013,0 -1.1503785,0.52448 -1.1503785,1.176402 0,0.651921 0.5128772,1.176402 1.1503785,1.176402 h 4.984983 c 0.637503,0 1.150379,-0.524481 1.150379,-1.176402 z"
id="path2-7" /> id="path2-7" />
<rect
x="0.5"
y="0.5"
width="31"
height="31"
rx="7.5"
stroke="white"
id="rect8"
style="fill:none;stroke:none" />
<defs <defs
id="defs13"> id="defs13">
<clipPath <clipPath

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M96 151.4V360.6c9.7 5.6 17.8 13.7 23.4 23.4H328.6c0-.1 .1-.2 .1-.3l-4.5-7.9-32-56 0 0c-1.4 .1-2.8 .1-4.2 .1c-35.3 0-64-28.7-64-64s28.7-64 64-64c1.4 0 2.8 0 4.2 .1l0 0 32-56 4.5-7.9-.1-.3H119.4c-5.6 9.7-13.7 17.8-23.4 23.4zM384.3 352c35.2 .2 63.7 28.7 63.7 64c0 35.3-28.7 64-64 64c-23.7 0-44.4-12.9-55.4-32H119.4c-11.1 19.1-31.7 32-55.4 32c-35.3 0-64-28.7-64-64c0-23.7 12.9-44.4 32-55.4V151.4C12.9 140.4 0 119.7 0 96C0 60.7 28.7 32 64 32c23.7 0 44.4 12.9 55.4 32H328.6c11.1-19.1 31.7-32 55.4-32c35.3 0 64 28.7 64 64c0 35.3-28.5 63.8-63.7 64l-4.5 7.9-32 56-2.3 4c4.2 8.5 6.5 18 6.5 28.1s-2.3 19.6-6.5 28.1l2.3 4 32 56 4.5 7.9z"/></svg>

After

Width:  |  Height:  |  Size: 872 B

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 512 512"
version="1.1"
id="svg4"
sodipodi:docname="ground.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="1.1559539"
inkscape:cx="432.97576"
inkscape:cy="301.91516"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="m 130.61192,122.94993 c 4.83398,-2.4171 10.52734,-2.4171 15.36132,0 l 85.9375,42.97085 c 8.48633,4.24336 11.92383,14.55637 7.68066,23.04312 -3.00781,6.01593 -9.07714,9.5073 -15.36132,9.5073 v 42.97085 c 0,9.50729 -7.68066,17.18833 -17.1875,17.18833 h -2.63184 l 17.1875,103.13004 266.68729,0 c 9.50683,0 17.1875,7.68105 17.1875,17.18835 0,9.5073 -7.68067,17.18833 -17.1875,17.18833 l -280.97441,0 H 206.72031 69.86485 69.27404 17.98008 c -9.50683,0 -17.1875,-7.68103 -17.1875,-17.18833 0,-9.5073 7.68067,-17.18835 17.1875,-17.18835 h 37.00684 l 17.1875,-103.13004 h -2.63184 c -9.50684,0 -17.1875,-7.68104 -17.1875,-17.18833 V 198.4712 c -6.28418,0 -12.35351,-3.49137 -15.36132,-9.5073 -4.24317,-8.48675 -0.80567,-18.79976 7.68066,-23.04312 z m 39.10155,238.81049 -31.42089,-26.21222 -31.4209,26.21222 z m -62.68066,-103.13004 -2.52441,15.20096 33.78418,28.19961 33.78418,-28.19961 -2.52441,-15.20096 z m -7.46582,44.68969 -6.01561,35.98808 24.59961,-20.51857 z m 58.86718,15.46951 24.59961,20.46487 -6.01561,-35.98809 z M 95.32383,189.87702 c -4.72656,0 -8.59374,3.86738 -8.59374,8.59418 0,4.72679 3.86718,8.59417 8.59374,8.59417 h 85.9375 c 4.72656,0 8.59374,-3.86738 8.59374,-8.59417 0,-4.7268 -3.86718,-8.59418 -8.59374,-8.59418 z"
id="path2"
style="stroke-width:0.537122"
sodipodi:nodetypes="cccscssccsssccccsssccsscsccccccccccccccccccccsssssss" />
<path
d="m 398.88439,290.60444 -56.18308,-59.35032 c -4.82498,-5.06298 -11.50756,-7.93072 -18.4952,-7.84559 l -20.1384,0.17717 c -4.88076,0.0647 -7.83178,5.39609 -5.3158,9.5508 l 35.56198,58.63664 -43.41565,0.78856 -16.07612,-19.15941 c -2.39827,-2.87404 -5.9964,-4.54245 -9.73595,-4.48875 l -14.13363,0.13667 c -4.09068,0.0573 -7.05613,3.89607 -6.017,7.85435 l 11.10611,42.68377 c 0.92991,3.55548 3.36429,6.5345 6.64574,8.13577 l 48.58659,23.70886 c 1.76422,0.86089 3.69975,1.28114 5.63108,1.26246 l 119.77611,-1.14062 c 20.41896,-0.21499 40.00846,-8.13034 54.85055,-22.163 7.69088,-7.25597 5.74152,-20.00247 -3.78528,-24.65128 l -20.25324,-9.883 c -7.12743,-3.47798 -15.01076,-5.22788 -22.94599,-5.08091 z"
id="path1903"
sodipodi:nodetypes="ccccccccccccsscccsscc"
style="stroke-width:0.392612" />
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z"/></svg>

After

Width:  |  Height:  |  Size: 508 B

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 512 512"
version="1.1"
id="svg4"
sodipodi:docname="tower.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="0.40869141"
inkscape:cx="532.18638"
inkscape:cy="298.51374"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="m 130.61192,122.94993 c 4.83398,-2.4171 10.52734,-2.4171 15.36132,0 l 85.9375,42.97085 c 8.48633,4.24336 11.92383,14.55637 7.68066,23.04312 -3.00781,6.01593 -9.07714,9.5073 -15.36132,9.5073 v 42.97085 c 0,9.50729 -7.68066,17.18833 -17.1875,17.18833 h -2.63184 l 17.1875,103.13004 266.68729,0 c 9.50683,0 17.1875,7.68105 17.1875,17.18835 0,9.5073 -7.68067,17.18833 -17.1875,17.18833 l -280.97441,0 H 206.72031 69.86485 69.27404 17.98008 c -9.50683,0 -17.1875,-7.68103 -17.1875,-17.18833 0,-9.5073 7.68067,-17.18835 17.1875,-17.18835 h 37.00684 l 17.1875,-103.13004 h -2.63184 c -9.50684,0 -17.1875,-7.68104 -17.1875,-17.18833 V 198.4712 c -6.28418,0 -12.35351,-3.49137 -15.36132,-9.5073 -4.24317,-8.48675 -0.80567,-18.79976 7.68066,-23.04312 z m 39.10155,238.81049 -31.42089,-26.21222 -31.4209,26.21222 z m -62.68066,-103.13004 -2.52441,15.20096 33.78418,28.19961 33.78418,-28.19961 -2.52441,-15.20096 z m -7.46582,44.68969 -6.01561,35.98808 24.59961,-20.51857 z m 58.86718,15.46951 24.59961,20.46487 -6.01561,-35.98809 z M 95.32383,189.87702 c -4.72656,0 -8.59374,3.86738 -8.59374,8.59418 0,4.72679 3.86718,8.59417 8.59374,8.59417 h 85.9375 c 4.72656,0 8.59374,-3.86738 8.59374,-8.59417 0,-4.7268 -3.86718,-8.59418 -8.59374,-8.59418 z"
id="path2"
style="stroke-width:0.537122"
sodipodi:nodetypes="cccscssccsssccccsssccsscsccccccccccccccccccccsssssss" />
<path
d="m 387.14987,87.347348 -58.62532,-21.98825 c -5.0233,-1.86494 -10.58805,-1.59423 -15.37072,0.81215 l -13.80658,6.88825 c -3.33884,1.68446 -3.57948,6.34681 -0.45119,8.36215 l 44.18707,28.425312 -29.6285,15.13009 -17.50638,-7.79064 c -2.61694,-1.17311 -5.65498,-1.11295 -8.21176,0.18048 l -9.68566,4.84283 c -2.79741,1.41374 -3.5494,5.05338 -1.50398,7.42968 l 21.98825,25.65798 c 1.83486,2.13566 4.51196,3.36892 7.30937,3.36892 h 41.41973 c 1.50399,0 2.97789,-0.36095 4.3014,-1.02271 l 82.08748,-41.0287 c 13.98706,-7.00856 24.81575,-19.040442 30.32033,-33.689252 2.85757,-7.58008 -2.76733,-15.7016 -10.88885,-15.7016 h -17.26574 c -6.0761,0 -12.09204,1.44382 -17.50638,4.21116 z"
id="path1903"
sodipodi:nodetypes="ccccccccccccsscccsscc"
style="stroke-width:0.300797" />
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z"/></svg>

After

Width:  |  Height:  |  Size: 820 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 512 512"
version="1.1"
id="svg4"
sodipodi:docname="draw.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
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:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="0.81738281"
inkscape:cx="159.65591"
inkscape:cy="402.50418"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
d="m 352.44544,42.011059 -43.76337,43.76337 117.54624,117.546241 43.76337,-43.76337 c 22.60505,-22.60504 22.60505,-59.22522 0,-81.830267 L 434.36613,42.011059 c -22.60505,-22.605047 -59.22523,-22.605047 -81.83027,0 z M 288.2471,106.20939 77.477646,317.06927 c -9.4037,9.4037 -16.275634,21.0679 -20.073282,33.81715 L 25.395618,459.6619 c -2.260505,7.68572 -0.18084,15.91396 5.425211,21.52001 5.606052,5.60605 13.834289,7.68572 21.429585,5.51563 L 161.0259,454.68879 c 12.74925,-3.79764 24.41345,-10.66958 33.81715,-20.07328 l 210.9503,-210.85987 z"
id="path2"
style="stroke:#ffffff;stroke-width:45.7526;stroke-dasharray:none;stroke-opacity:1;fill:#247be2;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg8"
sodipodi:docname="groundunit-ewr.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">
<rect
x="25"
y="2.37241"
width="32"
height="32"
rx="1.1"
transform="rotate(45 25 2.37241)"
stroke="none"
stroke-width="2"
id="rect2" />
<rect
x="25"
y="5.20083"
width="28"
height="28"
transform="rotate(45 25 5.20083)"
fill="none"
stroke="#082E44"
stroke-width="2"
id="rect4" />
<path
stroke="#082E44"
stroke-width="1.5"
d="m 21.956541,27.690987 5.894515,-3.34903 -3.846924,-2.326589 5.759192,-3.618211"
id="path942"
sodipodi:nodetypes="cccc" />
<path
stroke="#082E44"
stroke-width="1.5"
d="m 26.663842,17.477657 3.292749,0.82871 -0.740705,2.943076"
id="path1230" />
<path
stroke="#082E44"
stroke-width="2"
id="path1657"
sodipodi:type="arc"
sodipodi:cx="27.107115"
sodipodi:cy="22.407019"
sodipodi:rx="10.069912"
sodipodi:ry="10.084956"
sodipodi:start="1.0594149"
sodipodi:end="3.5325464"
sodipodi:arc-type="arc"
d="M 32.035153,31.201799 A 10.069912,10.084956 0 0 1 20.42801,29.95437 10.069912,10.084956 0 0 1 17.797018,18.563941"
sodipodi:open="true" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg6"
sodipodi:docname="groundunit-sam-launcher.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">
<path
fill="#3BB9FF"
stroke="none"
stroke-width="2"
d="M45.7733 41.3423L25.9481 7.63951C25.5228 6.91648 24.4772 6.91646 24.0519 7.63951L4.22671 41.3423C3.79536 42.0756 4.32409 43 5.17484 43H44.8252C45.6759 43 46.2046 42.0756 45.7733 41.3423Z"
id="path2" />
<path
d="M6.74842 41L25 9.97231L43.2516 41H6.74842Z"
fill="none"
stroke="#082E44"
stroke-width="2"
id="path4" />
<path
stroke="#082E44"
stroke-width="2"
id="path849"
sodipodi:type="arc"
sodipodi:cx="25.284166"
sodipodi:cy="43.365532"
sodipodi:rx="8.2439137"
sodipodi:ry="8.2651606"
sodipodi:start="3.8397244"
sodipodi:end="5.5850536"
sodipodi:arc-type="arc"
d="m 18.968962,38.052789 a 8.2439137,8.2651606 0 0 1 6.315204,-2.952418 8.2439137,8.2651606 0 0 1 6.315205,2.952418"
sodipodi:open="true" />
<path
stroke="#082E44"
stroke-width="1.5"
d="M 25.114189,34.887901 V 19.887378"
id="path1085" />
<path
stroke="#082E44"
stroke-width="1.5"
d="m 23.074458,23.244436 1.981982,-3.432894 1.923784,3.332092"
id="path1087" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg6"
sodipodi:docname="groundunit-sam-radar.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">
<path
d="M45.7733 41.3423L25.9481 7.63951C25.5228 6.91648 24.4772 6.91646 24.0519 7.63951L4.22671 41.3423C3.79536 42.0756 4.32409 43 5.17484 43H44.8252C45.6759 43 46.2046 42.0756 45.7733 41.3423Z"
fill="#3BB9FF"
stroke="none"
stroke-width="2"
id="path2" />
<path
d="M6.74842 41L25 9.97231L43.2516 41H6.74842Z"
fill="none"
stroke="#082E44"
stroke-width="2"
id="path4"
style="fill:none;fill-opacity:1" />
<path
style="fill:none;stroke:#082e44;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 23.148216,33.643859 5.192016,-2.949206 -3.388454,-2.048829 5.07282,-3.186251"
id="path942"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#082e44;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 27.294508,24.649847 2.900324,0.729775 -0.652429,2.591717"
id="path1230" />
<path
style="fill:none;fill-opacity:1;stroke:#082e44;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="path1657"
sodipodi:type="arc"
sodipodi:cx="27.684952"
sodipodi:cy="28.990719"
sodipodi:rx="8.8697948"
sodipodi:ry="8.8809643"
sodipodi:start="1.0594149"
sodipodi:end="3.5325464"
sodipodi:arc-type="arc"
sodipodi:open="true"
d="M 32.025673,36.735535 A 8.8697948,8.8809643 0 0 1 21.801853,35.63703 8.8697948,8.8809643 0 0 1 19.484418,25.606446" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,17 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle
fill="white"
stroke="none"
stroke-width="2"
id="path2358"
cx="25"
cy="25"
r="22" />
<circle
cx="25"
cy="25"
r="20"
stroke="#082e44"
stroke-width="2"
id="circle6" />
</svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@@ -65,18 +65,17 @@
--unit-width: 50px; --unit-width: 50px;
/*** Air units ***/ /*** Air units ***/
--unit-aircraft-ammo-gap: calc(2px + var(--unit-stroke-width)); --unit-ammo-gap: calc(2px + var(--unit-stroke-width));
--unit-aircraft-ammo-border-radius: 50%; --unit-ammo-border-radius: 50%;
--unit-aircraft-ammo-border-width: 2px; --unit-ammo-border-width: 2px;
--unit-aircraft-ammo-radius: 2px; --unit-ammo-radius: 2px;
--unit-aircraft-ammo-spacing: 2px; --unit-ammo-spacing: 2px;
--unit-aircraft-ammo-x: 0px; --unit-ammo-x: 0px;
--unit-aircraft-ammo-y: 30px; --unit-ammo-y: 30px;
--unit-aircraft-fuel-border-width: 2px; --unit-fuel-border-width: 2px;
--unit-aircraft-fuel-height: 6px; --unit-fuel-height: 6px;
--unit-aircraft-fuel-width: 36px; --unit-fuel-width: 36px;
--unit-aircraft-fuel-x: 0px; --unit-fuel-x: 0px;
--unit-aircraft-fuel-y: 22px; --unit-fuel-y: 22px;
--unit-aircraft-height: 28px; --unit-vvi-width: 4px;
--unit-aircraft-vvi-width: 4px;
} }

View File

@@ -17,7 +17,10 @@ interface CustomEventMap {
"groupCreation": CustomEvent<Unit[]>, "groupCreation": CustomEvent<Unit[]>,
"groupDeletion": CustomEvent<Unit[]>, "groupDeletion": CustomEvent<Unit[]>,
"mapStateChanged": CustomEvent<string>, "mapStateChanged": CustomEvent<string>,
"mapContextMenu": CustomEvent<> "mapContextMenu": CustomEvent<>,
"mapVisibilityOptionsChanged": CustomEvent<>,
"commandModeOptionsChanged": CustomEvent<>,
"contactsUpdated": CustomEvent<Unit>,
} }
declare global { declare global {

View File

@@ -1,16 +1,46 @@
interface UnitsData {
units: {[key: string]: UnitData},
sessionHash: string
}
interface AirbasesData { interface AirbasesData {
airbases: {[key: string]: any}, airbases: {[key: string]: any},
sessionHash: string;
time: number;
} }
interface BullseyesData { interface BullseyesData {
bullseyes: {[key: string]: {latitude: number, longitude: number, coalition: string}}, bullseyes: {[key: string]: {latitude: number, longitude: number, coalition: string}},
sessionHash: string;
time: number;
}
interface MissionData {
mission: {
theatre: string,
dateAndTime: DateAndTime;
commandModeOptions: CommandModeOptions;
}
time: number;
sessionHash: string;
}
interface CommandModeOptions {
commandMode: string;
restrictSpawns: boolean;
restrictToCoalition: boolean;
setupTime: number;
spawnPoints: {
red: number,
blue: number
},
eras: string[]
}
interface DateAndTime {
date: {Year: number, Month: number, Day: number};
time: {h: number, m: number, s: number};
elapsedTime: number;
startTime: number;
} }
interface LogData { interface LogData {
logs: {[key: string]: string}, logs: {[key: string]: string},
sessionHash: string;
time: number;
} }

View File

@@ -1,60 +1,24 @@
interface UpdateData { import { LatLng } from "leaflet"
[key: string]: any
interface UnitIconOptions {
showState: boolean,
showVvi: boolean,
showHotgroup: boolean,
showUnitIcon: boolean,
showShortLabel: boolean,
showFuel: boolean,
showAmmo: boolean,
showSummary: boolean,
showCallsign: boolean,
rotateToHeading: boolean
} }
interface BaseData { interface GeneralSettings {
controlled: boolean; prohibitJettison: boolean;
name: string; prohibitAA: boolean;
unitName: string; prohibitAG: boolean;
groupName: string; prohibitAfterburner: boolean;
alive: boolean; prohibitAirWpn: boolean;
category: string;
}
interface FlightData {
latitude: number;
longitude: number;
altitude: number;
heading: number;
speed: number;
}
interface MissionData {
fuel: number;
flags: any;
ammo: any;
contacts: any;
hasTask: boolean;
coalition: string;
}
interface FormationData {
leaderID: number;
}
interface TaskData {
currentState: string;
currentTask: string;
activePath: any;
desiredSpeed: number;
desiredSpeedType: string;
desiredAltitude: number;
desiredAltitudeType: string;
targetLocation: any;
isTanker: boolean;
isAWACS: boolean;
onOff: boolean;
followRoads: boolean;
targetID: number;
}
interface OptionsData {
ROE: string;
reactionToThreat: string;
emissionsCountermeasures: string;
TACAN: TACAN;
radio: Radio;
generalSettings: GeneralSettings;
} }
interface TACAN { interface TACAN {
@@ -70,31 +34,21 @@ interface Radio {
callsignNumber: number; callsignNumber: number;
} }
interface GeneralSettings { interface Ammo {
prohibitJettison: boolean; quantity: number,
prohibitAA: boolean; name: string,
prohibitAG: boolean; guidance: number,
prohibitAfterburner: boolean; category: number,
prohibitAirWpn: boolean; missileCategory: number
} }
interface UnitIconOptions { interface Contact {
showState: boolean, ID: number,
showVvi: boolean, detectionMethod: number
showHotgroup: boolean,
showUnitIcon: boolean,
showShortLabel: boolean,
showFuel: boolean,
showAmmo: boolean,
showSummary: boolean,
rotateToHeading: boolean
} }
interface UnitData { interface Offset {
baseData: BaseData; x: number,
flightData: FlightData; y: number,
missionData: MissionData; z: number
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
} }

View File

@@ -14,9 +14,12 @@ interface LoadoutBlueprint {
interface UnitBlueprint { interface UnitBlueprint {
name: string; name: string;
era?: string[]; coalition: string;
era: string;
label: string; label: string;
shortLabel: string; shortLabel: string;
loadouts: LoadoutBlueprint[]; type?: string;
filename: string; range?: string;
loadouts?: LoadoutBlueprint[];
filename?: string;
} }

View File

@@ -1,4 +1,5 @@
import { getMissionData } from ".."; import { getMissionHandler } from "..";
import { convertDateAndTimeToDate } from "../other/utils";
import { getConnected } from "../server/server"; import { getConnected } from "../server/server";
import { ATCBoard } from "./atcboard"; import { ATCBoard } from "./atcboard";
import { ATCBoardGround } from "./board/ground"; import { ATCBoardGround } from "./board/ground";
@@ -140,11 +141,10 @@ export class ATC {
} }
getMissionDateTime() : Date { getMissionDate() : Date {
return new Date( getMissionData().getNowDate() ); return convertDateAndTimeToDate(getMissionHandler().getDateAndTime());
} }
lookForBoards() { lookForBoards() {
document.querySelectorAll( ".ol-strip-board" ).forEach( board => { document.querySelectorAll( ".ol-strip-board" ).forEach( board => {

View File

@@ -2,7 +2,7 @@ import { Dropdown } from "../controls/dropdown";
import { zeroAppend } from "../other/utils"; import { zeroAppend } from "../other/utils";
import { ATC } from "./atc"; import { ATC } from "./atc";
import { Unit } from "../units/unit"; import { Unit } from "../units/unit";
import { getMissionData, getUnitsManager } from ".."; import { getMissionHandler, getUnitsManager } from "..";
import Sortable from "sortablejs"; import Sortable from "sortablejs";
import { FlightInterface } from "./atc"; import { FlightInterface } from "./atc";
import { getConnected } from "../server/server"; import { getConnected } from "../server/server";
@@ -115,11 +115,11 @@ export abstract class ATCBoard {
addFlight( unit:Unit ) { addFlight( unit:Unit ) {
const baseData = unit.getBaseData(); const baseData = unit.getData();
const unitCanBeAdded = () => { const unitCanBeAdded = () => {
if ( baseData.category !== "Aircraft" ) { if ( unit.getCategory() !== "Aircraft" ) {
return false; return false;
} }
@@ -345,7 +345,7 @@ export abstract class ATCBoard {
const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => { const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => {
const unit = units[ unitId ]; const unit = units[ unitId ];
const baseData = unit.getBaseData(); const baseData = unit.getData();
if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) { if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) {
acc.push( unit ); acc.push( unit );
@@ -359,7 +359,7 @@ export abstract class ATCBoard {
results.forEach( unit => { results.forEach( unit => {
const baseData = unit.getBaseData(); const baseData = unit.getData();
const a = document.createElement( "a" ); const a = document.createElement( "a" );
a.innerText = baseData.unitName; a.innerText = baseData.unitName;
@@ -442,7 +442,7 @@ export abstract class ATCBoard {
timeToGo( timestamp:number ) { timeToGo( timestamp:number ) {
const timeData = this.calculateTimeToGo( this.getATC().getMissionDateTime().getTime(), timestamp ); const timeData = this.calculateTimeToGo( this.getATC().getMissionDate().getTime(), timestamp );
return ( timestamp === -1 ) ? "-" : timeData.elapsedMarker + timeData.time; return ( timestamp === -1 ) ? "-" : timeData.elapsedMarker + timeData.time;
@@ -455,8 +455,8 @@ export abstract class ATCBoard {
updateClock() { updateClock() {
const missionTime = this.#atc.getMissionDateTime().getTime(); const missionTime = this.#atc.getMissionDate().getTime();
const timeDiff = new Date().getTime() - getMissionData().getUpdateTime(); const timeDiff = new Date().getTime() - getMissionHandler().getDateAndTime().elapsedTime;
const nowDate = new Date( missionTime + timeDiff ); const nowDate = new Date( missionTime + timeDiff );

View File

@@ -17,7 +17,7 @@ export class ATCBoardGround extends ATCBoard {
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) ); const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
const stripBoard = this.getStripBoardElement(); const stripBoard = this.getStripBoardElement();
const missionTime = this.getATC().getMissionDateTime().getTime(); const missionTime = this.getATC().getMissionDate().getTime();
for( const strip of stripBoard.children ) { for( const strip of stripBoard.children ) {
strip.toggleAttribute( "data-updating", true ); strip.toggleAttribute( "data-updating", true );

View File

@@ -17,7 +17,7 @@ export class ATCBoardTower extends ATCBoard {
update() { update() {
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) ); const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
const missionTime = this.getATC().getMissionDateTime().getTime(); const missionTime = this.getATC().getMissionDate().getTime();
const selectableUnits = getUnitsManager().getSelectableAircraft(); const selectableUnits = getUnitsManager().getSelectableAircraft();
const stripBoard = this.getStripBoardElement(); const stripBoard = this.getStripBoardElement();
@@ -34,13 +34,13 @@ export class ATCBoardTower extends ATCBoard {
return; return;
} }
const flightData:FlightData = { const flightData = {
latitude: -1, latitude: -1,
longitude: -1, longitude: -1,
altitude: -1, altitude: -1,
heading: -1, heading: -1,
speed: -1, speed: -1,
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getFlightData() : {} ) ...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} )
}; };
if ( !strip ) { if ( !strip ) {

View File

@@ -12,8 +12,8 @@ export class UnitDataTable extends Panel {
var units = getUnitsManager().getUnits(); var units = getUnitsManager().getUnits();
const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => { const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => {
const aVal = a.getBaseData().unitName?.toLowerCase(); const aVal = a.getUnitName()?.toLowerCase();
const bVal = b.getBaseData().unitName?.toLowerCase(); const bVal = b.getUnitName()?.toLowerCase();
if (aVal > bVal) { if (aVal > bVal) {
return 1; return 1;
@@ -48,7 +48,7 @@ export class UnitDataTable extends Panel {
for (const unit of unitsArray) { for (const unit of unitsArray) {
const dataset = [unit.getBaseData().unitName, unit.getBaseData().name, unit.getBaseData().category, (unit.getBaseData().controlled) ? "AI" : "Human"]; const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"];
addRow(el, dataset); addRow(el, dataset);
} }

View File

@@ -1,12 +1,43 @@
import { LatLng, LatLngBounds, TileLayer, tileLayer } from "leaflet"; import { LatLng, LatLngBounds } from "leaflet";
export const ROEs: string[] = ["Hold", "Return", "Designated", "Free"]; export const NONE = "None";
export const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"]; export const GAME_MASTER = "Game master";
export const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"]; export const BLUE_COMMANDER = "Blue commander";
export const RED_COMMANDER = "Red commander";
export const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"]; export const VISUAL = 1;
export const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"]; export const OPTIC = 2;
export const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"]; export const RADAR = 4;
export const IRST = 8;
export const RWR = 16;
export const DLINK = 32;
export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area"];
export const ROEs: string[] = ["free", "designated", "return", "hold"];
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
export const ROEDescriptions: string[] = [
"Free (Attack anyone)",
"Designated (Attack the designated target only)",
"",
"Return (Only fire if fired upon)",
"Hold (Never fire)"
];
export const reactionsToThreatDescriptions: string[] = [
"None (No reaction)",
"Manoeuvre (no countermeasures)",
"Passive (Countermeasures only, no manoeuvre)",
"Evade (Countermeasures and manoeuvers)"
];
export const emissionsCountermeasuresDescriptions: string[] = [
"Silent (Radar OFF, no ECM)",
"Attack (Radar only for targeting, ECM only if locked)",
"Defend (Radar for searching, ECM if locked)",
"Always on (Radar and ECM always on)"
];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 }; export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 }; export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
@@ -100,3 +131,64 @@ export const layers = {
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' 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'
} }
} }
/* Map constants */
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const BOMBING = "Bombing";
export const CARPET_BOMBING = "Carpet bombing";
export const FIRE_AT_AREA = "Fire at area";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];
export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05};
export const SHOW_CONTACT_LINES = "Show unit contact lines";
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_UNIT_PATHS = "Show unit paths";
export const SHOW_UNIT_TARGETS = "Show unit targets";
export enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
heading,
isTanker,
isAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
isLeader,
endOfData = 255
};

View File

@@ -1,5 +1,6 @@
import { getMap, getUnitsManager, setActiveCoalition } from ".."; import { getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
import { Airbase } from "../missionhandler/airbase"; import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "../constants/constants";
import { Airbase } from "../mission/airbase";
import { ContextMenu } from "./contextmenu"; import { ContextMenu } from "./contextmenu";
export class AirbaseContextMenu extends ContextMenu { export class AirbaseContextMenu extends ContextMenu {
@@ -24,7 +25,8 @@ export class AirbaseContextMenu extends ContextMenu {
this.setProperties(airbase.getProperties()); this.setProperties(airbase.getProperties());
this.setParkings(airbase.getParkings()); this.setParkings(airbase.getParkings());
this.setCoalition(airbase.getCoalition()); this.setCoalition(airbase.getCoalition());
this.enableLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft" && (getUnitsManager().getSelectedUnitsCoalition() === airbase.getCoalition() || airbase.getCoalition() === "neutral")) this.enableLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsTypes()[0]) && (getUnitsManager().getSelectedUnitsCoalition() === airbase.getCoalition() || airbase.getCoalition() === "neutral"))
this.enableSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
} }
setName(airbaseName: string) { setName(airbaseName: string) {
@@ -50,7 +52,11 @@ export class AirbaseContextMenu extends ContextMenu {
} }
setCoalition(coalition: string) { setCoalition(coalition: string) {
(<HTMLElement>this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.activeCoalition = coalition; (<HTMLElement>this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.coalition = coalition;
}
enableSpawnButton(enableSpawnButton: boolean) {
this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !enableSpawnButton);
} }
enableLandButton(enableLandButton: boolean) { enableLandButton(enableLandButton: boolean) {
@@ -60,8 +66,10 @@ export class AirbaseContextMenu extends ContextMenu {
showSpawnMenu() { showSpawnMenu() {
if (this.#airbase != null) { if (this.#airbase != null) {
setActiveCoalition(this.#airbase.getCoalition()); setActiveCoalition(this.#airbase.getCoalition());
getMap().showMapContextMenu({ originalEvent: { x: this.getX(), y: this.getY(), latlng: this.getLatLng() } }); getMap().showMapContextMenu(this.getX(), this.getY(), this.getLatLng());
getMap().getMapContextMenu().hideUpperBar(); getMap().getMapContextMenu().hideUpperBar();
getMap().getMapContextMenu().hideLowerBar();
getMap().getMapContextMenu().hideAltitudeSlider();
getMap().getMapContextMenu().showSubMenu("aircraft"); getMap().getMapContextMenu().showSubMenu("aircraft");
getMap().getMapContextMenu().setAirbaseName(this.#airbase.getName()); getMap().getMapContextMenu().setAirbaseName(this.#airbase.getName());
getMap().getMapContextMenu().setLatLng(this.#airbase.getLatLng()); getMap().getMapContextMenu().setLatLng(this.#airbase.getLatLng());

View File

@@ -0,0 +1,124 @@
import { LatLng } from "leaflet";
import { getMap, getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER, IADSTypes } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { Switch } from "./switch";
import { groundUnitDatabase } from "../units/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
export class CoalitionAreaContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#coalitionArea: CoalitionArea | null = null;
#iadsDensitySlider: Slider;
#iadsDistributionSlider: Slider;
#iadsTypesDropdown: Dropdown;
#iadsErasDropdown: Dropdown;
#iadsRangesDropdown: Dropdown;
constructor(id: string) {
super(id);
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
this.#iadsTypesDropdown = new Dropdown("iads-units-type-options", () => { });
this.#iadsErasDropdown = new Dropdown("iads-era-options", () => {});
this.#iadsRangesDropdown = new Dropdown("iads-range-options", () => {});
this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { });
this.#iadsDistributionSlider = new Slider("iads-distribution-slider", 5, 100, "%", (value: number) => { });
this.#iadsDensitySlider.setIncrement(5);
this.#iadsDensitySlider.setValue(50);
this.#iadsDensitySlider.setActive(true);
this.#iadsDistributionSlider.setIncrement(5);
this.#iadsDistributionSlider.setValue(50);
this.#iadsDistributionSlider.setActive(true);
document.addEventListener("coalitionAreaContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.showSubMenu(e.detail.type);
else
this.hideSubMenus();
});
document.addEventListener("coalitionAreaBringToBack", (e: any) => {
if (this.#coalitionArea)
getMap().bringCoalitionAreaToBack(this.#coalitionArea);
getMap().hideCoalitionAreaContextMenu();
});
document.addEventListener("coalitionAreaDelete", (e: any) => {
if (this.#coalitionArea)
getMap().deleteCoalitionArea(this.#coalitionArea);
getMap().hideCoalitionAreaContextMenu();
});
document.addEventListener("contextMenuCreateIads", (e: any) => {
const area = this.getCoalitionArea();
if (area)
getUnitsManager().createIADS(area, getCheckboxOptions(this.#iadsTypesDropdown), getCheckboxOptions(this.#iadsErasDropdown), getCheckboxOptions(this.#iadsRangesDropdown), this.#iadsDensitySlider.getValue(), this.#iadsDistributionSlider.getValue());
})
this.hide();
}
show(x: number, y: number, latlng: LatLng) {
super.show(x, y, latlng);
/* Create the checkboxes to select the unit roles */
this.#iadsTypesDropdown.setOptionsElements(IADSTypes.map((role: string) => {
return createCheckboxOption(role, `Add ${role}s to the IADS` );
}));
/* Create the checkboxes to select the unit periods */
this.#iadsErasDropdown.setOptionsElements(groundUnitDatabase.getEras().map((era: string) => {
return createCheckboxOption(era, `Add ${era} era units to the IADS`);
}));
/* Create the checkboxes to select the unit ranges */
this.#iadsRangesDropdown.setOptionsElements(groundUnitDatabase.getRanges().map((range: string) => {
return createCheckboxOption(range, `Add ${range} units to the IADS`);
}));
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
this.#coalitionSwitch.hide()
}
showSubMenu(type: string) {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
this.clip();
this.setVisibleSubMenu(type);
}
hideSubMenus() {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
this.clip();
this.setVisibleSubMenu(null);
}
getCoalitionArea() {
return this.#coalitionArea;
}
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
});
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "red");
}
#onSwitchClick(value: boolean) {
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
this.getCoalitionArea()?.setCoalition(value ? "red" : "blue");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
});
}
}
}

View File

@@ -5,6 +5,8 @@ export class ContextMenu {
#latlng: LatLng = new LatLng(0, 0); #latlng: LatLng = new LatLng(0, 0);
#x: number = 0; #x: number = 0;
#y: number = 0; #y: number = 0;
#visibleSubMenu: string | null = null;
#hidden: boolean = true;
constructor(id: string) { constructor(id: string) {
this.#container = document.getElementById(id); this.#container = document.getElementById(id);
@@ -17,10 +19,12 @@ export class ContextMenu {
this.#x = x; this.#x = x;
this.#y = y; this.#y = y;
this.clip(); this.clip();
this.#hidden = false;
} }
hide() { hide() {
this.#container?.classList.toggle("hide", true); this.#container?.classList.toggle("hide", true);
this.#hidden = true;
} }
getContainer() { getContainer() {
@@ -39,6 +43,10 @@ export class ContextMenu {
return this.#y; return this.#y;
} }
getHidden() {
return this.#hidden;
}
clip() { clip() {
if (this.#container != null) { if (this.#container != null) {
if (this.#x + this.#container.offsetWidth < window.innerWidth) if (this.#x + this.#container.offsetWidth < window.innerWidth)
@@ -52,4 +60,12 @@ export class ContextMenu {
this.#container.style.top = window.innerHeight - this.#container.offsetHeight - 10 + "px"; this.#container.style.top = window.innerHeight - this.#container.offsetHeight - 10 + "px";
} }
} }
setVisibleSubMenu(menu: string | null) {
this.#visibleSubMenu = menu;
}
getVisibleSubMenu() {
return this.#visibleSubMenu;
}
} }

View File

@@ -33,6 +33,10 @@ export class Dropdown {
setOptions(optionsList: string[], sortAlphabetically: boolean = true) { setOptions(optionsList: string[], sortAlphabetically: boolean = true) {
this.#optionsList = optionsList.sort(); this.#optionsList = optionsList.sort();
if (this.#optionsList.length == 0) {
optionsList = ["No options available"]
this.#value.innerText = "No options available";
}
this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => { this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => {
var div = document.createElement("div"); var div = document.createElement("div");
var button = document.createElement("button"); var button = document.createElement("button");
@@ -50,6 +54,15 @@ export class Dropdown {
})); }));
} }
setOptionsElements(optionsElements: HTMLElement[]) {
this.#optionsList = [];
this.#options.replaceChildren(...optionsElements);
}
getOptionElements() {
return this.#options.children;
}
selectText(text: string) { selectText(text: string) {
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text); const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
if (index > -1) { if (index > -1) {

View File

@@ -1,69 +1,153 @@
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { getActiveCoalition, getMap, setActiveCoalition } from ".."; import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
import { spawnAircraft, spawnExplosion, spawnGroundUnit, spawnSmoke } from "../server/server"; import { spawnExplosion, spawnSmoke } from "../server/server";
import { aircraftDatabase } from "../units/aircraftdatabase"; import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase"; import { groundUnitDatabase } from "../units/groundunitdatabase";
import { helicopterDatabase } from "../units/helicopterdatabase";
import { ContextMenu } from "./contextmenu"; import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown"; import { Dropdown } from "./dropdown";
import { Switch } from "./switch"; import { Switch } from "./switch";
import { Slider } from "./slider"; import { Slider } from "./slider";
import { ftToM } from "../other/utils"; import { ftToM } from "../other/utils";
import { GAME_MASTER } from "../constants/constants";
export interface SpawnOptions { import { navyUnitDatabase } from "../units/navyunitdatabase";
role: string; import { CoalitionArea } from "../map/coalitionarea";
type: string;
latlng: LatLng;
coalition: string;
loadout: string | null;
airbaseName: string | null;
altitude: number | null;
}
export class MapContextMenu extends ContextMenu { export class MapContextMenu extends ContextMenu {
#coalitionSwitch: Switch; #coalitionSwitch: Switch;
#aircraftRoleDropdown: Dropdown; #aircraftRoleDropdown: Dropdown;
#aircraftTypeDropdown: Dropdown; #aircraftLabelDropdown: Dropdown;
#aircraftCountDropdown: Dropdown;
#aircraftLoadoutDropdown: Dropdown; #aircraftLoadoutDropdown: Dropdown;
#aircrafSpawnAltitudeSlider: Slider; #aircraftSpawnAltitudeSlider: Slider;
#groundUnitRoleDropdown: Dropdown; #helicopterRoleDropdown: Dropdown;
#helicopterLabelDropdown: Dropdown;
#helicopterCountDropdown: Dropdown;
#helicopterLoadoutDropdown: Dropdown;
#helicopterSpawnAltitudeSlider: Slider;
#groundUnitTypeDropdown: Dropdown; #groundUnitTypeDropdown: Dropdown;
#spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) }; #groundUnitLabelDropdown: Dropdown;
#groundUnitCountDropdown: Dropdown;
#navyUnitTypeDropdown: Dropdown;
#navyUnitLabelDropdown: Dropdown;
#navyUnitCountDropdown: Dropdown;
#spawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", loadout: "", airbaseName: "", altitude: 0, count: 1 };
#coalitionArea: CoalitionArea | null = null;
constructor(id: string) { constructor(id: string) {
super(id); super(id);
this.#coalitionSwitch = new Switch("coalition-switch", this.#onSwitchClick); this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false); this.#coalitionSwitch.setValue(false);
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e)); this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e));
this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type));
this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
this.#aircrafSpawnAltitudeSlider = new Slider("aircraft-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
this.#aircrafSpawnAltitudeSlider.setIncrement(500);
this.#aircrafSpawnAltitudeSlider.setValue(20000);
this.#aircrafSpawnAltitudeSlider.setActive(true);
this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role));
this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type));
document.addEventListener("contextMenuShow", (e: any) => { /* Aircraft menu */
this.showSubMenu(e.detail.type); this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
this.#aircraftLabelDropdown = new Dropdown("aircraft-label-options", (type: string) => this.#setAircraftLabel(type));
this.#aircraftCountDropdown = new Dropdown("aircraft-count-options", (type: string) => this.#setAircraftCount(type));
this.#aircraftCountDropdown.setOptions(["1", "2", "3", "4"]);
this.#aircraftCountDropdown.setValue("1");
this.#aircraftLoadoutDropdown = new Dropdown("aircraft-loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
this.#aircraftSpawnAltitudeSlider = new Slider("aircraft-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
this.#aircraftSpawnAltitudeSlider.setIncrement(500);
this.#aircraftSpawnAltitudeSlider.setValue(20000);
this.#aircraftSpawnAltitudeSlider.setActive(true);
/* Helicopter menu */
this.#helicopterRoleDropdown = new Dropdown("helicopter-role-options", (role: string) => this.#setHelicopterRole(role));
this.#helicopterLabelDropdown = new Dropdown("helicopter-label-options", (type: string) => this.#setHelicopterLabel(type));
this.#helicopterCountDropdown = new Dropdown("helicopter-count-options", (type: string) => this.#setHelicopterCount(type));
this.#helicopterCountDropdown.setOptions(["1", "2", "3", "4"]);
this.#helicopterCountDropdown.setValue("1");
this.#helicopterLoadoutDropdown = new Dropdown("helicopter-loadout-options", (loadout: string) => this.#setHelicopterLoadout(loadout));
this.#helicopterSpawnAltitudeSlider = new Slider("helicopter-spawn-altitude-slider", 0, 10000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
this.#helicopterSpawnAltitudeSlider.setIncrement(50);
this.#helicopterSpawnAltitudeSlider.setValue(5000);
this.#helicopterSpawnAltitudeSlider.setActive(true);
var count = [];
for (let i = 1; i < 10; i++) count.push(String(i));
/* Ground unit menu */
this.#groundUnitTypeDropdown = new Dropdown("groundunit-type-options", (type: string) => this.#setGroundUnitType(type));
this.#groundUnitLabelDropdown = new Dropdown("groundunit-label-options", (name: string) => this.#setGroundUnitLabel(name));
this.#groundUnitCountDropdown = new Dropdown("groundunit-count-options", (count: string) => this.#setGroundUnitCount(count));
this.#groundUnitCountDropdown.setOptions(count);
this.#groundUnitCountDropdown.setValue("1");
/* Navy unit menu */
this.#navyUnitTypeDropdown = new Dropdown("navyunit-type-options", (type: string) => this.#setNavyUnitType(type));
this.#navyUnitLabelDropdown = new Dropdown("navyunit-label-options", (name: string) => this.#setNavyUnitLabel(name));
this.#navyUnitCountDropdown = new Dropdown("navyunit-count-options", (count: string) => this.#setNavyUnitCount(count));
this.#navyUnitCountDropdown.setOptions(count);
this.#navyUnitCountDropdown.setValue("1");
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.showSubMenu(e.detail.type);
else
this.hideSubMenus(e.detail.type);
}); });
document.addEventListener("contextMenuDeployAircraft", () => { document.addEventListener("contextMenuDeployAircrafts", () => {
this.hide();
this.#spawnOptions.coalition = getActiveCoalition(); this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) { if (this.#spawnOptions) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng); var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, altitude: this.#spawnOptions.altitude, loadout: this.#spawnOptions.loadout};
spawnAircraft(this.#spawnOptions); var units = [];
for (let i = 1; i < parseInt(this.#aircraftCountDropdown.getValue()) + 1; i++) {
units.push(unitTable);
}
if (getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, this.#spawnOptions.airbaseName)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
this.hide();
}
} }
}); });
document.addEventListener("contextMenuDeployGroundUnit", () => { document.addEventListener("contextMenuDeployHelicopters", () => {
this.hide();
this.#spawnOptions.coalition = getActiveCoalition(); this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) { if (this.#spawnOptions) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng); var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, altitude: this.#spawnOptions.altitude, loadout: this.#spawnOptions.loadout};
spawnGroundUnit(this.#spawnOptions); var units = [];
for (let i = 1; i < parseInt(this.#helicopterCountDropdown.getValue()) + 1; i++) {
units.push(unitTable);
}
if (getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, this.#spawnOptions.airbaseName)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
this.hide();
}
}
});
document.addEventListener("contextMenuDeployGroundUnits", () => {
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng};
var units = [];
for (let i = 1; i < parseInt(this.#groundUnitCountDropdown.getValue()) + 1; i++) {
units.push(JSON.parse(JSON.stringify(unitTable)));
unitTable.location.lat += 0.0001;
}
if (getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
this.hide();
}
}
});
document.addEventListener("contextMenuDeployNavyUnits", () => {
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng};
var units = [];
for (let i = 1; i < parseInt(this.#navyUnitCountDropdown.getValue()) + 1; i++) {
units.push(JSON.parse(JSON.stringify(unitTable)));
unitTable.location.lat += 0.0001;
}
if (getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
this.hide();
}
} }
}); });
@@ -77,40 +161,140 @@ export class MapContextMenu extends ContextMenu {
spawnExplosion(e.detail.strength, this.getLatLng()); spawnExplosion(e.detail.strength, this.getLatLng());
}); });
document.addEventListener("editCoalitionArea", (e: any) => {
this.hide();
if (this.#coalitionArea) {
getMap().deselectAllCoalitionAreas();
this.#coalitionArea.setSelected(true);
}
});
document.addEventListener("commandModeOptionsChanged", (e: any) => {
this.#refreshOptions();
});
this.hide(); this.hide();
} }
show(x: number, y: number, latlng: LatLng) { show(x: number, y: number, latlng: LatLng) {
this.#spawnOptions.airbaseName = null;
super.show(x, y, latlng); super.show(x, y, latlng);
this.#spawnOptions.latlng = latlng;
this.showUpperBar(); this.showUpperBar();
this.showAltitudeSlider();
this.#spawnOptions.airbaseName = "";
this.#spawnOptions.latlng = latlng;
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
if (getActiveCoalition() == "blue")
this.#coalitionSwitch.setValue(false);
else if (getActiveCoalition() == "red")
this.#coalitionSwitch.setValue(true);
else
this.#coalitionSwitch.setValue(undefined);
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
this.#coalitionSwitch.hide()
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true);
} }
showSubMenu(type: string) { showSubMenu(type: string) {
if (type === "more")
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide");
else if (["aircraft", "groundunit"].includes(type))
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft"); this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft"); this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", type !== "ground-unit"); this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
this.getContainer()?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", type === "ground-unit"); this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit");
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit");
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit");
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit");
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke"); this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke"); this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion"); this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion"); this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion");
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => {(element as HTMLButtonElement).disabled = true;})
this.#resetAircraftRole(); this.#resetAircraftRole();
this.#resetAircraftType(); this.#resetAircraftLabel();
this.#resetGroundUnitRole(); this.#resetHelicopterRole();
this.#resetHelicopterLabel();
this.#resetGroundUnitType(); this.#resetGroundUnitType();
this.#resetGroundUnitLabel();
this.#resetNavyUnitType();
this.#resetNavyUnitLabel();
this.#aircraftCountDropdown.setValue("1");
this.#helicopterCountDropdown.setValue("1");
this.#groundUnitCountDropdown.setValue("1");
this.clip(); this.clip();
if (type === "aircraft") {
this.#spawnOptions.altitude = ftToM(this.#aircraftSpawnAltitudeSlider.getValue());
}
else if (type === "helicopter") {
this.#spawnOptions.altitude = ftToM(this.#helicopterSpawnAltitudeSlider.getValue());
}
this.setVisibleSubMenu(type);
}
hideSubMenus(type: string) {
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "groundunit"].includes(type));
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false);
this.#resetAircraftRole();
this.#resetAircraftLabel();
this.#resetHelicopterRole();
this.#resetHelicopterLabel();
this.#resetHelicopterRole();
this.#resetHelicopterLabel();
this.#resetGroundUnitType();
this.#resetGroundUnitLabel();
this.#resetNavyUnitType();
this.#resetNavyUnitLabel();
this.clip();
this.setVisibleSubMenu(null);
} }
showUpperBar() { showUpperBar() {
this.getContainer()?.querySelector("#upper-bar")?.classList.toggle("hide", false); this.getContainer()?.querySelector(".upper-bar")?.classList.toggle("hide", false);
} }
hideUpperBar() { hideUpperBar() {
this.getContainer()?.querySelector("#upper-bar")?.classList.toggle("hide", true); this.getContainer()?.querySelector(".upper-bar")?.classList.toggle("hide", true);
}
showLowerBar() {
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", false);
}
hideLowerBar() {
this.getContainer()?.querySelector("#more-optionsbutton-bar")?.classList.toggle("hide", true);
}
showAltitudeSlider() {
this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", false);
}
hideAltitudeSlider() {
this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", true);
} }
setAirbaseName(airbaseName: string) { setAirbaseName(airbaseName: string) {
@@ -121,63 +305,100 @@ export class MapContextMenu extends ContextMenu {
this.#spawnOptions.latlng = latlng; this.#spawnOptions.latlng = latlng;
} }
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false);
}
#onSwitchClick(value: boolean) { #onSwitchClick(value: boolean) {
value? setActiveCoalition("red"): setActiveCoalition("blue"); value? setActiveCoalition("red"): setActiveCoalition("blue");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
} }
#onSwitchRightClick(e: any) { #onSwitchRightClick(e: any) {
this.#coalitionSwitch.setValue(undefined); this.#coalitionSwitch.setValue(undefined);
setActiveCoalition("neutral"); setActiveCoalition("neutral");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
}
#refreshOptions() {
if (!aircraftDatabase.getRoles().includes(this.#aircraftRoleDropdown.getValue()))
this.#resetAircraftRole();
if (!aircraftDatabase.getByRole(this.#aircraftRoleDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#aircraftLabelDropdown.getValue()))
this.#resetAircraftLabel();
if (!helicopterDatabase.getRoles().includes(this.#helicopterRoleDropdown.getValue()))
this.#resetHelicopterRole();
if (!helicopterDatabase.getByRole(this.#helicopterRoleDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#helicopterLabelDropdown.getValue()))
this.#resetHelicopterLabel();
if (!groundUnitDatabase.getRoles().includes(this.#groundUnitTypeDropdown.getValue()))
this.#resetGroundUnitType();
if (!groundUnitDatabase.getByType(this.#groundUnitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#groundUnitLabelDropdown.getValue()))
this.#resetGroundUnitLabel();
if (!navyUnitDatabase.getRoles().includes(this.#navyUnitTypeDropdown.getValue()))
this.#resetNavyUnitType();
if (!navyUnitDatabase.getByType(this.#navyUnitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#aircraftLabelDropdown.getValue()))
this.#resetNavyUnitLabel();
} }
/********* Aircraft spawn menu *********/ /********* Aircraft spawn menu *********/
#setAircraftRole(role: string) { #setAircraftRole(role: string) {
this.#spawnOptions.role = role; this.#spawnOptions.role = role;
this.#resetAircraftType(); this.#resetAircraftLabel();
this.#aircraftTypeDropdown.setOptions(aircraftDatabase.getByRole(role).map((blueprint) => { return blueprint.label })); this.#aircraftLabelDropdown.setOptions(aircraftDatabase.getByRole(role).map((blueprint) => { return blueprint.label }));
this.#aircraftTypeDropdown.selectValue(0); this.#aircraftLabelDropdown.selectValue(0);
this.clip(); this.clip();
this.#computeSpawnPoints();
} }
#resetAircraftRole() { #resetAircraftRole() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; (<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
(<HTMLButtonElement>this.getContainer()?.querySelector("#loadout-list")).replaceChildren(); (<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren();
this.#aircraftRoleDropdown.reset(); this.#aircraftRoleDropdown.reset();
this.#aircraftTypeDropdown.reset(); this.#aircraftLabelDropdown.reset();
this.#aircraftRoleDropdown.setOptions(aircraftDatabase.getRoles()); this.#aircraftRoleDropdown.setOptions(aircraftDatabase.getRoles());
this.clip(); this.clip();
} }
#setAircraftType(label: string) { #setAircraftLabel(label: string) {
this.#resetAircraftType(); this.#resetAircraftLabel();
var type = aircraftDatabase.getByLabel(label)?.name || null; var name = aircraftDatabase.getByLabel(label)?.name || null;
if (type != null) { if (name != null) {
this.#spawnOptions.type = type; this.#spawnOptions.name = name;
this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role)); this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(name, this.#spawnOptions.role));
this.#aircraftLoadoutDropdown.selectValue(0); this.#aircraftLoadoutDropdown.selectValue(0);
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#unit-image")); var image = (<HTMLImageElement>this.getContainer()?.querySelector("#aircraft-unit-image"));
image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`; image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`;
image.classList.toggle("hide", false); image.classList.toggle("hide", false);
} }
this.clip(); this.clip();
this.#computeSpawnPoints();
} }
#resetAircraftType() { #resetAircraftLabel() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; (<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
(<HTMLButtonElement>this.getContainer()?.querySelector("#loadout-list")).replaceChildren(); (<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren();
this.#aircraftLoadoutDropdown.reset(); this.#aircraftLoadoutDropdown.reset();
(<HTMLImageElement>this.getContainer()?.querySelector("#unit-image")).classList.toggle("hide", true); (<HTMLImageElement>this.getContainer()?.querySelector("#aircraft-unit-image")).classList.toggle("hide", true);
this.clip(); this.clip();
} }
#setAircraftCount(count: string) {
this.#spawnOptions.count = parseInt(count);
this.clip();
this.#computeSpawnPoints();
}
#setAircraftLoadout(loadoutName: string) { #setAircraftLoadout(loadoutName: string) {
var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.type, loadoutName); var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
if (loadout) { if (loadout) {
this.#spawnOptions.loadout = loadout.code; this.#spawnOptions.loadout = loadout.code;
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; (<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
var items = loadout.items.map((item: any) => { return `${item.quantity}x ${item.name}`; }); var items = loadout.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
items.length == 0 ? items.push("Empty loadout") : ""; items.length == 0 ? items.push("Empty loadout") : "";
(<HTMLButtonElement>this.getContainer()?.querySelector("#loadout-list")).replaceChildren( (<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren(
...items.map((item: any) => { ...items.map((item: any) => {
var div = document.createElement('div'); var div = document.createElement('div');
div.innerText = item; div.innerText = item;
@@ -188,40 +409,179 @@ export class MapContextMenu extends ContextMenu {
this.clip(); this.clip();
} }
/********* Ground unit spawn menu *********/ /********* Helicopter spawn menu *********/
#setGroundUnitRole(role: string) { #setHelicopterRole(role: string) {
this.#spawnOptions.role = role; this.#spawnOptions.role = role;
this.#resetGroundUnitType(); this.#resetHelicopterLabel();
this.#helicopterLabelDropdown.setOptions(helicopterDatabase.getByRole(role).map((blueprint) => { return blueprint.label }));
this.#helicopterLabelDropdown.selectValue(0);
this.clip();
this.#computeSpawnPoints();
}
const types = groundUnitsDatabase.getByRole(role).map((blueprint) => { return blueprint.label }); #resetHelicopterRole() {
this.#groundUnitTypeDropdown.setOptions(types); (<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.#groundUnitTypeDropdown.selectValue(0); (<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren();
this.#helicopterRoleDropdown.reset();
this.#helicopterLabelDropdown.reset();
this.#helicopterRoleDropdown.setOptions(helicopterDatabase.getRoles());
this.clip(); this.clip();
} }
#resetGroundUnitRole() { #setHelicopterLabel(label: string) {
(<HTMLButtonElement>this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; this.#resetHelicopterLabel();
(<HTMLButtonElement>this.getContainer()?.querySelector("#loadout-list")).replaceChildren(); var name = helicopterDatabase.getByLabel(label)?.name || null;
this.#groundUnitRoleDropdown.reset(); if (name != null) {
this.#groundUnitTypeDropdown.reset(); this.#spawnOptions.name = name;
this.#helicopterLoadoutDropdown.setOptions(helicopterDatabase.getLoadoutNamesByRole(name, this.#spawnOptions.role));
this.#helicopterLoadoutDropdown.selectValue(0);
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#helicopter-unit-image"));
image.src = `images/units/${helicopterDatabase.getByLabel(label)?.filename}`;
image.classList.toggle("hide", false);
}
this.clip();
this.#computeSpawnPoints();
}
const roles = groundUnitsDatabase.getRoles(); #resetHelicopterLabel() {
this.#groundUnitRoleDropdown.setOptions(roles); (<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren();
this.#helicopterLoadoutDropdown.reset();
(<HTMLImageElement>this.getContainer()?.querySelector("#helicopter-unit-image")).classList.toggle("hide", true);
this.clip(); this.clip();
} }
#setGroundUnitType(label: string) { #setHelicopterCount(count: string) {
this.#resetGroundUnitType(); this.#spawnOptions.count = parseInt(count);
var type = groundUnitsDatabase.getByLabel(label)?.name || null; this.clip();
if (type != null) { this.#computeSpawnPoints();
this.#spawnOptions.type = type; }
(<HTMLButtonElement>this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
#setHelicopterLoadout(loadoutName: string) {
var loadout = helicopterDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
if (loadout) {
this.#spawnOptions.loadout = loadout.code;
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
var items = loadout.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
items.length == 0 ? items.push("Empty loadout") : "";
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren(
...items.map((item: any) => {
var div = document.createElement('div');
div.innerText = item;
return div;
})
)
} }
this.clip(); this.clip();
} }
/********* Groundunit spawn menu *********/
#setGroundUnitType(role: string) {
this.#resetGroundUnitLabel();
const types = groundUnitDatabase.getByType(role).map((blueprint) => { return blueprint.label });
this.#groundUnitLabelDropdown.setOptions(types);
this.#groundUnitLabelDropdown.selectValue(0);
this.clip();
this.#computeSpawnPoints();
}
#resetGroundUnitType() { #resetGroundUnitType() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; (<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.#groundUnitTypeDropdown.reset();
this.#groundUnitLabelDropdown.reset();
const types = groundUnitDatabase.getTypes();
this.#groundUnitTypeDropdown.setOptions(types);
this.clip(); this.clip();
} }
#setGroundUnitLabel(label: string) {
this.#resetGroundUnitLabel();
var type = groundUnitDatabase.getByLabel(label)?.name || null;
if (type != null) {
this.#spawnOptions.name = type;
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
}
this.clip();
this.#computeSpawnPoints();
}
#resetGroundUnitLabel() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.clip();
}
#setGroundUnitCount(count: string) {
this.#spawnOptions.count = parseInt(count);
this.clip();
this.#computeSpawnPoints();
}
/********* Navyunit spawn menu *********/
#setNavyUnitType(role: string) {
this.#resetNavyUnitLabel();
const types = navyUnitDatabase.getByType(role).map((blueprint) => { return blueprint.label });
this.#navyUnitLabelDropdown.setOptions(types);
this.#navyUnitLabelDropdown.selectValue(0);
this.clip();
this.#computeSpawnPoints();
}
#resetNavyUnitType() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.#navyUnitTypeDropdown.reset();
this.#navyUnitLabelDropdown.reset();
const types = navyUnitDatabase.getTypes();
this.#navyUnitTypeDropdown.setOptions(types);
this.clip();
}
#setNavyUnitLabel(label: string) {
this.#resetNavyUnitLabel();
var type = navyUnitDatabase.getByLabel(label)?.name || null;
if (type != null) {
this.#spawnOptions.name = type;
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
}
this.clip();
this.#computeSpawnPoints();
}
#resetNavyUnitLabel() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.clip();
}
#setNavyUnitCount(count: string) {
this.#spawnOptions.count = parseInt(count);
this.clip();
this.#computeSpawnPoints();
}
#computeSpawnPoints() {
if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER){
var aircraftCount = parseInt(this.#aircraftCountDropdown.getValue());
var aircraftSpawnPoints = aircraftCount * aircraftDatabase.getSpawnPointsByLabel(this.#aircraftLabelDropdown.getValue());
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${aircraftSpawnPoints}`;
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = aircraftSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
var helicopterCount = parseInt(this.#helicopterCountDropdown.getValue());
var helicopterSpawnPoints = helicopterCount * helicopterDatabase.getSpawnPointsByLabel(this.#helicopterLabelDropdown.getValue());
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${helicopterSpawnPoints}`;
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = helicopterSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
var groundUnitCount = parseInt(this.#groundUnitCountDropdown.getValue());
var groundUnitSpawnPoints = groundUnitCount * groundUnitDatabase.getSpawnPointsByLabel(this.#groundUnitLabelDropdown.getValue());
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${groundUnitSpawnPoints}`;
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = groundUnitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
var navyUnitCount = parseInt(this.#navyUnitCountDropdown.getValue());
var navyUnitSpawnPoints = navyUnitCount * navyUnitDatabase.getSpawnPointsByLabel(this.#navyUnitLabelDropdown.getValue());
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${navyUnitSpawnPoints}`;
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = navyUnitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
}
}
} }

View File

@@ -2,7 +2,7 @@ import { Map } from "./map/map"
import { UnitsManager } from "./units/unitsmanager"; import { UnitsManager } from "./units/unitsmanager";
import { UnitInfoPanel } from "./panels/unitinfopanel"; import { UnitInfoPanel } from "./panels/unitinfopanel";
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
import { MissionHandler } from "./missionhandler/missionhandler"; import { MissionHandler } from "./mission/missionhandler";
import { UnitControlPanel } from "./panels/unitcontrolpanel"; import { UnitControlPanel } from "./panels/unitcontrolpanel";
import { MouseInfoPanel } from "./panels/mouseinfopanel"; import { MouseInfoPanel } from "./panels/mouseinfopanel";
import { AIC } from "./aic/aic"; import { AIC } from "./aic/aic";
@@ -16,6 +16,8 @@ import { Popup } from "./popups/popup";
import { Dropdown } from "./controls/dropdown"; import { Dropdown } from "./controls/dropdown";
import { HotgroupPanel } from "./panels/hotgrouppanel"; import { HotgroupPanel } from "./panels/hotgrouppanel";
import { SVGInjector } from "@tanem/svg-injector"; import { SVGInjector } from "@tanem/svg-injector";
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants";
import { ServerStatusPanel } from "./panels/serverstatuspanel";
var map: Map; var map: Map;
@@ -27,6 +29,7 @@ var atc: ATC;
var unitInfoPanel: UnitInfoPanel; var unitInfoPanel: UnitInfoPanel;
var connectionStatusPanel: ConnectionStatusPanel; var connectionStatusPanel: ConnectionStatusPanel;
var serverStatusPanel: ServerStatusPanel;
var unitControlPanel: UnitControlPanel; var unitControlPanel: UnitControlPanel;
var mouseInfoPanel: MouseInfoPanel; var mouseInfoPanel: MouseInfoPanel;
var logPanel: LogPanel; var logPanel: LogPanel;
@@ -44,17 +47,18 @@ function setup() {
featureSwitches = new FeatureSwitches(); featureSwitches = new FeatureSwitches();
/* Initialize base functionalitites */ /* Initialize base functionalitites */
map = new Map('map-container');
unitsManager = new UnitsManager(); unitsManager = new UnitsManager();
map = new Map('map-container');
missionHandler = new MissionHandler(); missionHandler = new MissionHandler();
/* Panels */ /* Panels */
unitInfoPanel = new UnitInfoPanel("unit-info-panel"); unitInfoPanel = new UnitInfoPanel("unit-info-panel");
unitControlPanel = new UnitControlPanel("unit-control-panel"); unitControlPanel = new UnitControlPanel("unit-control-panel");
connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel"); connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel");
serverStatusPanel = new ServerStatusPanel("server-status-panel");
mouseInfoPanel = new MouseInfoPanel("mouse-info-panel"); mouseInfoPanel = new MouseInfoPanel("mouse-info-panel");
hotgroupPanel = new HotgroupPanel("hotgroup-panel"); hotgroupPanel = new HotgroupPanel("hotgroup-panel");
//logPanel = new LogPanel("log-panel"); logPanel = new LogPanel("log-panel");
/* Popups */ /* Popups */
infoPopup = new Popup("info-popup"); infoPopup = new Popup("info-popup");
@@ -184,12 +188,12 @@ function setupEvents() {
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form"); const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
const username = (<HTMLInputElement>(form?.querySelector("#username"))).value; const username = (<HTMLInputElement>(form?.querySelector("#username"))).value;
const password = (<HTMLInputElement>(form?.querySelector("#password"))).value; const password = (<HTMLInputElement>(form?.querySelector("#password"))).value;
setCredentials(username, btoa("admin" + ":" + password)); setCredentials(username, password);
/* Start periodically requesting updates */ /* Start periodically requesting updates */
startUpdate(); startUpdate();
setConnectionStatus("connecting"); setLoginStatus("connecting");
}) })
document.addEventListener("reloadPage", () => { document.addEventListener("reloadPage", () => {
@@ -204,17 +208,12 @@ function setupEvents() {
else else
img.onload = () => SVGInjector(img); img.onload = () => SVGInjector(img);
}) })
} }
export function getMap() { export function getMap() {
return map; return map;
} }
export function getMissionData() {
return missionHandler;
}
export function getUnitDataTable() { export function getUnitDataTable() {
return unitDataTable; return unitDataTable;
} }
@@ -223,6 +222,10 @@ export function getUnitsManager() {
return unitsManager; return unitsManager;
} }
export function getMissionHandler() {
return missionHandler;
}
export function getUnitInfoPanel() { export function getUnitInfoPanel() {
return unitInfoPanel; return unitInfoPanel;
} }
@@ -243,21 +246,34 @@ export function getConnectionStatusPanel() {
return connectionStatusPanel; return connectionStatusPanel;
} }
export function getServerStatusPanel() {
return serverStatusPanel;
}
export function getHotgroupPanel() { export function getHotgroupPanel() {
return hotgroupPanel; return hotgroupPanel;
} }
export function setActiveCoalition(newActiveCoalition: string) { export function setActiveCoalition(newActiveCoalition: string) {
activeCoalition = newActiveCoalition; if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER)
document.querySelectorAll('[data-active-coalition]').forEach((element: any) => { element.setAttribute("data-active-coalition", activeCoalition) }); activeCoalition = newActiveCoalition;
} }
export function getActiveCoalition() { export function getActiveCoalition() {
return activeCoalition; if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER)
return activeCoalition;
else {
if (getMissionHandler().getCommandModeOptions().commandMode == BLUE_COMMANDER)
return "blue";
else if (getMissionHandler().getCommandModeOptions().commandMode == RED_COMMANDER)
return "red";
else
return "neutral";
}
} }
export function setConnectionStatus(status: string) { export function setLoginStatus(status: string) {
const el = document.querySelector("#connection-status") as HTMLElement; const el = document.querySelector("#login-status") as HTMLElement;
if (el) if (el)
el.dataset["status"] = status; el.dataset["status"] = status;
} }
@@ -267,3 +283,4 @@ export function getInfoPopup() {
} }
window.onload = setup; window.onload = setup;

View File

@@ -46,7 +46,7 @@ export var BoxSelect = Handler.extend({
_onMouseDown: function (e: any) { _onMouseDown: function (e: any) {
if ((e.which == 1 && e.button == 0 && e.shiftKey)) { if ((e.which == 1 && e.button == 0 && e.shiftKey)) {
this._map.fire('selectionstart');
// Clear the deferred resetState if it hasn't executed yet, otherwise it // Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container. // will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState(); this._clearDeferredResetState();

View File

@@ -0,0 +1,166 @@
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
import { getMap, getMissionHandler, getUnitsManager } from "..";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
import { BLUE_COMMANDER, RED_COMMANDER } from "../constants/constants";
export class CoalitionArea extends Polygon {
#coalition: string = "blue";
#selected: boolean = true;
#editing: boolean = true;
#handles: CoalitionAreaHandle[] = [];
#middleHandles: CoalitionAreaMiddleHandle[] = [];
#activeIndex: number = 0;
constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) {
if (options === undefined)
options = {};
options.bubblingMouseEvents = false;
options.interactive = false;
super(latlngs, options);
this.#setColors();
this.#registerCallbacks();
if ([BLUE_COMMANDER, RED_COMMANDER].includes(getMissionHandler().getCommandModeOptions().commandMode))
this.setCoalition(getMissionHandler().getCommandedCoalition());
}
setCoalition(coalition: string) {
this.#coalition = coalition;
this.#setColors();
}
getCoalition() {
return this.#coalition;
}
setSelected(selected: boolean) {
this.#selected = selected;
this.#setColors();
this.#setHandles();
this.setOpacity(selected? 1: 0.5);
if (!this.getSelected() && this.getEditing()) {
/* Remove the vertex we were working on */
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 1);
this.setLatLngs(latlngs);
this.setEditing(false);
}
}
getSelected() {
return this.#selected;
}
setEditing(editing: boolean) {
this.#editing = editing;
this.#setHandles();
var latlngs = this.getLatLngs()[0] as LatLng[];
/* Remove areas with less than 2 vertexes */
if (latlngs.length <= 2)
getMap().deleteCoalitionArea(this);
}
getEditing() {
return this.#editing;
}
addTemporaryLatLng(latlng: LatLng) {
this.#activeIndex++;
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 0, latlng);
this.setLatLngs(latlngs);
this.#setHandles();
}
moveActiveVertex(latlng: LatLng) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[this.#activeIndex] = latlng;
this.setLatLngs(latlngs);
this.#setHandles();
}
setOpacity(opacity: number) {
this.setStyle({opacity: opacity, fillOpacity: opacity * 0.25});
}
onRemove(map: Map): this {
super.onRemove(map);
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getMap()));
return this;
}
#setColors() {
const coalitionColor = this.getCoalition() === "blue" ? "#247be2" : "#ff5858";
this.setStyle({ color: this.getSelected() ? "white" : coalitionColor, fillColor: coalitionColor });
}
#setHandles() {
this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getMap()));
this.#handles = [];
if (this.getSelected()) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.forEach((latlng: LatLng, idx: number) => {
/* Add the polygon vertex handle (for moving the vertex) */
const handle = new CoalitionAreaHandle(latlng);
handle.addTo(getMap());
handle.on("drag", (e: any) => {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[idx] = e.target.getLatLng();
this.setLatLngs(latlngs);
this.#setMiddleHandles();
});
this.#handles.push(handle);
});
}
this.#setMiddleHandles();
}
#setMiddleHandles() {
this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getMap()));
this.#middleHandles = [];
var latlngs = this.getLatLngs()[0] as LatLng[];
if (this.getSelected() && latlngs.length >= 2) {
var lastLatLng: LatLng | null = null;
latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => {
/* Add the polygon middle point handle (for adding new vertexes) */
if (lastLatLng != null) {
const handle1Point = getMap().latLngToLayerPoint(latlng);
const handle2Point = getMap().latLngToLayerPoint(lastLatLng);
const middlePoint = new Point((handle1Point.x + handle2Point.x) / 2, (handle1Point.y + handle2Point.y) / 2);
const middleLatLng = getMap().layerPointToLatLng(middlePoint);
const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng);
middleHandle.addTo(getMap());
middleHandle.on("click", (e: any) => {
this.#activeIndex = idx - 1;
this.addTemporaryLatLng(middleLatLng);
});
this.#middleHandles.push(middleHandle);
}
lastLatLng = latlng;
});
}
}
#registerCallbacks() {
this.on("click", (e: any) => {
getMap().deselectAllCoalitionAreas();
if (!this.getSelected()) {
this.setSelected(true);
}
});
this.on("contextmenu", (e: any) => {
if (!this.getEditing()) {
getMap().deselectAllCoalitionAreas();
this.setSelected(true);
}
else
this.setEditing(false);
});
}
}

View File

@@ -0,0 +1,19 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
export class CoalitionAreaHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: true});
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [24, 24],
iconAnchor: [12, 12],
className: "leaflet-coalitionarea-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -0,0 +1,19 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
export class CoalitionAreaMiddleHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: false});
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [16, 16],
iconAnchor: [8, 8],
className: "leaflet-coalitionarea-middle-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-middle-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,7 +1,12 @@
import { DivIcon } from "leaflet"; import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker"; import { CustomMarker } from "./custommarker";
export class DestinationPreviewMarker extends CustomMarker { export class DestinationPreviewMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
createIcon() { createIcon() {
this.setIcon(new DivIcon({ this.setIcon(new DivIcon({
iconSize: [52, 52], iconSize: [52, 52],

View File

@@ -0,0 +1,20 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
export class DrawingCursor extends CustomMarker {
constructor() {
super(new LatLng(0, 0), {interactive: false})
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [24, 24],
iconAnchor: [0, 24],
className: "leaflet-draw-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-draw-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,33 +1,28 @@
import * as L from "leaflet" import * as L from "leaflet"
import { getUnitsManager } from ".."; import { getUnitsManager } from "..";
import { BoxSelect } from "./boxselect"; import { BoxSelect } from "./boxselect";
import { MapContextMenu, SpawnOptions } from "../controls/mapcontextmenu"; import { MapContextMenu } from "../controls/mapcontextmenu";
import { UnitContextMenu } from "../controls/unitcontextmenu"; import { UnitContextMenu } from "../controls/unitcontextmenu";
import { AirbaseContextMenu } from "../controls/airbasecontextmenu"; import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
import { Dropdown } from "../controls/dropdown"; import { Dropdown } from "../controls/dropdown";
import { Airbase } from "../missionhandler/airbase"; import { Airbase } from "../mission/airbase";
import { Unit } from "../units/unit"; import { Unit } from "../units/unit";
import { bearing } from "../other/utils"; import { bearing, createCheckboxOption } from "../other/utils";
import { DestinationPreviewMarker } from "./destinationpreviewmarker"; import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker"; import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap"; import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector' import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants"; import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS } from "../constants/constants";
import { TargetMarker } from "./targetmarker"; import { TargetMarker } from "./targetmarker";
import { CoalitionArea } from "./coalitionarea";
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
import { DrawingCursor } from "./drawingcursor";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
// TODO would be nice to convert to ts // TODO would be nice to convert to ts
require("../../public/javascripts/leaflet.nauticscale.js") require("../../public/javascripts/leaflet.nauticscale.js")
require("../../public/javascripts/L.Path.Drag.js")
/* Map constants */
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const BOMBING = "Bombing";
export const CARPET_BOMBING = "Carpet bombing";
export const FIRE_AT_AREA = "Fire at area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export class Map extends L.Map { export class Map extends L.Map {
#ID: string; #ID: string;
@@ -42,28 +37,38 @@ export class Map extends L.Map {
#panUp: boolean = false; #panUp: boolean = false;
#panDown: boolean = false; #panDown: boolean = false;
#lastMousePosition: L.Point = new L.Point(0, 0); #lastMousePosition: L.Point = new L.Point(0, 0);
#shiftKey: boolean = false;
#ctrlKey: boolean = false;
#centerUnit: Unit | null = null; #centerUnit: Unit | null = null;
#miniMap: ClickableMiniMap | null = null; #miniMap: ClickableMiniMap | null = null;
#miniMapLayerGroup: L.LayerGroup; #miniMapLayerGroup: L.LayerGroup;
#temporaryMarkers: TemporaryUnitMarker[] = []; #temporaryMarkers: TemporaryUnitMarker[] = [];
#destinationPreviewMarkers: DestinationPreviewMarker[] = []; #selecting: boolean = false;
#targetMarker: TargetMarker; #isZooming: boolean = false;
#destinationGroupRotation: number = 0; #destinationGroupRotation: number = 0;
#computeDestinationRotation: boolean = false; #computeDestinationRotation: boolean = false;
#destinationRotationCenter: L.LatLng | null = null; #destinationRotationCenter: L.LatLng | null = null;
#coalitionAreas: CoalitionArea[] = [];
#targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false });
#destinationPreviewCursors: DestinationPreviewMarker[] = [];
#drawingCursor: DrawingCursor = new DrawingCursor();
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu"); #mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu"); #unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu"); #airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu");
#mapSourceDropdown: Dropdown; #mapSourceDropdown: Dropdown;
#mapVisibilityOptionsDropdown: Dropdown;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {} #optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#visibiityOptions: { [key: string]: boolean } = {}
constructor(ID: string) { constructor(ID: string) {
/* Init the leaflet map */ /* Init the leaflet map */
//@ts-ignore Needed because the boxSelect option is non-standard
//@ts-ignore super(ID, { zoomSnap: 0, zoomDelta: 0.25, preferCanvas: true, doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
this.setView([37.23, -115.8], 10); this.setView([37.23, -115.8], 10);
this.#ID = ID; this.#ID = ID;
@@ -81,7 +86,10 @@ export class Map extends L.Map {
L.control.scalenautic({ position: "topright", maxWidth: 300, nautic: true, metric: true, imperial: false }).addTo(this); L.control.scalenautic({ position: "topright", maxWidth: 300, nautic: true, metric: true, imperial: false }).addTo(this);
/* Map source dropdown */ /* Map source dropdown */
this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers()) this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers());
/* Visibility options dropdown */
this.#mapVisibilityOptionsDropdown = new Dropdown("map-visibility-options", () => {});
/* Init the state machine */ /* Init the state machine */
this.#state = IDLE; this.#state = IDLE;
@@ -89,21 +97,23 @@ export class Map extends L.Map {
/* Register event handles */ /* Register event handles */
this.on("click", (e: any) => this.#onClick(e)); this.on("click", (e: any) => this.#onClick(e));
this.on("dblclick", (e: any) => this.#onDoubleClick(e)); this.on("dblclick", (e: any) => this.#onDoubleClick(e));
this.on("zoomstart", (e: any) => this.#onZoom(e)); this.on("zoomstart", (e: any) => this.#onZoomStart(e));
this.on("zoomend", (e: any) => this.#onZoomEnd(e));
this.on("drag", (e: any) => this.centerOnUnit(null)); this.on("drag", (e: any) => this.centerOnUnit(null));
this.on("contextmenu", (e: any) => this.#onContextMenu(e)); this.on("contextmenu", (e: any) => this.#onContextMenu(e));
this.on('selectionstart', (e: any) => this.#onSelectionStart(e));
this.on('selectionend', (e: any) => this.#onSelectionEnd(e)); this.on('selectionend', (e: any) => this.#onSelectionEnd(e));
this.on('mousedown', (e: any) => this.#onMouseDown(e)); this.on('mousedown', (e: any) => this.#onMouseDown(e));
this.on('mouseup', (e: any) => this.#onMouseUp(e)); this.on('mouseup', (e: any) => this.#onMouseUp(e));
this.on('mousemove', (e: any) => this.#onMouseMove(e)); this.on('mousemove', (e: any) => this.#onMouseMove(e));
this.on('keydown', (e: any) => this.#updateDestinationPreview(e)); this.on('keydown', (e: any) => this.#onKeyDown(e));
this.on('keyup', (e: any) => this.#updateDestinationPreview(e)); this.on('keyup', (e: any) => this.#onKeyUp(e));
/* Event listeners */ /* Event listeners */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
const el = ev.detail._element; const el = ev.detail._element;
el?.classList.toggle("off"); el?.classList.toggle("off");
getUnitsManager().setHiddenType(ev.detail.coalition, (el?.currentTarget as HTMLElement)?.classList.contains("off")); getUnitsManager().setHiddenType(ev.detail.coalition, !el?.classList.contains("off"));
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
}); });
@@ -114,6 +124,18 @@ export class Map extends L.Map {
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
}); });
document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => {
this.getMapContextMenu().hide();
this.deselectAllCoalitionAreas();
if (ev.detail?.type == "polygon") {
if (this.getState() !== COALITIONAREA_DRAW_POLYGON)
this.setState(COALITIONAREA_DRAW_POLYGON);
else
this.setState(IDLE);
}
});
document.addEventListener("unitUpdated", (ev: CustomEvent) => { document.addEventListener("unitUpdated", (ev: CustomEvent) => {
if (this.#centerUnit != null && ev.detail == this.#centerUnit) if (this.#centerUnit != null && ev.detail == this.#centerUnit)
this.#panToUnit(this.#centerUnit); this.#panToUnit(this.#centerUnit);
@@ -122,7 +144,7 @@ export class Map extends L.Map {
/* Pan interval */ /* Pan interval */
this.#panInterval = window.setInterval(() => { this.#panInterval = window.setInterval(() => {
if (this.#panLeft || this.#panDown || this.#panRight || this.#panLeft) if (this.#panLeft || this.#panDown || this.#panRight || this.#panLeft)
this.panBy(new L.Point(((this.#panLeft? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta, this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta,
((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta)); ((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta));
}, 20); }, 20);
@@ -132,15 +154,24 @@ export class Map extends L.Map {
}); });
document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]);
/* Markers */ /* Create the checkboxes to select the advanced visibility options */
this.#targetMarker = new TargetMarker(new L.LatLng(0, 0), {interactive: false});
this.#visibiityOptions[SHOW_CONTACT_LINES] = false;
this.#visibiityOptions[HIDE_GROUP_MEMBERS] = true;
this.#visibiityOptions[SHOW_UNIT_PATHS] = true;
this.#visibiityOptions[SHOW_UNIT_TARGETS] = true;
this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibiityOptions).map((option: string) => {
return createCheckboxOption(option, option, this.#visibiityOptions[option], (ev: any) => {
this.#setVisibilityOption(option, ev);
});
}));
} }
setLayer(layerName: string) { setLayer(layerName: string) {
if (this.#layer != null) if (this.#layer != null)
this.removeLayer(this.#layer) this.removeLayer(this.#layer)
if (layerName in mapLayers){ if (layerName in mapLayers) {
const layerData = mapLayers[layerName as keyof typeof mapLayers]; const layerData = mapLayers[layerName as keyof typeof mapLayers];
var options: L.TileLayerOptions = { var options: L.TileLayerOptions = {
attribution: layerData.attribution, attribution: layerData.attribution,
@@ -160,21 +191,17 @@ export class Map extends L.Map {
/* State machine */ /* State machine */
setState(state: string) { setState(state: string) {
this.#state = state; this.#state = state;
if (this.#state === IDLE) { this.#updateCursor();
this.#resetDestinationMarkers();
this.#resetTargetMarker(); /* Operations to perform if you are NOT in a state */
this.#showCursor(); if (this.#state !== COALITIONAREA_DRAW_POLYGON) {
this.#deselectCoalitionAreas();
} }
else if (this.#state === MOVE_UNIT) {
this.#resetTargetMarker(); /* Operations to perform if you ARE in a state */
this.#createDestinationMarkers(); if (this.#state === COALITIONAREA_DRAW_POLYGON) {
if (this.#destinationPreviewMarkers.length > 0) this.#coalitionAreas.push(new CoalitionArea([]));
this.#hideCursor(); this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
}
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
this.#resetDestinationMarkers();
this.#createTargetMarker();
this.#hideCursor();
} }
document.dispatchEvent(new CustomEvent("mapStateChanged")); document.dispatchEvent(new CustomEvent("mapStateChanged"));
} }
@@ -183,18 +210,28 @@ export class Map extends L.Map {
return this.#state; return this.#state;
} }
deselectAllCoalitionAreas() {
this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => coalitionArea.setSelected(false));
}
deleteCoalitionArea(coalitionArea: CoalitionArea) {
if (this.#coalitionAreas.includes(coalitionArea))
this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1);
if (this.hasLayer(coalitionArea))
this.removeLayer(coalitionArea);
}
/* Context Menus */ /* Context Menus */
hideAllContextMenus() { hideAllContextMenus() {
this.hideMapContextMenu(); this.hideMapContextMenu();
this.hideUnitContextMenu(); this.hideUnitContextMenu();
this.hideAirbaseContextMenu(); this.hideAirbaseContextMenu();
this.hideCoalitionAreaContextMenu();
} }
showMapContextMenu(e: any) { showMapContextMenu(x: number, y: number, latlng: L.LatLng) {
this.hideAllContextMenus(); this.hideAllContextMenus();
var x = e.originalEvent.x; this.#mapContextMenu.show(x, y, latlng);
var y = e.originalEvent.y;
this.#mapContextMenu.show(x, y, e.latlng);
document.dispatchEvent(new CustomEvent("mapContextMenu")); document.dispatchEvent(new CustomEvent("mapContextMenu"));
} }
@@ -207,11 +244,9 @@ export class Map extends L.Map {
return this.#mapContextMenu; return this.#mapContextMenu;
} }
showUnitContextMenu(e: any) { showUnitContextMenu(x: number, y: number, latlng: L.LatLng) {
this.hideAllContextMenus(); this.hideAllContextMenus();
var x = e.originalEvent.x; this.#unitContextMenu.show(x, y, latlng);
var y = e.originalEvent.y;
this.#unitContextMenu.show(x, y, e.latlng);
} }
getUnitContextMenu() { getUnitContextMenu() {
@@ -222,11 +257,9 @@ export class Map extends L.Map {
this.#unitContextMenu.hide(); this.#unitContextMenu.hide();
} }
showAirbaseContextMenu(e: any, airbase: Airbase) { showAirbaseContextMenu(x: number, y: number, latlng: L.LatLng, airbase: Airbase) {
this.hideAllContextMenus(); this.hideAllContextMenus();
var x = e.originalEvent.x; this.#airbaseContextMenu.show(x, y, latlng);
var y = e.originalEvent.y;
this.#airbaseContextMenu.show(x, y, e.latlng);
this.#airbaseContextMenu.setAirbase(airbase); this.#airbaseContextMenu.setAirbase(airbase);
} }
@@ -238,6 +271,24 @@ export class Map extends L.Map {
this.#airbaseContextMenu.hide(); this.#airbaseContextMenu.hide();
} }
showCoalitionAreaContextMenu(x: number, y: number, latlng: L.LatLng, coalitionArea: CoalitionArea) {
this.hideAllContextMenus();
this.#coalitionAreaContextMenu.show(x, y, latlng);
this.#coalitionAreaContextMenu.setCoalitionArea(coalitionArea);
}
getCoalitionAreaContextMenu() {
return this.#coalitionAreaContextMenu;
}
hideCoalitionAreaContextMenu() {
this.#coalitionAreaContextMenu.hide();
}
isZooming() {
return this.#isZooming;
}
/* Mouse coordinates */ /* Mouse coordinates */
getMousePosition() { getMousePosition() {
return this.#lastMousePosition; return this.#lastMousePosition;
@@ -263,6 +314,10 @@ export class Map extends L.Map {
} }
} }
getCenterUnit() {
return this.#centerUnit;
}
setTheatre(theatre: string) { setTheatre(theatre: string) {
var bounds = new L.LatLngBounds([-90, -180], [90, 180]); var bounds = new L.LatLngBounds([-90, -180], [90, 180]);
var miniMapZoom = 5; var miniMapZoom = 5;
@@ -333,36 +388,59 @@ export class Map extends L.Map {
} }
} }
addTemporaryMarker(latlng: L.LatLng) { addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string) {
var marker = new TemporaryUnitMarker(latlng); var marker = new TemporaryUnitMarker(latlng, name, coalition);
marker.addTo(this); marker.addTo(this);
this.#temporaryMarkers.push(marker); this.#temporaryMarkers.push(marker);
} }
removeTemporaryMarker(latlng: L.LatLng) { removeTemporaryMarker(latlng: L.LatLng) {
var d: number | null = null; // TODO something more refined than this
var dist: number | null = null;
var closest: L.Marker | null = null; var closest: L.Marker | null = null;
var i: number = 0; var i: number = 0;
this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => { this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => {
var t = latlng.distanceTo(marker.getLatLng()); var t = latlng.distanceTo(marker.getLatLng());
if (d == null || t < d) { if (dist == null || t < dist) {
d = t; dist = t;
closest = marker; closest = marker;
i = idx; i = idx;
} }
}); });
if (closest) { if (closest && dist != null && dist < 100) {
this.removeLayer(closest); this.removeLayer(closest);
delete this.#temporaryMarkers[i]; this.#temporaryMarkers.splice(i, 1);
} }
} }
getSelectedCoalitionArea() {
return this.#coalitionAreas.find((area: CoalitionArea) => { return area.getSelected() });
}
bringCoalitionAreaToBack(coalitionArea: CoalitionArea) {
coalitionArea.bringToBack();
this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1);
this.#coalitionAreas.unshift(coalitionArea);
}
getVisibilityOptions() {
return this.#visibiityOptions;
}
/* Event handlers */ /* Event handlers */
#onClick(e: any) { #onClick(e: any) {
if (!this.#preventLeftClick) { if (!this.#preventLeftClick) {
this.hideAllContextMenus(); this.hideAllContextMenus();
if (this.#state === IDLE) { if (this.#state === IDLE) {
this.deselectAllCoalitionAreas();
}
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
if (this.getSelectedCoalitionArea()?.getEditing()) {
this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng);
}
else {
this.deselectAllCoalitionAreas();
}
} }
else { else {
this.setState(IDLE); this.setState(IDLE);
@@ -372,57 +450,68 @@ export class Map extends L.Map {
} }
#onDoubleClick(e: any) { #onDoubleClick(e: any) {
this.deselectAllCoalitionAreas();
} }
#onContextMenu(e: any) { #onContextMenu(e: any) {
this.hideMapContextMenu(); this.hideMapContextMenu();
if (this.#state === IDLE) { if (this.#state === IDLE) {
if (this.#state == IDLE) { if (this.#state == IDLE) {
this.showMapContextMenu(e); this.showMapContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
var clickedCoalitionArea = null;
/* Coalition areas are ordered in the #coalitionAreas array according to their zindex. Select the upper one */
for (let coalitionArea of this.#coalitionAreas) {
if (coalitionArea.getBounds().contains(e.latlng)) {
if (coalitionArea.getSelected())
clickedCoalitionArea = coalitionArea;
else
this.getMapContextMenu().setCoalitionArea(coalitionArea);
}
}
if (clickedCoalitionArea)
this.showCoalitionAreaContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, clickedCoalitionArea);
} }
} }
else if (this.#state === MOVE_UNIT) { else if (this.#state === MOVE_UNIT) {
if (!e.originalEvent.ctrlKey) { if (!e.originalEvent.ctrlKey) {
getUnitsManager().selectedUnitsClearDestinations(); getUnitsManager().selectedUnitsClearDestinations();
} }
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation) getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0; this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null; this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false; this.#computeDestinationRotation = false;
} }
else if (this.#state === BOMBING) { else if (this.#state === BOMBING) {
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE); getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates()); getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
} }
else if (this.#state === CARPET_BOMBING) { else if (this.#state === CARPET_BOMBING) {
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE); getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates()); getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
} }
else if (this.#state === FIRE_AT_AREA) { else if (this.#state === FIRE_AT_AREA) {
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE); getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates()); getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
} }
else {
this.setState(IDLE);
}
} }
#executeAction(e: any, action: string) { #onSelectionStart(e: any) {
if (action === "bomb") this.#selecting = true;
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates()); this.#updateCursor();
else if (action === "carpet-bomb")
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
else if (action === "building-bomb")
getUnitsManager().selectedUnitsBombBuilding(this.getMouseCoordinates());
else if (action === "fire-at-area")
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
} }
#onSelectionEnd(e: any) { #onSelectionEnd(e: any) {
this.#selecting = false;
clearTimeout(this.#leftClickTimer); clearTimeout(this.#leftClickTimer);
this.#preventLeftClick = true; this.#preventLeftClick = true;
this.#leftClickTimer = window.setTimeout(() => { this.#leftClickTimer = window.setTimeout(() => {
this.#preventLeftClick = false; this.#preventLeftClick = false;
}, 200); }, 200);
getUnitsManager().selectFromBounds(e.selectionBounds); getUnitsManager().selectFromBounds(e.selectionBounds);
this.#updateCursor();
} }
#onMouseDown(e: any) { #onMouseDown(e: any) {
@@ -446,23 +535,51 @@ export class Map extends L.Map {
this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.x = e.originalEvent.x;
this.#lastMousePosition.y = e.originalEvent.y; this.#lastMousePosition.y = e.originalEvent.y;
if (this.#state === MOVE_UNIT){ this.#updateCursor();
if (this.#state === MOVE_UNIT) {
/* Update the position of the destination cursors depeding on mouse rotation */
if (this.#computeDestinationRotation && this.#destinationRotationCenter != null) if (this.#computeDestinationRotation && this.#destinationRotationCenter != null)
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng); this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
this.#updateDestinationPreview(e); this.#updateDestinationCursors();
} }
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
this.#targetMarker.setLatLng(this.getMouseCoordinates()); this.#targetCursor.setLatLng(this.getMouseCoordinates());
}
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
this.#drawingCursor.setLatLng(e.latlng);
/* Update the polygon being drawn with the current position of the mouse cursor */
this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng);
} }
} }
#onZoom(e: any) { #onKeyDown(e: any) {
if (this.#centerUnit != null) this.#shiftKey = e.originalEvent.shiftKey;
this.#panToUnit(this.#centerUnit); this.#ctrlKey = e.originalEvent.ctrlKey;
this.#updateCursor();
this.#updateDestinationCursors();
} }
#onKeyUp(e: any) {
this.#shiftKey = e.originalEvent.shiftKey;
this.#ctrlKey = e.originalEvent.ctrlKey;
this.#updateCursor();
this.#updateDestinationCursors();
}
#onZoomStart(e: any) {
if (this.#centerUnit != null)
this.#panToUnit(this.#centerUnit);
this.#isZooming = true;
}
#onZoomEnd(e: any) {
this.#isZooming = false;
}
#panToUnit(unit: Unit) { #panToUnit(unit: Unit) {
var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude); var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
this.setView(unitPosition, this.getZoom(), { animate: false }); this.setView(unitPosition, this.getZoom(), { animate: false });
} }
@@ -471,13 +588,6 @@ export class Map extends L.Map {
return minimapBoundaries; return minimapBoundaries;
} }
#updateDestinationPreview(e: any) {
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
if (idx < this.#destinationPreviewMarkers.length)
this.#destinationPreviewMarkers[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
})
}
#createOptionButton(value: string, url: string, title: string, callback: string, argument: string) { #createOptionButton(value: string, url: string, title: string, callback: string, argument: string) {
var button = document.createElement("button"); var button = document.createElement("button");
const img = document.createElement("img"); const img = document.createElement("img");
@@ -491,47 +601,120 @@ export class Map extends L.Map {
return button; return button;
} }
#createDestinationMarkers() { #deselectCoalitionAreas() {
this.#resetDestinationMarkers(); this.getSelectedCoalitionArea()?.setSelected(false);
}
if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0) { /* Cursors */
/* Create the unit destination preview markers */ #showDefaultCursor() {
this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false}); }
#hideDefaultCursor() {
document.getElementById(this.#ID)?.classList.add("hidden-cursor");
}
#showDestinationCursors() {
const singleCursor = !this.#shiftKey;
const selectedUnitsCount = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
if (selectedUnitsCount > 0) {
if (singleCursor && this.#destinationPreviewCursors.length != 1) {
this.#hideDestinationCursors();
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
marker.addTo(this); marker.addTo(this);
return marker; this.#destinationPreviewCursors = [marker];
}) }
else if (!singleCursor) {
while (this.#destinationPreviewCursors.length > selectedUnitsCount) {
this.removeLayer(this.#destinationPreviewCursors[0]);
this.#destinationPreviewCursors.splice(0, 1);
}
while (this.#destinationPreviewCursors.length < selectedUnitsCount) {
var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
cursor.addTo(this);
this.#destinationPreviewCursors.push(cursor);
}
this.#updateDestinationCursors();
}
} }
} }
#resetDestinationMarkers() { #updateDestinationCursors() {
/* Remove all the destination preview markers */ const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates();
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => { if (this.#destinationPreviewCursors.length == 1)
this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates());
else {
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
if (idx < this.#destinationPreviewCursors.length)
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey? latlng : this.getMouseCoordinates());
})
};
}
#hideDestinationCursors() {
/* Remove all the destination cursors */
this.#destinationPreviewCursors.forEach((marker: L.Marker) => {
this.removeLayer(marker); this.removeLayer(marker);
}) })
this.#destinationPreviewMarkers = []; this.#destinationPreviewCursors = [];
/* Reset the variables used to compute the rotation of the group cursors */
this.#destinationGroupRotation = 0; this.#destinationGroupRotation = 0;
this.#computeDestinationRotation = false; this.#computeDestinationRotation = false;
this.#destinationRotationCenter = null; this.#destinationRotationCenter = null;
} }
#createTargetMarker(){ #showTargetCursor() {
this.#resetTargetMarker(); this.#hideTargetCursor();
this.#targetMarker.addTo(this); this.#targetCursor.addTo(this);
} }
#resetTargetMarker() { #hideTargetCursor() {
this.#targetMarker.setLatLng(new L.LatLng(0, 0)); this.#targetCursor.setLatLng(new L.LatLng(0, 0));
this.removeLayer(this.#targetMarker); this.removeLayer(this.#targetCursor);
} }
#showCursor() { #showDrawingCursor() {
document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); this.#hideDefaultCursor();
if (!this.hasLayer(this.#drawingCursor))
this.#drawingCursor.addTo(this);
} }
#hideCursor() { #hideDrawingCursor() {
document.getElementById(this.#ID)?.classList.add("hidden-cursor"); this.#drawingCursor.setLatLng(new L.LatLng(0, 0));
if (this.hasLayer(this.#drawingCursor))
this.#drawingCursor.removeFrom(this);
}
#updateCursor() {
/* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */
if (this.#ctrlKey || this.#selecting) {
/* Hide all non default cursors */
this.#hideDestinationCursors();
this.#hideTargetCursor();
this.#hideDrawingCursor();
this.#showDefaultCursor();
} else {
/* Hide all the unnecessary cursors depending on the active state */
if (this.#state !== IDLE) this.#hideDefaultCursor();
if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors();
if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor();
if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor();
/* Show the active cursor depending on the active state */
if (this.#state === IDLE) this.#showDefaultCursor();
else if (this.#state === MOVE_UNIT) this.#showDestinationCursors();
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor();
else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor();
}
}
#setVisibilityOption(option: string, ev: any) {
this.#visibiityOptions[option] = ev.currentTarget.checked;
document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged"));
} }
} }

View File

@@ -1,8 +1,11 @@
import { DivIcon } from "leaflet"; import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker"; import { CustomMarker } from "./custommarker";
export class TargetMarker extends CustomMarker { export class TargetMarker extends CustomMarker {
#interactive: boolean = false; constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
createIcon() { createIcon() {
this.setIcon(new DivIcon({ this.setIcon(new DivIcon({
@@ -12,7 +15,6 @@ export class TargetMarker extends CustomMarker {
})); }));
var el = document.createElement("div"); var el = document.createElement("div");
el.classList.add("ol-target-icon"); el.classList.add("ol-target-icon");
el.classList.toggle("ol-target-icon-interactive", this.#interactive)
this.getElement()?.appendChild(el); this.getElement()?.appendChild(el);
} }
} }

View File

@@ -1,13 +1,53 @@
import { Icon } from "leaflet";
import { CustomMarker } from "./custommarker"; import { CustomMarker } from "./custommarker";
import { DivIcon, LatLng } from "leaflet";
import { SVGInjector } from "@tanem/svg-injector";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
export class TemporaryUnitMarker extends CustomMarker { export class TemporaryUnitMarker extends CustomMarker {
#name: string;
#coalition: string;
constructor(latlng: LatLng, name: string, coalition: string) {
super(latlng, {interactive: false});
this.#name = name;
this.#coalition = coalition;
}
createIcon() { createIcon() {
var icon = new Icon({ const category = getMarkerCategoryByName(this.#name);
iconUrl: '/resources/theme/images/markers/temporary-icon.png',
iconSize: [52, 52], /* Set the icon */
iconAnchor: [26, 26] var icon = new DivIcon({
className: 'leaflet-unit-icon',
iconAnchor: [25, 25],
iconSize: [50, 50],
}); });
this.setIcon(icon); this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${category}`);
el.setAttribute("data-coalition", this.#coalition);
// Main icon
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${category}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", false);
el.append(unitIcon);
// Short label
if (category == "aircraft" || category == "helicopter") {
var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label");
shortLabel.innerText = getUnitDatabaseByCategory(category)?.getByName(this.#name)?.shortLabel || "";
el.append(shortLabel);
}
this.getElement()?.appendChild(el);
this.getElement()?.classList.add("ol-temporary-marker");
} }
} }

View File

@@ -0,0 +1,214 @@
import { LatLng } from "leaflet";
import { getInfoPopup, getMap, getUnitsManager } from "..";
import { Airbase } from "./airbase";
import { Bullseye } from "./bullseye";
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "../constants/constants";
import { setCommandModeOptions } from "../server/server";
import { Dropdown } from "../controls/dropdown";
import { groundUnitDatabase } from "../units/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { helicopterDatabase } from "../units/helicopterdatabase";
import { navyUnitDatabase } from "../units/navyunitdatabase";
export class MissionHandler {
#bullseyes: { [name: string]: Bullseye } = {};
#airbases: { [name: string]: Airbase } = {};
#theatre: string = "";
#dateAndTime: DateAndTime = {date: {Year: 0, Month: 0, Day: 0}, time: {h: 0, m: 0, s: 0}, startTime: 0, elapsedTime: 0};
#commandModeOptions: CommandModeOptions = {commandMode: "Hide all", restrictSpawns: false, restrictToCoalition: false, setupTime: Infinity, spawnPoints: {red: Infinity, blue: Infinity}, eras: []};
#remainingSetupTime: number = 0;
#spentSpawnPoint: number = 0;
#commandModeDialog: HTMLElement;
#commandModeErasDropdown: Dropdown;
constructor() {
document.addEventListener("showCommandModeDialog", () => this.showCommandModeDialog());
document.addEventListener("applycommandModeOptions", () => this.#applycommandModeOptions());
/* command-mode settings dialog */
this.#commandModeDialog = <HTMLElement> document.querySelector("#command-mode-settings-dialog");
this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
}
updateBullseyes(data: BullseyesData) {
for (let idx in data.bullseyes) {
const bullseye = data.bullseyes[idx];
if (!(idx in this.#bullseyes))
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap());
if (bullseye.latitude && bullseye.longitude && bullseye.coalition) {
this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
this.#bullseyes[idx].setCoalition(bullseye.coalition);
}
}
}
updateAirbases(data: AirbasesData) {
for (let idx in data.airbases) {
var airbase = data.airbases[idx]
if (this.#airbases[idx] === undefined && airbase.callsign != '') {
this.#airbases[idx] = new Airbase({
position: new LatLng(airbase.latitude, airbase.longitude),
name: airbase.callsign
}).addTo(getMap());
this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
}
if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition) {
this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
this.#airbases[idx].setCoalition(airbase.coalition);
}
}
}
updateMission(data: MissionData) {
if (data.mission) {
/* Set the mission theatre */
if (data.mission.theatre != this.#theatre) {
this.#theatre = data.mission.theatre;
getMap().setTheatre(this.#theatre);
getInfoPopup().setText("Map set to " + this.#theatre);
}
/* Set the date and time data */
this.#dateAndTime = data.mission.dateAndTime;
/* Set the command mode options */
this.#setcommandModeOptions(data.mission.commandModeOptions);
this.#remainingSetupTime = this.getCommandModeOptions().setupTime - this.getDateAndTime().elapsedTime;
var commandModePhaseEl = document.querySelector("#command-mode-phase") as HTMLElement;
if (commandModePhaseEl) {
if (this.#remainingSetupTime > 0) {
var remainingTime = `-${new Date(this.#remainingSetupTime * 1000).toISOString().substring(14, 19)}`;
commandModePhaseEl.dataset.remainingTime = remainingTime;
}
commandModePhaseEl.classList.toggle("setup-phase", this.#remainingSetupTime > 0 && this.getCommandModeOptions().restrictSpawns);
commandModePhaseEl.classList.toggle("game-commenced", this.#remainingSetupTime <= 0 || !this.getCommandModeOptions().restrictSpawns);
commandModePhaseEl.classList.toggle("no-restrictions", !this.getCommandModeOptions().restrictSpawns);
}
}
}
getBullseyes() {
return this.#bullseyes;
}
getAirbases() {
return this.#airbases;
}
getCommandModeOptions() {
return this.#commandModeOptions;
}
getDateAndTime() {
return this.#dateAndTime;
}
getRemainingSetupTime() {
return this.#remainingSetupTime;
}
getAvailableSpawnPoints() {
if (this.getCommandModeOptions().commandMode === GAME_MASTER)
return Infinity;
else if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER)
return this.getCommandModeOptions().spawnPoints.blue - this.#spentSpawnPoint;
else if (this.getCommandModeOptions().commandMode === RED_COMMANDER)
return this.getCommandModeOptions().spawnPoints.red - this.#spentSpawnPoint;
else
return 0;
}
getCommandedCoalition() {
if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER)
return "blue";
else if (this.getCommandModeOptions().commandMode === RED_COMMANDER)
return "red";
else
return "all";
}
refreshSpawnPoints() {
var spawnPointsEl = document.querySelector("#spawn-points");
if (spawnPointsEl) {
spawnPointsEl.textContent = `${this.getAvailableSpawnPoints()}`;
}
}
setSpentSpawnPoints(spawnPoints: number) {
this.#spentSpawnPoint = spawnPoints;
this.refreshSpawnPoints();
}
showCommandModeDialog() {
/* Create the checkboxes to select the unit eras */
var eras = aircraftDatabase.getEras().concat(helicopterDatabase.getEras()).concat(groundUnitDatabase.getEras()).concat(navyUnitDatabase.getEras());
eras = eras.filter((item: string, index: number) => eras.indexOf(item) === index).sort();
this.#commandModeErasDropdown.setOptionsElements(eras.map((era: string) => {
return createCheckboxOption(era, `Enable ${era} units spawns`, this.getCommandModeOptions().eras.includes(era));
}));
this.#commandModeDialog.classList.remove("hide");
const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
restrictSpawnsCheckbox.checked = this.getCommandModeOptions().restrictSpawns;
restrictToCoalitionCheckbox.checked = this.getCommandModeOptions().restrictToCoalition;
blueSpawnPointsInput.value = String(this.getCommandModeOptions().spawnPoints.blue);
redSpawnPointsInput.value = String(this.getCommandModeOptions().spawnPoints.red);
setupTimeInput.value = String(Math.floor(this.getCommandModeOptions().setupTime / 60.0));
}
#applycommandModeOptions() {
this.#commandModeDialog.classList.add("hide");
const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
var eras: string[] = [];
const enabledEras = getCheckboxOptions(this.#commandModeErasDropdown);
Object.keys(enabledEras).forEach((key: string) => {if (enabledEras[key]) eras.push(key)});
setCommandModeOptions(restrictSpawnsCheckbox.checked, restrictToCoalitionCheckbox.checked, {blue: parseInt(blueSpawnPointsInput.value), red: parseInt(redSpawnPointsInput.value)}, eras, parseInt(setupTimeInput.value) * 60);
}
#setcommandModeOptions(commandModeOptions: CommandModeOptions) {
var commandModeOptionsChanged = (!commandModeOptions.eras.every((value: string, idx: number) => {return value === this.getCommandModeOptions().eras[idx]}) ||
commandModeOptions.spawnPoints.red !== this.getCommandModeOptions().spawnPoints.red ||
commandModeOptions.spawnPoints.blue !== this.getCommandModeOptions().spawnPoints.blue ||
commandModeOptions.restrictSpawns !== this.getCommandModeOptions().restrictSpawns ||
commandModeOptions.restrictToCoalition !== this.getCommandModeOptions().restrictToCoalition);
this.#commandModeOptions = commandModeOptions;
this.setSpentSpawnPoints(0);
this.refreshSpawnPoints();
if (commandModeOptionsChanged) {
document.dispatchEvent(new CustomEvent("commandModeOptionsChanged", { detail: this }));
document.getElementById("command-mode-toolbar")?.classList.remove("hide");
const el = document.getElementById("command-mode");
if (el) {
el.dataset.mode = commandModeOptions.commandMode;
el.textContent = commandModeOptions.commandMode.toUpperCase();
}
}
document.querySelector("#spawn-points-container")?.classList.toggle("hide", this.getCommandModeOptions().commandMode === GAME_MASTER || !this.getCommandModeOptions().restrictSpawns);
document.querySelector("#command-mode-settings-button")?.classList.toggle("hide", this.getCommandModeOptions().commandMode !== GAME_MASTER);
}
#onAirbaseClick(e: any) {
getMap().showAirbaseContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, e.sourceTarget);
}
}

View File

@@ -1,169 +0,0 @@
import { LatLng } from "leaflet";
import { getInfoPopup, getMap } from "..";
import { Airbase } from "./airbase";
import { Bullseye } from "./bullseye";
export class MissionHandler
{
#bullseyes : {[name: string]: Bullseye} = {};
#airbases : {[name: string]: Airbase} = {};
#theatre : string = "";
#airbaseData : { [name: string]: object } = {};
// Time
#date : any;
#elapsedTime : any;
#startTime : any;
#time : any;
#updateTime : any;
constructor()
{
}
update(data: BullseyesData | AirbasesData | any)
{
if ("bullseyes" in data)
{
for (let idx in data.bullseyes)
{
const bullseye = data.bullseyes[idx];
if (!(idx in this.#bullseyes))
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap());
if (bullseye.latitude && bullseye.longitude && bullseye.coalition)
{
this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
this.#bullseyes[idx].setCoalition(bullseye.coalition);
}
}
}
if ("mission" in data)
{
if (data.mission != null && data.mission.theatre != this.#theatre)
{
this.#theatre = data.mission.theatre;
getMap().setTheatre(this.#theatre);
getInfoPopup().setText("Map set to " + this.#theatre);
}
}
if ("airbases" in data)
{
/*
console.log( Object.values( data.airbases ).sort( ( a:any, b:any ) => {
const aVal = a.callsign.toLowerCase();
const bVal = b.callsign.toLowerCase();
return aVal > bVal ? 1 : -1;
}) );
//*/
for (let idx in data.airbases)
{
var airbase = data.airbases[idx]
if (this.#airbases[idx] === undefined && airbase.callsign != '')
{
this.#airbases[idx] = new Airbase({
position: new LatLng(airbase.latitude, airbase.longitude),
name: airbase.callsign
}).addTo(getMap());
this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
}
if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition)
{
this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
this.#airbases[idx].setCoalition(airbase.coalition);
}
//this.#airbases[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]);
//this.#airbases[idx].setParkings(["2x big", "5x small"]);
}
}
if ("mission" in data && data.mission != null)
{
if (data.mission != null && data.mission.theatre != this.#theatre)
{
this.#theatre = data.mission.theatre;
getMap().setTheatre(this.#theatre);
getInfoPopup().setText("Map set to " + this.#theatre);
}
if ( "date" in data.mission ) {
this.#date = data.mission.date;
}
if ( "elapsedTime" in data.mission ) {
this.#elapsedTime = data.mission.elapsedTime;
}
if ( "startTime" in data.mission ) {
this.#startTime = data.mission.startTime;
}
if ( "time" in data.mission ) {
this.#time = data.mission.time;
}
}
if ( "time" in data ) {
this.#updateTime = data.time;
}
}
getBullseyes()
{
return this.#bullseyes;
}
getDate() {
return this.#date;
}
getNowDate() {
const date = this.getDate();
const time = this.getTime();
if ( !date ) {
return new Date();
}
let year = date.Year;
let month = date.Month - 1;
if ( month < 0 ) {
month = 11;
year--;
}
return new Date( year, month, date.Day, time.h, time.m, time.s );
}
getTime() {
return this.#time;
}
getUpdateTime() {
return this.#updateTime;
}
#onAirbaseClick(e: any)
{
getMap().showAirbaseContextMenu(e, e.sourceTarget);
}
}

View File

@@ -1,3 +1,13 @@
import { LatLng, Point, Polygon } from "leaflet";
import * as turf from "@turf/turf";
import { UnitDatabase } from "../units/unitdatabase";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { helicopterDatabase } from "../units/helicopterdatabase";
import { groundUnitDatabase } from "../units/groundunitdatabase";
import { Buffer } from "buffer";
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
import { Dropdown } from "../controls/dropdown";
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2); const φ2 = deg2rad(lat2);
@@ -26,6 +36,16 @@ export function distance(lat1: number, lon1: number, lat2: number, lon2: number)
return d; return d;
} }
export function bearingAndDistanceToLatLng(lat: number, lon: number, brng: number, dist: number) {
const R = 6371e3; // metres
const φ1 = deg2rad(lat); // φ, λ in radians
const λ1 = deg2rad(lon);
const φ2 = Math.asin( Math.sin(φ1)*Math.cos(dist/R) + Math.cos(φ1)*Math.sin(dist/R)*Math.cos(brng) );
const λ2 = λ1 + Math.atan2(Math.sin(brng)*Math.sin(dist/R)*Math.cos(φ1), Math.cos(dist/R)-Math.sin(φ1)*Math.sin(φ2));
return new LatLng(rad2deg(φ2), rad2deg(λ2));
}
export function ConvertDDToDMS(D: number, lng: boolean) { export function ConvertDDToDMS(D: number, lng: boolean) {
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N"; var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
var deg = 0 | (D < 0 ? (D = -D) : D); var deg = 0 | (D < 0 ? (D = -D) : D);
@@ -185,3 +205,193 @@ export function mToNm(m: number) {
export function nmToFt(nm: number) { export function nmToFt(nm: number) {
return nm * 6076.12; return nm * 6076.12;
} }
export function polyContains(latlng: LatLng, polygon: Polygon) {
var poly = polygon.toGeoJSON();
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
}
export function randomPointInPoly(polygon: Polygon): LatLng {
var bounds = polygon.getBounds();
var x_min = bounds.getEast();
var x_max = bounds.getWest();
var y_min = bounds.getSouth();
var y_max = bounds.getNorth();
var lat = y_min + (Math.random() * (y_max - y_min));
var lng = x_min + (Math.random() * (x_max - x_min));
var poly = polygon.toGeoJSON();
var inside = turf.inside(turf.point([lng, lat]), poly);
if (inside) {
return new LatLng(lat, lng);
} else {
return randomPointInPoly(polygon);
}
}
export function polygonArea(polygon: Polygon) {
var poly = polygon.toGeoJSON();
return turf.area(poly);
}
export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: {type?: string, role?: string, ranges?: string[], eras?: string[]} ) {
/* Start from all the unit blueprints in the database */
var unitBlueprints = Object.values(unitDatabase.getBlueprints());
/* If a specific type or role is provided, use only the blueprints of that type or role */
if (options.type && options.role) {
console.error("Can't create random unit if both type and role are provided. Either create by type or by role.")
return null;
}
if (options.type) {
unitBlueprints = unitDatabase.getByType(options.type);
}
else if (options.role) {
unitBlueprints = unitDatabase.getByType(options.role);
}
/* Keep only the units that have a range included in the requested values */
if (options.ranges) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
//@ts-ignore
return unitBlueprint.range? options.ranges.includes(unitBlueprint.range): true;
});
}
/* Keep only the units that have an era included in the requested values */
if (options.eras) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
//@ts-ignore
return unitBlueprint.era? options.eras.includes(unitBlueprint.era): true;
});
}
var index = Math.floor(Math.random() * unitBlueprints.length);
return unitBlueprints[index];
}
export function getMarkerCategoryByName(name: string) {
if (aircraftDatabase.getByName(name) != null)
return "aircraft";
else if (helicopterDatabase.getByName(name) != null)
return "helicopter";
else if (groundUnitDatabase.getByName(name) != null){
var type = groundUnitDatabase.getByName(name)?.type;
if (type === "SAM")
return "groundunit-sam";
else if (type === "SAM Search radar" || type === "SAM Track radar" || type === "SAM Search/Track radar")
return "groundunit-sam-radar";
else if (type === "SAM Launcher")
return "groundunit-sam-launcher";
else if (type === "Radar")
return "groundunit-ewr";
else
return "groundunit-other";
}
else
return "groundunit-other"; // TODO add other unit types
}
export function getUnitDatabaseByCategory(category: string) {
if (category == "aircraft")
return aircraftDatabase;
else if (category == "helicopter")
return helicopterDatabase;
else if (category.includes("groundunit"))
return groundUnitDatabase;
else
return null;
}
export function base64ToBytes(base64: string) {
return Buffer.from(base64, 'base64').buffer;
}
export function enumToState(state: number) {
if (state < states.length)
return states[state];
else
return states[0];
}
export function enumToROE(ROE: number) {
if (ROE < ROEs.length)
return ROEs[ROE];
else
return ROEs[0];
}
export function enumToReactionToThreat(reactionToThreat: number) {
if (reactionToThreat < reactionsToThreat.length)
return reactionsToThreat[reactionToThreat];
else
return reactionsToThreat[0];
}
export function enumToEmissioNCountermeasure(emissionCountermeasure: number) {
if (emissionCountermeasure < emissionsCountermeasures.length)
return emissionsCountermeasures[emissionCountermeasure];
else
return emissionsCountermeasures[0];
}
export function enumToCoalition(coalitionID: number) {
switch (coalitionID){
case 0: return "neutral";
case 1: return "red";
case 2: return "blue";
}
return "";
}
export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
const date = dateAndTime.date;
const time = dateAndTime.time;
if (!date) {
return new Date();
}
let year = date.Year;
let month = date.Month - 1;
if (month < 0) {
month = 11;
year--;
}
return new Date(year, month, date.Day, time.h, time.m, time.s);
}
export function createCheckboxOption(value: string, text: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}) {
var div = document.createElement("div");
div.classList.add("ol-checkbox");
var label = document.createElement("label");
label.title = text;
var input = document.createElement("input");
input.type = "checkbox";
input.checked = checked;
var span = document.createElement("span");
span.innerText = value;
label.appendChild(input);
label.appendChild(span);
div.appendChild(label);
input.onclick = (ev: any) => callback(ev);
return div as HTMLElement;
}
export function getCheckboxOptions(dropdown: Dropdown) {
var values: { [key: string]: boolean } = {};
const element = dropdown.getOptionElements();
for (let idx = 0; idx < element.length; idx++) {
const option = element.item(idx) as HTMLElement;
const key = option.querySelector("span")?.innerText;
const value = option.querySelector("input")?.checked;
if (key !== undefined && value !== undefined)
values[key] = value;
}
return values;
}

View File

@@ -1,28 +1,71 @@
import { Panel } from "./panel"; import { Panel } from "./panel";
export class LogPanel extends Panel export class LogPanel extends Panel {
{ #open: boolean = false;
#logs: String[]; #queuedMessages: number = 0;
#scrolledDown: boolean = true;
#logs: {[key: string]: string} = {};
constructor(ID: string) constructor(ID: string) {
{
super(ID); super(ID);
this.#logs = [];
document.addEventListener("toggleLogPanel", () => {
this.getElement().classList.toggle("open");
this.#open = !this.#open;
this.#queuedMessages = 0;
this.#updateHeader();
if (this.#scrolledDown)
this.#scrollDown();
});
const scrollEl = this.getElement().querySelector(".ol-scrollable");
if (scrollEl) {
scrollEl.addEventListener("scroll", () => {
this.#scrolledDown = Math.abs(scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight) < 1
})
}
} }
update(data: any) appendLogs(logs: {[key: string]: string}) {
{ Object.keys(logs).forEach((key: string) => {
var logs = data["logs"]; if (!(key in this.#logs)) {
for (let idx in logs) this.#logs[key] = logs[key];
{ this.appendLog(logs[key]);
if (parseInt(idx) >= this.#logs.length) {
this.#logs.push(logs[idx]);
var el = document.createElement("div");
el.innerText = logs[idx];
el.classList.add("js-log-element", "ol-log-element");
this.getElement().appendChild(el);
this.getElement().scrollTop = this.getElement().scrollHeight;
} }
});
}
appendLog(log: string) {
var el = document.createElement("div");
el.classList.add("ol-log-entry");
el.textContent = log;
this.getElement().querySelector(".ol-scrollable")?.appendChild(el);
console.log(log);
if (!this.#open)
this.#queuedMessages++;
this.#updateHeader();
if (this.#scrolledDown)
this.#scrollDown();
}
#updateHeader() {
const headerEl = this.getElement().querySelector("#log-panel-header") as HTMLElement;
if (headerEl) {
if (this.#queuedMessages)
headerEl.innerText = `Server log (${this.#queuedMessages})`;
else
headerEl.innerText = `Server log`;
}
}
#scrollDown() {
const scrollEl = this.getElement().querySelector(".ol-scrollable");
if (scrollEl) {
scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.clientHeight;
} }
} }
} }

View File

@@ -1,5 +1,5 @@
import { Icon, LatLng, Marker, Polyline } from "leaflet"; import { Icon, LatLng, Marker, Polyline } from "leaflet";
import { getMap, getMissionData, getUnitsManager } from ".."; import { getMap, getMissionHandler, getUnitsManager } from "..";
import { distance, bearing, zeroAppend, mToNm, nmToFt } from "../other/utils"; import { distance, bearing, zeroAppend, mToNm, nmToFt } from "../other/utils";
import { Unit } from "../units/unit"; import { Unit } from "../units/unit";
import { Panel } from "./panel"; import { Panel } from "./panel";
@@ -36,7 +36,7 @@ export class MouseInfoPanel extends Panel {
var selectedUnitPosition = null; var selectedUnitPosition = null;
var selectedUnits = getUnitsManager().getSelectedUnits(); var selectedUnits = getUnitsManager().getSelectedUnits();
if (selectedUnits && selectedUnits.length == 1) if (selectedUnits && selectedUnits.length == 1)
selectedUnitPosition = new LatLng(selectedUnits[0].getFlightData().latitude, selectedUnits[0].getFlightData().longitude); selectedUnitPosition = new LatLng(selectedUnits[0].getPosition().lat, selectedUnits[0].getPosition().lng);
/* Draw measures from selected unit, from pin location, and from bullseyes */ /* Draw measures from selected unit, from pin location, and from bullseyes */
this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition); this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition);
@@ -44,7 +44,7 @@ export class MouseInfoPanel extends Panel {
this.getElement().querySelector(`#measuring-tool`)?.classList.toggle("hide", this.#measurePoint === null && selectedUnitPosition === null); this.getElement().querySelector(`#measuring-tool`)?.classList.toggle("hide", this.#measurePoint === null && selectedUnitPosition === null);
var bullseyes = getMissionData().getBullseyes(); var bullseyes = getMissionHandler().getBullseyes();
for (let idx in bullseyes) for (let idx in bullseyes)
this.#drawMeasure(null, `bullseye-${idx}`, bullseyes[idx].getLatLng(), mousePosition); this.#drawMeasure(null, `bullseye-${idx}`, bullseyes[idx].getLatLng(), mousePosition);

View File

@@ -0,0 +1,26 @@
import { Panel } from "./panel";
export class ServerStatusPanel extends Panel {
constructor(ID: string) {
super(ID);
}
update(frameRate: number, load: number) {
const frameRateEl = this.getElement().querySelector("#server-frame-rate");
if (frameRateEl) {
frameRateEl.textContent = `${frameRate}`;
frameRateEl.classList.toggle("fps-high", frameRate >= 60)
frameRateEl.classList.toggle("fps-medium", frameRate >= 30 && frameRate < 60)
frameRateEl.classList.toggle("fps-low", frameRate <= 30)
}
const loadEl = this.getElement().querySelector("#server-load");
if (loadEl) {
loadEl.textContent = `${load}`;
loadEl.classList.toggle("load-high", load >= 1000)
loadEl.classList.toggle("load-medium", load >= 100 && load < 1000)
loadEl.classList.toggle("load-low", load <= 100)
}
}
}

View File

@@ -8,6 +8,7 @@ import { Panel } from "./panel";
import { Switch } from "../controls/switch"; import { Switch } from "../controls/switch";
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants"; import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils"; import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
import { GeneralSettings, Radio, TACAN } from "../@types/unit";
export class UnitControlPanel extends Panel { export class UnitControlPanel extends Panel {
#altitudeSlider: Slider; #altitudeSlider: Slider;
@@ -33,8 +34,9 @@ export class UnitControlPanel extends Panel {
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); }); this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); });
/* Option buttons */ /* Option buttons */
this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => { // Reversing the ROEs so that the least "aggressive" option is always on the left
return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); }); this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => {
return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions.slice(0).reverse()[index], () => { getUnitsManager().selectedUnitsSetROE(option); });
}); });
this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => { this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => {
@@ -98,13 +100,13 @@ export class UnitControlPanel extends Panel {
if (units.length < 20) { if (units.length < 20) {
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => { this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
var button = document.createElement("button"); var button = document.createElement("button");
var callsign = unit.getBaseData().unitName || ""; var callsign = unit.getUnitName() || "";
var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name; var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName();
button.setAttribute("data-label", label); button.setAttribute("data-label", label);
button.setAttribute("data-callsign", callsign); button.setAttribute("data-callsign", callsign);
button.setAttribute("data-coalition", unit.getMissionData().coalition); button.setAttribute("data-coalition", unit.getCoalition());
button.classList.add("pill", "highlight-coalition") button.classList.add("pill", "highlight-coalition")
button.addEventListener("click", () => { button.addEventListener("click", () => {
@@ -139,12 +141,12 @@ export class UnitControlPanel extends Panel {
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1); element.toggleAttribute("data-show-advanced-settings-button", units.length == 1);
/* Flight controls */ /* Flight controls */
var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitude}); var desiredAltitude = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitudeType}); var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeed}); var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeedType}); var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().onOff}); var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().followRoads}); var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
if (selectedUnitsTypes.length == 1) { if (selectedUnitsTypes.length == 1) {
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false); this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false);
@@ -170,15 +172,15 @@ export class UnitControlPanel extends Panel {
/* Option buttons */ /* Option buttons */
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => { this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value)) button.classList.toggle("selected", units.every((unit: Unit) => unit.getROE() === button.value))
}); });
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => { this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value)) button.classList.toggle("selected", units.every((unit: Unit) => unit.getReactionToThreat() === button.value))
}); });
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => { this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value)) button.classList.toggle("selected", units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value))
}); });
this.#onOffSwitch.setValue(onOff, false); this.#onOffSwitch.setValue(onOff, false);
@@ -207,11 +209,11 @@ export class UnitControlPanel extends Panel {
const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement; const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement;
const unit = units[0]; const unit = units[0];
const roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) const roles = aircraftDatabase.getByName(unit.getName())?.loadouts?.map((loadout) => {return loadout.roles})
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker"); const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker");
const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS"); const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS");
const radioMHz = Math.floor(unit.getOptionsData().radio.frequency / 1000000); const radioMHz = Math.floor(unit.getRadio().frequency / 1000000);
const radioDecimals = (unit.getOptionsData().radio.frequency / 1000000 - radioMHz) * 1000; const radioDecimals = (unit.getRadio().frequency / 1000000 - radioMHz) * 1000;
/* Activate the correct options depending on unit type */ /* Activate the correct options depending on unit type */
this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS); this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS);
@@ -223,28 +225,28 @@ export class UnitControlPanel extends Panel {
/* Set common properties */ /* Set common properties */
// Name // Name
unitNameEl.innerText = unit.getBaseData().unitName; unitNameEl.innerText = unit.getUnitName();
// General settings // General settings
prohibitJettisonCheckbox.checked = unit.getOptionsData().generalSettings.prohibitJettison; prohibitJettisonCheckbox.checked = unit.getGeneralSettings().prohibitJettison;
prohibitAfterburnerCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAfterburner; prohibitAfterburnerCheckbox.checked = unit.getGeneralSettings().prohibitAfterburner;
prohibitAACheckbox.checked = unit.getOptionsData().generalSettings.prohibitAA; prohibitAACheckbox.checked = unit.getGeneralSettings().prohibitAA;
prohibitAGCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAG; prohibitAGCheckbox.checked = unit.getGeneralSettings().prohibitAG;
prohibitAirWpnCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAirWpn; prohibitAirWpnCheckbox.checked = unit.getGeneralSettings().prohibitAirWpn;
// Tasking // Tasking
tankerCheckbox.checked = unit.getTaskData().isTanker; tankerCheckbox.checked = unit.getIsTanker();
AWACSCheckbox.checked = unit.getTaskData().isAWACS; AWACSCheckbox.checked = unit.getIsAWACS();
// TACAN // TACAN
TACANCheckbox.checked = unit.getOptionsData().TACAN.isOn; TACANCheckbox.checked = unit.getTACAN().isOn;
TACANChannelInput.value = String(unit.getOptionsData().TACAN.channel); TACANChannelInput.value = String(unit.getTACAN().channel);
TACANCallsignInput.value = String(unit.getOptionsData().TACAN.callsign); TACANCallsignInput.value = String(unit.getTACAN().callsign);
this.#TACANXYDropdown.setValue(unit.getOptionsData().TACAN.XY); this.#TACANXYDropdown.setValue(unit.getTACAN().XY);
// Radio // Radio
radioMhzInput.value = String(radioMHz); radioMhzInput.value = String(radioMHz);
radioCallsignNumberInput.value = String(unit.getOptionsData().radio.callsignNumber); radioCallsignNumberInput.value = String(unit.getRadio().callsignNumber);
this.#radioDecimalsDropdown.setValue("." + radioDecimals); this.#radioDecimalsDropdown.setValue("." + radioDecimals);
if (tanker) /* Set tanker specific options */ if (tanker) /* Set tanker specific options */
@@ -255,7 +257,7 @@ export class UnitControlPanel extends Panel {
this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]);
// This must be done after setting the options // This must be done after setting the options
if (!this.#radioCallsignDropdown.selectValue(unit.getOptionsData().radio.callsign - 1)) // Ensure the selected value is in the acceptable range if (!this.#radioCallsignDropdown.selectValue(unit.getRadio().callsign - 1)) // Ensure the selected value is in the acceptable range
this.#radioCallsignDropdown.selectValue(0); this.#radioCallsignDropdown.selectValue(0);
} }
} }

View File

@@ -1,4 +1,5 @@
import { getUnitsManager } from ".."; import { getUnitsManager } from "..";
import { Ammo } from "../@types/unit";
import { ConvertDDToDMS, rad2deg } from "../other/utils"; import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { aircraftDatabase } from "../units/aircraftdatabase"; import { aircraftDatabase } from "../units/aircraftdatabase";
import { Unit } from "../units/unit"; import { Unit } from "../units/unit";
@@ -51,21 +52,21 @@ export class UnitInfoPanel extends Panel {
#onUnitUpdate(unit: Unit) { #onUnitUpdate(unit: Unit) {
if (this.getElement() != null && this.getVisible() && unit.getSelected()) { if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
const baseData = unit.getBaseData(); const baseData = unit.getData();
/* Set the unit info */ /* Set the unit info */
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name; this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
this.#unitName.innerText = baseData.unitName; this.#unitName.innerText = baseData.unitName;
if (unit.getMissionData().flags.Human) if (unit.getHuman())
this.#unitControl.innerText = "Human"; this.#unitControl.innerText = "Human";
else if (baseData.controlled) else if (baseData.controlled)
this.#unitControl.innerText = "Olympus controlled"; this.#unitControl.innerText = "Olympus controlled";
else else
this.#unitControl.innerText = "DCS Controlled"; this.#unitControl.innerText = "DCS Controlled";
this.#fuelBar.style.width = String(unit.getMissionData().fuel + "%"); this.#fuelBar.style.width = String(unit.getFuel() + "%");
this.#fuelPercentage.dataset.percentage = "" + unit.getMissionData().fuel; this.#fuelPercentage.dataset.percentage = "" + unit.getFuel();
this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== "" ? unit.getTaskData().currentTask : "No task"; this.#currentTask.dataset.currentTask = unit.getTask() !== "" ? unit.getTask() : "No task";
this.#currentTask.dataset.coalition = unit.getMissionData().coalition; this.#currentTask.dataset.coalition = unit.getCoalition();
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`; this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`;
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == ''); this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == '');
@@ -74,13 +75,13 @@ export class UnitInfoPanel extends Panel {
const items = <HTMLElement>this.#loadoutContainer.querySelector("#loadout-items"); const items = <HTMLElement>this.#loadoutContainer.querySelector("#loadout-items");
if (items) { if (items) {
const ammo = Object.values(unit.getMissionData().ammo); const ammo = Object.values(unit.getAmmo());
if (ammo.length > 0) { if (ammo.length > 0) {
items.replaceChildren(...Object.values(unit.getMissionData().ammo).map( items.replaceChildren(...Object.values(unit.getAmmo()).map(
(ammo: any) => { (ammo: Ammo) => {
var el = document.createElement("div"); var el = document.createElement("div");
el.dataset.qty = ammo.count; el.dataset.qty = `${ammo.quantity}`;
el.dataset.item = ammo.desc.displayName; el.dataset.item = ammo.name;
return el; return el;
} }
)); ));

View File

@@ -1,6 +1,7 @@
import { LatLng } from 'leaflet'; import { LatLng } from 'leaflet';
import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..'; import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitDataTable, getUnitsManager, setLoginStatus } from '..';
import { SpawnOptions } from '../controls/mapcontextmenu'; import { GeneralSettings, Radio, TACAN } from '../@types/unit';
import { ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
var connected: boolean = false; var connected: boolean = false;
var paused: boolean = false; var paused: boolean = false;
@@ -14,22 +15,22 @@ const BULLSEYE_URI = "bullseyes";
const MISSION_URI = "mission"; const MISSION_URI = "mission";
var username = ""; var username = "";
var credentials = ""; var password = "";
var sessionHash: string | null = null; var sessionHash: string | null = null;
var lastUpdateTime = 0; var lastUpdateTimes: {[key: string]: number} = {}
var demoEnabled = false; var demoEnabled = false;
export function toggleDemoEnabled() { export function toggleDemoEnabled() {
demoEnabled = !demoEnabled; demoEnabled = !demoEnabled;
} }
export function setCredentials(newUsername: string, newCredentials: string) { export function setCredentials(newUsername: string, newPassword: string) {
username = newUsername; username = newUsername;
credentials = newCredentials; password = newPassword;
} }
export function GET(callback: CallableFunction, uri: string, options?: { time?: number }) { export function GET(callback: CallableFunction, uri: string, options?: { time?: number }, responseType?: string) {
var xmlHttp = new XMLHttpRequest(); var xmlHttp = new XMLHttpRequest();
/* Assemble the request options string */ /* Assemble the request options string */
@@ -37,24 +38,36 @@ export function GET(callback: CallableFunction, uri: string, options?: { time?:
if (options?.time != undefined) if (options?.time != undefined)
optionsString = `time=${options.time}`; optionsString = `time=${options.time}`;
/* On the connection */
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true); xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
if (credentials)
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials); /* If provided, set the credentials */
if (username && password)
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${username}:${password}`));
/* If specified, set the response type */
if (responseType)
xmlHttp.responseType = responseType as XMLHttpRequestResponseType;
xmlHttp.onload = function (e) { xmlHttp.onload = function (e) {
if (xmlHttp.status == 200) { if (xmlHttp.status == 200) {
var data = JSON.parse(xmlHttp.responseText); /* Success */
if (uri !== UNITS_URI || (options?.time == 0) || parseInt(data.time) > lastUpdateTime) { setConnected(true);
callback(data); if (xmlHttp.responseType == 'arraybuffer')
lastUpdateTime = parseInt(data.time); lastUpdateTimes[uri] = callback(xmlHttp.response);
if (isNaN(lastUpdateTime)) else {
lastUpdateTime = 0; const result = JSON.parse(xmlHttp.responseText);
setConnected(true); lastUpdateTimes[uri] = callback(result);
if (result.frameRate !== undefined && result.load !== undefined)
getServerStatusPanel().update(result.frameRate, result.load);
} }
} else if (xmlHttp.status == 401) { } else if (xmlHttp.status == 401) {
/* Bad credentials */
console.error("Incorrect username/password"); console.error("Incorrect username/password");
setConnectionStatus("failed"); setLoginStatus("failed");
} else { } else {
/* Failure, probably disconnected */
setConnected(false); setConnected(false);
} }
}; };
@@ -69,8 +82,8 @@ export function POST(request: object, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest(); var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", demoEnabled ? DEMO_ADDRESS : REST_ADDRESS); xmlHttp.open("PUT", demoEnabled ? DEMO_ADDRESS : REST_ADDRESS);
xmlHttp.setRequestHeader("Content-Type", "application/json"); xmlHttp.setRequestHeader("Content-Type", "application/json");
if (credentials) if (username && password)
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials); xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${username}:${password}`));
xmlHttp.onreadystatechange = () => { xmlHttp.onreadystatechange = () => {
callback(); callback();
}; };
@@ -103,8 +116,8 @@ export function getBullseye(callback: CallableFunction) {
GET(callback, BULLSEYE_URI); GET(callback, BULLSEYE_URI);
} }
export function getLogs(callback: CallableFunction) { export function getLogs(callback: CallableFunction, refresh: boolean = false) {
GET(callback, LOGS_URI); GET(callback, LOGS_URI, { time: refresh ? 0 : lastUpdateTimes[LOGS_URI]});
} }
export function getMission(callback: CallableFunction) { export function getMission(callback: CallableFunction) {
@@ -112,7 +125,7 @@ export function getMission(callback: CallableFunction) {
} }
export function getUnits(callback: CallableFunction, refresh: boolean = false) { export function getUnits(callback: CallableFunction, refresh: boolean = false) {
GET(callback, `${UNITS_URI}`, { time: refresh ? 0 : lastUpdateTime }); GET(callback, UNITS_URI, { time: refresh ? 0 : lastUpdateTimes[UNITS_URI] }, 'arraybuffer');
} }
export function addDestination(ID: number, path: any) { export function addDestination(ID: number, path: any) {
@@ -133,15 +146,27 @@ export function spawnExplosion(intensity: number, latlng: LatLng) {
POST(data, () => { }); POST(data, () => { });
} }
export function spawnGroundUnit(spawnOptions: SpawnOptions) { export function spawnAircrafts(units: any, coalition: string, airbaseName: string, immediate: boolean, spawnPoints: number) {
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition }; var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnGround": command } var data = { "spawnAircrafts": command }
POST(data, () => { }); POST(data, () => { });
} }
export function spawnAircraft(spawnOptions: SpawnOptions) { export function spawnHelicopters(units: any, coalition: string, airbaseName: string, immediate: boolean, spawnPoints: number) {
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "" }; var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnAir": command } var data = { "spawnHelicopters": command }
POST(data, () => { });
}
export function spawnGroundUnits(units: any, coalition: string, immediate: boolean, spawnPoints: number) {
var command = { "units": units, "coalition": coalition, "immediate": immediate, "spawnPoints": spawnPoints };;
var data = { "spawnGroundUnits": command }
POST(data, () => { });
}
export function spawnNavyUnits(units: any, coalition: string, immediate: boolean, spawnPoints: number) {
var command = { "units": units, "coalition": coalition, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnNavyUnits": command }
POST(data, () => { }); POST(data, () => { });
} }
@@ -167,8 +192,8 @@ export function cloneUnit(ID: number, latlng: LatLng) {
POST(data, () => { }); POST(data, () => { });
} }
export function deleteUnit(ID: number, explosion: boolean) { export function deleteUnit(ID: number, explosion: boolean, immediate: boolean) {
var command = { "ID": ID, "explosion": explosion }; var command = { "ID": ID, "explosion": explosion, "immediate": immediate };
var data = { "deleteUnit": command } var data = { "deleteUnit": command }
POST(data, () => { }); POST(data, () => { });
} }
@@ -222,19 +247,19 @@ export function createFormation(ID: number, isLeader: boolean, wingmenIDs: numbe
} }
export function setROE(ID: number, ROE: string) { export function setROE(ID: number, ROE: string) {
var command = { "ID": ID, "ROE": ROE } var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) }
var data = { "setROE": command } var data = { "setROE": command }
POST(data, () => { }); POST(data, () => { });
} }
export function setReactionToThreat(ID: number, reactionToThreat: string) { export function setReactionToThreat(ID: number, reactionToThreat: string) {
var command = { "ID": ID, "reactionToThreat": reactionToThreat } var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) }
var data = { "setReactionToThreat": command } var data = { "setReactionToThreat": command }
POST(data, () => { }); POST(data, () => { });
} }
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) { export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) {
var command = { "ID": ID, "emissionsCountermeasures": emissionCountermeasure } var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) }
var data = { "setEmissionsCountermeasures": command } var data = { "setEmissionsCountermeasures": command }
POST(data, () => { }); POST(data, () => { });
} }
@@ -295,48 +320,86 @@ export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolea
POST(data, () => { }); POST(data, () => { });
} }
export function startUpdate() { export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number) {
/* On the first connection, force request of full data */ var command = {
getAirbases((data: AirbasesData) => getMissionData()?.update(data)); "restrictSpawns": restrictSpawns,
getBullseye((data: BullseyesData) => getMissionData()?.update(data)); "restrictToCoalition": restrictToCoalition,
getMission((data: any) => { getMissionData()?.update(data) }); "spawnPoints": spawnPoints,
getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */); "eras": eras,
"setupTime": setupTime
};
requestUpdate(); var data = { "setCommandModeOptions": command };
requestRefresh(); POST(data, () => { });
}
export function startUpdate() {
window.setInterval(() => {
if (!getPaused()) {
getAirbases((data: AirbasesData) => {
checkSessionHash(data.sessionHash);
getMissionHandler()?.updateAirbases(data);
return data.time;
});
}
}, 10000);
window.setInterval(() => {
if (!getPaused()){
getBullseye((data: BullseyesData) => {
checkSessionHash(data.sessionHash);
getMissionHandler()?.updateBullseyes(data);
return data.time;
});
}
}, 10000);
window.setInterval(() => {
if (!getPaused()) {
getMission((data: MissionData) => {
checkSessionHash(data.sessionHash);
getMissionHandler()?.updateMission(data);
return data.time;
});
}
}, 1000);
window.setInterval(() => {
if (!getPaused()) {
getLogs((data: any) => {
checkSessionHash(data.sessionHash);
getLogPanel().appendLogs(data.logs)
return data.time;
});
}
}, 1000);
window.setInterval(() => {
if (!getPaused()) {
getUnits((buffer: ArrayBuffer) => {
var time = getUnitsManager()?.update(buffer);
return time;
}, false);
}
}, 250);
window.setInterval(() => {
if (!getPaused()) {
getUnits((buffer: ArrayBuffer) => {
var time = getUnitsManager()?.update(buffer);
return time;
}, true);
getConnectionStatusPanel()?.update(getConnected());
}
}, 5000);
} }
export function requestUpdate() { export function requestUpdate() {
/* Main update rate = 250ms is minimum time, equal to server update time. */ /* Main update rate = 250ms is minimum time, equal to server update time. */
getUnits((data: UnitsData) => { if (!getPaused()) {
if (!getPaused()) { getUnits((buffer: ArrayBuffer) => { return getUnitsManager()?.update(buffer); }, false);
getUnitsManager()?.update(data); }
checkSessionHash(data.sessionHash);
}
}, false);
window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000); window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
getConnectionStatusPanel()?.update(getConnected());
}
export function requestRefresh() {
/* Main refresh rate = 5000ms. */
getUnits((data: UnitsData) => {
if (!getPaused()) {
getUnitsManager()?.update(data);
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
getMission((data: any) => {
getMissionData()?.update(data)
});
// Update the list of existing units
getUnitDataTable()?.update();
checkSessionHash(data.sessionHash);
}
}, true);
window.setTimeout(() => requestRefresh(), 5000);
} }
export function checkSessionHash(newSessionHash: string) { export function checkSessionHash(newSessionHash: string) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,154 @@
import { LatLng } from "leaflet";
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit";
export class DataExtractor {
#seekPosition = 0;
#dataview: DataView;
#decoder: TextDecoder;
#buffer: ArrayBuffer;
constructor(buffer: ArrayBuffer) {
this.#buffer = buffer;
this.#dataview = new DataView(this.#buffer);
this.#decoder = new TextDecoder("utf-8");
}
getSeekPosition() {
return this.#seekPosition;
}
extractBool() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value > 0;
}
extractUInt8() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value;
}
extractUInt16() {
const value = this.#dataview.getUint16(this.#seekPosition, true);
this.#seekPosition += 2;
return value;
}
extractUInt32() {
const value = this.#dataview.getUint32(this.#seekPosition, true);
this.#seekPosition += 4;
return value;
}
extractUInt64() {
const value = this.#dataview.getBigUint64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractFloat64() {
const value = this.#dataview.getFloat64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractLatLng() {
return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64())
}
extractFromBitmask(bitmask: number, position: number) {
return ((bitmask >> position) & 1) > 0;
}
extractString(length?: number) {
if (length === undefined)
length = this.extractUInt16()
var stringBuffer = this.#buffer.slice(this.#seekPosition, this.#seekPosition + length);
var view = new Int8Array(stringBuffer);
var stringLength = length;
view.forEach((value: number, idx: number) => { if (value === 0) stringLength = idx; });
const value = this.#decoder.decode(stringBuffer);
this.#seekPosition += length;
return value.substring(0, stringLength).trim();
}
extractChar() {
return this.extractString(1);
}
extractTACAN() {
const value: TACAN = {
isOn: this.extractBool(),
channel: this.extractUInt8(),
XY: this.extractChar(),
callsign: this.extractString(4)
}
return value;
}
extractRadio() {
const value: Radio = {
frequency: this.extractUInt32(),
callsign: this.extractUInt8(),
callsignNumber: this.extractUInt8()
}
return value;
}
extractGeneralSettings() {
const value: GeneralSettings = {
prohibitJettison: this.extractBool(),
prohibitAA: this.extractBool(),
prohibitAG: this.extractBool(),
prohibitAfterburner: this.extractBool(),
prohibitAirWpn: this.extractBool(),
}
return value;
}
extractAmmo() {
const value: Ammo[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
quantity: this.extractUInt16(),
name: this.extractString(33),
guidance: this.extractUInt8(),
category: this.extractUInt8(),
missileCategory: this.extractUInt8()
});
}
return value;
}
extractContacts(){
const value: Contact[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
ID: this.extractUInt32(),
detectionMethod: this.extractUInt8()
});
}
return value;
}
extractActivePath() {
const value: LatLng[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push(this.extractLatLng());
}
return value;
}
extractOffset() {
const value: Offset = {
x: this.extractFloat64(),
y: this.extractFloat64(),
z: this.extractFloat64(),
}
return value;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,486 @@
import { getMissionHandler } from "..";
import { GAME_MASTER } from "../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class NavyUnitDatabase extends UnitDatabase {
constructor() {
super();
this.blueprints = {
"Type_052B": {
"name": "Type_052B",
"coalition": "red",
"type": "Destroyer",
"era": "Modern",
"label": "052B DDG-168 Guangzhou",
"shortLabel": "Type 52B",
"range": "Short",
"filename": ""
},
"Type_052C": {
"name": "Type_052C",
"coalition": "red",
"type": "Destroyer",
"era": "Modern",
"label": "052C DDG-171 Haikou",
"shortLabel": "Type 52C",
"range": "Short",
"filename": ""
},
"Type_054A": {
"name": "",
"coalition": "red",
"type": "Frigate",
"era": "Modern",
"label": "054A FFG-538 Yantai",
"shortLabel": "Type 54A",
"range": "Medium",
"filename": ""
},
"Type_071": {
"name": "Type_071",
"coalition": "red",
"type": "Transport",
"era": "Modern",
"label": "Type 071",
"shortLabel": "Type 071",
"range": "",
"filename": ""
},
"Type_093": {
"name": "Type_093",
"coalition": "red",
"type": "Submarine",
"era": "Modern",
"label": "Type 093",
"shortLabel": "Type 093",
"range": "",
"filename": ""
},
"santafe": {
"name": "santafe",
"coalition": "",
"type": "Submarine",
"era": "Early Cold War",
"label": "ARA Santa Fe S-21",
"shortLabel": "ARA Santa",
"range": "",
"filename": ""
},
"ara_vdm": {
"name": "ara_vdm",
"coalition": "",
"type": "Aircraft Carrier",
"era": "Mid Cold War",
"label": "ARA Vienticinco de Mayo",
"shortLabel": "ARA Vienticinco de Mayo",
"range": "",
"filename": ""
},
"kuznecow": {
"name": "kuznecow",
"coalition": "red",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "Admiral Kuznetsov",
"shortLabel": "Admiral Kuznetsov",
"range": "Medium",
"filename": ""
},
"albatros": {
"name": "albatros",
"coalition": "red",
"type": "Aircraft Carrier",
"era": "Early Cold War",
"label": "Albatros (Grisha-5)",
"shortLabel": "Albatros",
"range": "",
"filename": ""
},
"leander-gun-condell": {
"name": "leander-gun-condell",
"coalition": "",
"type": "Frigate",
"era": "Mid Cold War",
"label": "Almirante Condell PFG-06",
"shortLabel": "Almirante Condell",
"range": "",
"filename": ""
},
"Boat Armed Hi-Speed": {
"name": "Boat Armed Hi-Speed",
"coalition": "",
"type": "Fast Attack Craft",
"era": "Mid Cold War",
"label": "Boat Armed Hi-Speed",
"shortLabel": "Boat Armed Hi-Speed",
"range": "",
"filename": ""
},
"HandyWind": {
"name": "HandyWind",
"coalition": "blue",
"type": "Cargoship",
"era": "Late Cold War",
"label": "Bulker Handy Wind",
"shortLabel": "Bulker Handy Wind",
"range": "",
"filename": ""
},
"CV_1143_5": {
"name": "CV_1143_5",
"coalition": "red",
"type": "Aircraft Carrier",
"era": "Modern",
"label": "CV Admiral Kuznetsov(2017)",
"shortLabel": "Admiral Kuznetsov(2017)",
"range": "Medium",
"filename": ""
},
"CV_59": {
"name": "CV_59",
"coalition": "blue",
"type": "Aircraft Carrier",
"era": "Early Cold War",
"label": "CV-59 Forrestal",
"shortLabel": "CV-59",
"range": "Short",
"filename": ""
},
"CVN_71": {
"name": "CVN_71",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-71 Theodore Roosevelt",
"shortLabel": "CVN-71",
"range": "Short",
"filename": ""
},
"CVN_72": {
"name": "CVN_72",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-72 Abraham Lincoln",
"shortLabel": "CVN-72",
"range": "Short",
"filename": ""
},
"CVN_73": {
"name": "CVN_73",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-73 George Washington",
"shortLabel": "CVN-73",
"range": "Medium",
"filename": ""
},
"Stennis": {
"name": "Stennis",
"coalition": "blue",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-74 John C. Stennis",
"shortLabel": "CVN-74",
"range": "Medium",
"filename": ""
},
"CVN_75": {
"name": "CVN_75",
"coalition": "blue",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-75 Harry S. Truman",
"shortLabel": "CVN-75",
"range": "Medium",
"filename": ""
},
"CastleClass_01": {
"name": "CastleClass_01",
"coalition": "blue",
"type": "Patrol",
"era": "Mid Cold War",
"label": "HMS Leeds Castle (P-258)",
"shortLabel": "HMS Leeds Castle (P-258)",
"range": "",
"filename": ""
},
"USS_Arleigh_Burke_IIa": {
"name": "USS_Arleigh_Burke_IIa",
"coalition": "blue",
"type": "Destroyer",
"era": "Late Cold War",
"label": "DDG Arleigh Burke lla",
"shortLabel": "DDG Arleigh Burke",
"range": "Medium",
"filename": ""
},
"barge-1": {
"name": "barge-1",
"coalition": "red",
"type": "Cargoship",
"era": "Late Cold War",
"label": "Dry cargo ship Ivanov",
"shortLabel": "Dry cargo ship Ivanov",
"range": "",
"filename": ""
},
"barge-2": {
"name": "barge-2",
"coalition": "red",
"type": "Cargoship",
"era": "Late Cold War",
"label": "Dry cargo ship Yakushev",
"shortLabel": "Dry cargo ship Yakushev",
"range": "",
"filename": ""
},
"elnya": {
"name": "elnya",
"coalition": "red",
"type": "Tanker",
"era": "Late Cold War",
"label": "Elnya tanker",
"shortLabel": "Elnya tanker",
"range": "",
"filename": ""
},
"La_Combattante_II": {
"name": "La_Combattante_II",
"coalition": "blue",
"type": "Fast Attack Craft",
"era": "Mid Cold War",
"label": "FAC La Combattante lla",
"shortLabel": "FAC La Combattante",
"range": "",
"filename": ""
},
"leander-gun-achilles": {
"name": "leander-gun-achilles",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"label": "HMS Achilles (F12)",
"shortLabel": "HMS Achilles",
"range": "",
"filename": ""
},
"leander-gun-andromeda": {
"name": "leander-gun-andromeda",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"label": "HMS Andromeda (F57)",
"shortLabel": "HMS Andromeda",
"range": "",
"filename": ""
},
"leander-gun-ariadne": {
"name": "leander-gun-ariadne",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"label": "HMS Ariadne (F72)",
"shortLabel": "HMS Ariadne",
"range": "",
"filename": ""
},
"leander-gun-lynch": {
"name": "leander-gun-lynch",
"coalition": "",
"type": "Frigate",
"era": "Mid Cold War",
"label": "CNS Almirante Lynch (PFG-07)",
"shortLabel": "CNS Almirante Lynch",
"range": "",
"filename": ""
},
"hms_invincible": {
"name": "hms_invincible",
"coalition": "blue",
"type": "Aircraft Carrier",
"era": "Mid Cold War",
"label": "HMS Invincible (R05)",
"shortLabel": "HMS Invincible",
"range": "",
"filename": ""
},
"HarborTug": {
"name": "HarborTug",
"coalition": "",
"type": "Tug",
"era": "Mid Cold War",
"label": "Harbor Tug",
"shortLabel": "Harbor Tug",
"range": "",
"filename": ""
},
"kilo_636": {
"name": "kilo_636",
"coalition": "red",
"type": "Submarine",
"era": "Late Cold War",
"label": "Project 636 Varshavyanka Improved",
"shortLabel": "Varshavyanka Improved",
"range": "Medium",
"filename": ""
},
"kilo": {
"name": "kilo",
"coalition": "red",
"type": "Submarine",
"era": "Late Cold War",
"label": "Project 636 Varshavyanka Basic",
"shortLabel": "Varshavyanka Basic",
"range": "Medium",
"filename": ""
},
"LHA_Tarawa": {
"name": "LHA_Tarawa",
"coalition": "blue",
"type": "Aircraft Carrier",
"era": "Mid Cold War",
"label": "LHA-1 Tarawa",
"shortLabel": "LHA-1 Tarawa",
"range": "Short",
"filename": ""
},
"BDK-775": {
"name": "BDK-775",
"coalition": "blue",
"type": "Landing Craft",
"era": "Mid Cold War",
"label": "LS Ropucha",
"shortLabel": "LS Ropucha",
"range": "",
"filename": ""
},
"molniya": {
"name": "molniya",
"coalition": "",
"type": "Fast Attack Craft",
"era": "Late Cold War",
"label": "Molniya (Tarantul-3)",
"shortLabel": "Molniya",
"range": "Short",
"filename": ""
},
"moscow": {
"name": "moscow",
"coalition": "red",
"type": "Cruiser",
"era": "Late Cold War",
"label": "Moscow",
"shortLabel": "Moscow",
"range": "Medium",
"filename": ""
},
"neustrash": {
"name": "neustrash",
"coalition": "red",
"type": "Frigate",
"era": "Late Cold War",
"label": "Neustrashimy",
"shortLabel": "Neustrashimy",
"range": "Short",
"filename": ""
},
"perry": {
"name": "perry",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"label": "Oliver H. Perry",
"shortLabel": "Oliver H. Perry",
"range": "Medium",
"filename": ""
},
"piotr_velikiy": {
"name": "piotr_velikiy",
"coalition": "red",
"type": "Cruiser",
"era": "Late Cold War",
"label": "Pyotr Velikiy",
"shortLabel": "Pyotr Velikiy",
"range": "Medium",
"filename": ""
},
"rezky": {
"name": "Rezky (Krivak-2)",
"coalition": "red",
"type": "Frigate",
"era": "Early Cold War",
"label": "Rezky (Krivak-2)",
"shortLabel": "Rezky",
"range": "Short",
"filename": ""
},
"Ship_Tilde_Supply": {
"name": "Ship_Tilde_Supply",
"coalition": "blue",
"type": "Transport",
"era": "Late Cold War",
"label": "Supply Ship MV Tilde",
"shortLabel": "Supply Ship Tilde",
"range": "",
"filename": ""
},
"Seawise_Giant": {
"name": "Seawise_Giant",
"coalition": "blue",
"type": "Tanker",
"era": "Late Cold War",
"label": "Tanker Seawise Giant",
"shortLabel": "Seawise Giant",
"range": "",
"filename": ""
},
"TICONDEROG": {
"name": "TICONDEROG",
"coalition": "blue",
"type": "Cruiser",
"era": "Late Cold War",
"label": "Ticonderoga",
"shortLabel": "Ticonderoga",
"range": "Medium",
"filename": ""
},
"zwezdny": {
"name": "zwezdny",
"coalition": "",
"type": "Civilian Boat",
"era": "Modern",
"label": "Zwezdny",
"shortLabel": "Zwezdny",
"range": "",
"filename": ""
}
}
}
getSpawnPointsByName(name: string) {
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns)
return 0;
const blueprint = this.getByName(name);
if (blueprint?.era == "WW2")
return 20;
else if (blueprint?.era == "Early Cold War")
return 50;
else if (blueprint?.era == "Mid Cold War")
return 100;
else if (blueprint?.era == "Late Cold War")
return 200;
else if (blueprint?.era == "Modern")
return 400;
return 0;
}
getCategory() {
return "NavyUnit";
}
}
export var navyUnitDatabase = new NavyUnitDatabase();

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,7 @@
import { LatLng } from "leaflet";
import { getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER } from "../constants/constants";
export class UnitDatabase { export class UnitDatabase {
blueprints: { [key: string]: UnitBlueprint } = {}; blueprints: { [key: string]: UnitBlueprint } = {};
@@ -5,18 +9,8 @@ export class UnitDatabase {
} }
/* Returns a list of all possible roles in a database */ getCategory() {
getRoles() { return "";
var roles: string[] = [];
for (let unit in this.blueprints) {
for (let loadout of this.blueprints[unit].loadouts) {
for (let role of loadout.roles) {
if (role !== "" && !roles.includes(role))
roles.push(role);
}
}
}
return roles;
} }
/* Gets a specific blueprint by name */ /* Gets a specific blueprint by name */
@@ -35,14 +29,113 @@ export class UnitDatabase {
return null; return null;
} }
getBlueprints() {
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns)
return this.blueprints;
else {
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
for (let unit in this.blueprints) {
const blueprint = this.blueprints[unit];
if (this.getSpawnPointsByName(blueprint.name) <= getMissionHandler().getAvailableSpawnPoints() &&
getMissionHandler().getCommandModeOptions().eras.includes(blueprint.era) &&
(!getMissionHandler().getCommandModeOptions().restrictToCoalition || blueprint.coalition === getMissionHandler().getCommandedCoalition())) {
filteredBlueprints[unit] = blueprint;
}
}
return filteredBlueprints;
}
}
/* Returns a list of all possible roles in a database */
getRoles() {
var roles: string[] = [];
var filteredBlueprints = this.getBlueprints();
for (let unit in filteredBlueprints) {
var loadouts = filteredBlueprints[unit].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
for (let role of loadout.roles) {
if (role !== "" && !roles.includes(role))
roles.push(role);
}
}
}
}
return roles;
}
/* Returns a list of all possible types in a database */
getTypes() {
var filteredBlueprints = this.getBlueprints();
var types: string[] = [];
for (let unit in filteredBlueprints) {
var type = filteredBlueprints[unit].type;
if (type && type !== "" && !types.includes(type))
types.push(type);
}
return types;
}
/* Returns a list of all possible periods in a database */
getEras() {
var filteredBlueprints = this.getBlueprints();
var eras: string[] = [];
for (let unit in filteredBlueprints) {
var era = filteredBlueprints[unit].era;
if (era && era !== "" && !eras.includes(era))
eras.push(era);
}
return eras;
}
/* Returns a list of all possible ranges in a database */
getRanges() {
var filteredBlueprints = this.getBlueprints();
var ranges: string[] = [];
for (let unit in filteredBlueprints) {
var range = filteredBlueprints[unit].range;
if (range && range !== "" && !ranges.includes(range))
ranges.push(range);
}
return ranges;
}
/* Get all blueprints by range */
getByRange(range: string) {
var filteredBlueprints = this.getBlueprints();
var unitswithrange = [];
for (let unit in filteredBlueprints) {
if (filteredBlueprints[unit].range === range) {
unitswithrange.push(filteredBlueprints[unit]);
}
}
return unitswithrange;
}
/* Get all blueprints by type */
getByType(type: string) {
var filteredBlueprints = this.getBlueprints();
var units = [];
for (let unit in filteredBlueprints) {
if (filteredBlueprints[unit].type === type) {
units.push(filteredBlueprints[unit]);
}
}
return units;
}
/* Get all blueprints by role */ /* Get all blueprints by role */
getByRole(role: string) { getByRole(role: string) {
var filteredBlueprints = this.getBlueprints();
var units = []; var units = [];
for (let unit in this.blueprints) { for (let unit in filteredBlueprints) {
for (let loadout of this.blueprints[unit].loadouts) { var loadouts = filteredBlueprints[unit].loadouts;
if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) { if (loadouts) {
units.push(this.blueprints[unit]) for (let loadout of loadouts) {
break; if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) {
units.push(filteredBlueprints[unit])
break;
}
} }
} }
} }
@@ -51,21 +144,53 @@ export class UnitDatabase {
/* Get the names of all the loadouts for a specific unit and for a specific role */ /* Get the names of all the loadouts for a specific unit and for a specific role */
getLoadoutNamesByRole(name: string, role: string) { getLoadoutNamesByRole(name: string, role: string) {
var loadouts = []; var filteredBlueprints = this.getBlueprints();
for (let loadout of this.blueprints[name].loadouts) { var loadoutsByRole = [];
if (loadout.roles.includes(role) || loadout.roles.includes("")) { var loadouts = filteredBlueprints[name].loadouts;
loadouts.push(loadout.name) if (loadouts) {
for (let loadout of loadouts) {
if (loadout.roles.includes(role) || loadout.roles.includes("")) {
loadoutsByRole.push(loadout.name)
}
} }
} }
return loadouts; return loadoutsByRole;
} }
/* Get the loadout content from the unit name and loadout name */ /* Get the loadout content from the unit name and loadout name */
getLoadoutByName(name: string, loadoutName: string) { getLoadoutByName(name: string, loadoutName: string) {
for (let loadout of this.blueprints[name].loadouts) { var loadouts = this.blueprints[name].loadouts;
if (loadout.name === loadoutName) if (loadouts) {
return loadout; for (let loadout of loadouts) {
if (loadout.name === loadoutName)
return loadout;
}
} }
return null; return null;
} }
generateTestGrid(initialPosition: LatLng) {
var filteredBlueprints = this.getBlueprints();
const step = 0.01;
var nUnits = Object.values(filteredBlueprints).length;
var gridSize = Math.ceil(Math.sqrt(nUnits));
Object.values(filteredBlueprints).forEach((unitBlueprint: UnitBlueprint, idx: number) => {
var row = Math.floor(idx / gridSize);
var col = idx - row * gridSize;
var location = new LatLng(initialPosition.lat + col * step, initialPosition.lng + row * step)
getUnitsManager().spawnUnits(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: ""}]);
})
}
getSpawnPointsByLabel(label: string) {
var blueprint = this.getByLabel(label);
if (blueprint)
return this.getSpawnPointsByName(blueprint.name);
else
return Infinity;
}
getSpawnPointsByName(name: string) {
return Infinity;
}
} }

View File

@@ -1,16 +1,25 @@
import { LatLng, LatLngBounds } from "leaflet"; import { LatLng, LatLngBounds } from "leaflet";
import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from ".."; import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsManager } from "..";
import { Unit } from "./unit"; import { Unit } from "./unit";
import { cloneUnit } from "../server/server"; import { cloneUnit, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils"; import { bearingAndDistanceToLatLng, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils";
import { IDLE, MOVE_UNIT } from "../map/map"; import { CoalitionArea } from "../map/coalitionarea";
import { groundUnitDatabase } from "./groundunitdatabase";
import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT, NONE } from "../constants/constants";
import { DataExtractor } from "./dataextractor";
import { Contact } from "../@types/unit";
import { citiesDatabase } from "./citiesdatabase";
import { aircraftDatabase } from "./aircraftdatabase";
import { helicopterDatabase } from "./helicopterdatabase";
import { navyUnitDatabase } from "./navyunitdatabase";
export class UnitsManager { export class UnitsManager {
#units: { [ID: number]: Unit }; #units: { [ID: number]: Unit };
#copiedUnits: Unit[]; #copiedUnits: any[];
#selectionEventDisabled: boolean = false; #selectionEventDisabled: boolean = false;
#pasteDisabled: boolean = false; #pasteDisabled: boolean = false;
#hiddenTypes: string[] = []; #hiddenTypes: string[] = [];
#requestDetectionUpdate: boolean = false;
constructor() { constructor() {
this.#units = {}; this.#units = {};
@@ -23,13 +32,16 @@ export class UnitsManager {
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete()); document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete());
document.addEventListener('explodeSelectedUnits', () => this.selectedUnitsDelete(true)); document.addEventListener('explodeSelectedUnits', () => this.selectedUnitsDelete(true));
document.addEventListener('keyup', (event) => this.#onKeyUp(event)); document.addEventListener('keyup', (event) => this.#onKeyUp(event));
document.addEventListener('exportToFile', () => this.exportToFile());
document.addEventListener('importFromFile', () => this.importFromFile());
document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true});
document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility())});
} }
getSelectableAircraft() { getSelectableAircraft() {
const units = this.getUnits(); const units = this.getUnits();
return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => { return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => {
const baseData = units[unitId].getBaseData(); if (units[unitId].getCategory() === "Aircraft" && units[unitId].getAlive() === true) {
if (baseData.category === "Aircraft" && baseData.alive === true) {
acc[unitId] = units[unitId]; acc[unitId] = units[unitId];
} }
return acc; return acc;
@@ -48,15 +60,15 @@ export class UnitsManager {
} }
getUnitsByHotgroup(hotgroup: number) { getUnitsByHotgroup(hotgroup: number) {
return Object.values(this.#units).filter((unit: Unit) => { return unit.getBaseData().alive && unit.getHotgroup() == hotgroup }); return Object.values(this.#units).filter((unit: Unit) => { return unit.getAlive() && unit.getHotgroup() == hotgroup });
} }
addUnit(ID: number, data: UnitData) { addUnit(ID: number, category: string) {
if (data.baseData && data.baseData.category){ if (category){
/* The name of the unit category is exactly the same as the constructor name */ /* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.baseData.category); var constructor = Unit.getConstructor(category);
if (constructor != undefined) { if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data); this.#units[ID] = new constructor(ID);
} }
} }
} }
@@ -65,30 +77,40 @@ export class UnitsManager {
} }
update(data: UnitsData) { update(buffer: ArrayBuffer) {
var updatedUnits: Unit[] = []; var dataExtractor = new DataExtractor(buffer);
Object.keys(data.units) var updateTime = Number(dataExtractor.extractUInt64());
.filter((ID: string) => !(ID in this.#units)) var requestRefresh = false;
.reduce((timeout: number, ID: string) => { while (dataExtractor.getSeekPosition() < buffer.byteLength) {
window.setTimeout(() => { const ID = dataExtractor.extractUInt32();
if (!(ID in this.#units)) if (!(ID in this.#units)) {
this.addUnit(parseInt(ID), data.units[ID]); const datumIndex = dataExtractor.extractUInt8();
this.#units[parseInt(ID)]?.setData(data.units[ID]); if (datumIndex == DataIndexes.category) {
}, timeout); const category = dataExtractor.extractString();
return timeout + 10; this.addUnit(ID, category);
}, 10); }
else {
requestRefresh = true;
}
}
this.#units[ID]?.setData(dataExtractor);
}
Object.keys(data.units) if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
.filter((ID: string) => ID in this.#units) for (let ID in this.#units) {
.forEach((ID: string) => { var unit = this.#units[ID];
updatedUnits.push(this.#units[parseInt(ID)]); if (!unit.belongsToCommandedCoalition())
this.#units[parseInt(ID)]?.setData(data.units[ID]) unit.setDetectionMethods(this.getUnitDetectedMethods(unit));
}); }
this.#requestDetectionUpdate = false;
}
this.getSelectedUnits().forEach((unit: Unit) => { for (let ID in this.#units) {
if (!updatedUnits.includes(unit)) if (this.#units[ID].getSelected())
unit.setData({}) this.#units[ID].drawLines();
}); };
return updateTime;
} }
setHiddenType(key: string, value: boolean) { setHiddenType(key: string, value: boolean) {
@@ -115,7 +137,7 @@ export class UnitsManager {
this.deselectAllUnits(); this.deselectAllUnits();
for (let ID in this.#units) { for (let ID in this.#units) {
if (this.#units[ID].getHidden() == false) { if (this.#units[ID].getHidden() == false) {
var latlng = new LatLng(this.#units[ID].getFlightData().latitude, this.#units[ID].getFlightData().longitude); var latlng = new LatLng(this.#units[ID].getPosition().lat, this.#units[ID].getPosition().lng);
if (bounds.contains(latlng)) { if (bounds.contains(latlng)) {
this.#units[ID].setSelected(true); this.#units[ID].setSelected(true);
} }
@@ -132,11 +154,11 @@ export class UnitsManager {
} }
if (options) { if (options) {
if (options.excludeHumans) if (options.excludeHumans)
selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getMissionData().flags.Human }); selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getHuman() });
if (options.onlyOnePerGroup) { if (options.onlyOnePerGroup) {
var temp: Unit[] = []; var temp: Unit[] = [];
for (let unit of selectedUnits) { for (let unit of selectedUnits) {
if (!temp.some((otherUnit: Unit) => unit.getBaseData().groupName == otherUnit.getBaseData().groupName)) if (!temp.some((otherUnit: Unit) => unit.getGroupName() == otherUnit.getGroupName()))
temp.push(unit); temp.push(unit);
} }
selectedUnits = temp; selectedUnits = temp;
@@ -157,36 +179,59 @@ export class UnitsManager {
} }
getSelectedUnitsTypes() { getSelectedUnitsTypes() {
if (this.getSelectedUnits().length == 0) const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
return []; return [];
return this.getSelectedUnits().map((unit: Unit) => { return selectedUnits.map((unit: Unit) => {
return unit.constructor.name return unit.getCategory();
})?.filter((value: any, index: any, array: string[]) => { })?.filter((value: any, index: any, array: string[]) => {
return array.indexOf(value) === index; return array.indexOf(value) === index;
}); });
}; };
/* Gets the value of a variable from the selected units. If all the units have the same value, returns the value, else returns undefined */
getSelectedUnitsVariable(variableGetter: CallableFunction) { getSelectedUnitsVariable(variableGetter: CallableFunction) {
if (this.getSelectedUnits().length == 0) const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
return undefined; return undefined;
return this.getSelectedUnits().map((unit: Unit) => { return selectedUnits.map((unit: Unit) => {
return variableGetter(unit); return variableGetter(unit);
})?.reduce((a: any, b: any) => { })?.reduce((a: any, b: any) => {
return a == b ? a : undefined return a === b ? a : undefined
}); });
}; };
getSelectedUnitsCoalition() { getSelectedUnitsCoalition() {
if (this.getSelectedUnits().length == 0) const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
return undefined; return undefined;
return this.getSelectedUnits().map((unit: Unit) => { return selectedUnits.map((unit: Unit) => {
return unit.getMissionData().coalition return unit.getCoalition()
})?.reduce((a: any, b: any) => { })?.reduce((a: any, b: any) => {
return a == b ? a : undefined return a == b ? a : undefined
}); });
}; };
getByType(type: string) {
Object.values(this.getUnits()).filter((unit: Unit) => {
return unit.getType() === type;
})
}
getUnitDetectedMethods(unit: Unit) {
var detectionMethods: number[] = [];
for (let idx in this.#units) {
if (this.#units[idx].getAlive() && this.#units[idx].getIsLeader() && this.#units[idx].getCoalition() !== "neutral" && this.#units[idx].getCoalition() != unit.getCoalition())
{
this.#units[idx].getContacts().forEach((contact: Contact) => {
if (contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod))
detectionMethods.push(contact.detectionMethod);
});
}
}
return detectionMethods;
}
/*********************** Actions on selected units ************************/ /*********************** Actions on selected units ************************/
selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) { selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
@@ -196,13 +241,13 @@ export class UnitsManager {
if (mantainRelativePosition) if (mantainRelativePosition)
unitDestinations = this.selectedUnitsComputeGroupDestination(latlng, rotation); unitDestinations = this.selectedUnitsComputeGroupDestination(latlng, rotation);
else else
selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng }); selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng; });
for (let idx in selectedUnits) { for (let idx in selectedUnits) {
const unit = selectedUnits[idx]; const unit = selectedUnits[idx];
/* If a unit is following another unit, and that unit is also selected, send the command to the followed unit */ /* If a unit is following another unit, and that unit is also selected, send the command to the followed unit */
if (unit.getTaskData().currentState === "Follow") { if (unit.getState() === "Follow") {
const leader = this.getUnitByID(unit.getFormationData().leaderID) const leader = this.getUnitByID(unit.getLeaderID())
if (leader && leader.getSelected()) if (leader && leader.getSelected())
leader.addDestination(latlng); leader.addDestination(latlng);
else else
@@ -221,8 +266,8 @@ export class UnitsManager {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
for (let idx in selectedUnits) { for (let idx in selectedUnits) {
const unit = selectedUnits[idx]; const unit = selectedUnits[idx];
if (unit.getTaskData().currentState === "Follow") { if (unit.getState() === "Follow") {
const leader = this.getUnitByID(unit.getFormationData().leaderID) const leader = this.getUnitByID(unit.getLeaderID())
if (leader && leader.getSelected()) if (leader && leader.getSelected())
leader.clearDestinations(); leader.clearDestinations();
else else
@@ -333,21 +378,25 @@ export class UnitsManager {
for (let idx in selectedUnits) { for (let idx in selectedUnits) {
selectedUnits[idx].attackUnit(ID); selectedUnits[idx].attackUnit(ID);
} }
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getUnitName()}`);
} }
selectedUnitsDelete(explosion: boolean = false) { selectedUnitsDelete(explosion: boolean = false) {
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */ var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => { const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => {
return unit.getMissionData().flags.Human === true; return unit.getHuman() === true;
}); });
if (selectionContainsAHuman && !confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) { if (selectionContainsAHuman && !confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) {
return; return;
} }
var immediate = false;
if (selectedUnits.length > 20)
immediate = confirm(`You are trying to delete ${selectedUnits.length} units, do you want to delete them immediately? This may cause lag for players.`)
for (let idx in selectedUnits) { for (let idx in selectedUnits) {
selectedUnits[idx].delete(explosion); selectedUnits[idx].delete(explosion, immediate);
} }
this.#showActionMessage(selectedUnits, `deleted`); this.#showActionMessage(selectedUnits, `deleted`);
} }
@@ -400,7 +449,7 @@ export class UnitsManager {
} }
count++; count++;
} }
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getUnitName()}`);
} }
selectedUnitsSetHotgroup(hotgroup: number) { selectedUnitsSetHotgroup(hotgroup: number) {
@@ -422,7 +471,7 @@ export class UnitsManager {
/* Compute the center of the group */ /* Compute the center of the group */
var center = { x: 0, y: 0 }; var center = { x: 0, y: 0 };
selectedUnits.forEach((unit: Unit) => { selectedUnits.forEach((unit: Unit) => {
var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude); var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng);
center.x += mercator.x / selectedUnits.length; center.x += mercator.x / selectedUnits.length;
center.y += mercator.y / selectedUnits.length; center.y += mercator.y / selectedUnits.length;
}); });
@@ -430,7 +479,7 @@ export class UnitsManager {
/* Compute the distances from the center of the group */ /* Compute the distances from the center of the group */
var unitDestinations: { [key: number]: LatLng } = {}; var unitDestinations: { [key: number]: LatLng } = {};
selectedUnits.forEach((unit: Unit) => { selectedUnits.forEach((unit: Unit) => {
var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude); var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng);
var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y }; var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y };
/* Rotate the distance according to the group rotation */ /* Rotate the distance according to the group rotation */
@@ -480,29 +529,190 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `unit bombing point`); this.#showActionMessage(selectedUnits, `unit bombing point`);
} }
// TODO add undo group
selectedUnitsCreateGroup() {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: false });
var units = [];
var coalition = "neutral";
for (let idx in selectedUnits) {
var unit = selectedUnits[idx];
coalition = unit.getCoalition();
deleteUnit(unit.ID, false, true);
units.push({unitType: unit.getName(), location: unit.getPosition()});
}
const category = this.getSelectedUnitsTypes()[0];
this.spawnUnits(category, units, coalition, true);
}
/***********************************************/ /***********************************************/
copyUnits() { copyUnits() {
this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */ this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => {return unit.getData()}))); /* Can be applied to humans too */
this.#showActionMessage(this.#copiedUnits, `copied`); getInfoPopup().setText(`${this.#copiedUnits.length} units copied`);
} }
pasteUnits() { pasteUnits() {
if (!this.#pasteDisabled) { if (!this.#pasteDisabled && getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
/* Compute the position of the center of the copied units */
var nUnits = this.#copiedUnits.length;
var avgLat = 0;
var avgLng = 0;
for (let idx in this.#copiedUnits) { for (let idx in this.#copiedUnits) {
var unit = this.#copiedUnits[idx]; var unit = this.#copiedUnits[idx];
getMap().addTemporaryMarker(getMap().getMouseCoordinates()); avgLat += unit.position.lat / nUnits;
cloneUnit(unit.ID, getMap().getMouseCoordinates()); avgLng += unit.position.lng / nUnits;
this.#showActionMessage(this.#copiedUnits, `pasted`);
} }
this.#pasteDisabled = true;
window.setTimeout(() => this.#pasteDisabled = false, 250); /* Organize the copied units in groups */
var groups: {[key: string]: any} = {};
this.#copiedUnits.forEach((unit: any) => {
if (!(unit.groupName in groups))
groups[unit.groupName] = [];
groups[unit.groupName].push(unit);
});
for (let groupName in groups) {
/* Paste the units as groups. Only for ground and navy units because of loadouts, TODO: find a better solution so it works for them too*/
if (!["Aircraft", "Helicopter"].includes(groups[groupName][0].category)) {
var units = groups[groupName].map((unit: any) => {
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
getMap().addTemporaryMarker(position, unit.name, unit.coalition);
return {unitType: unit.name, location: position};
});
this.spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
}
else {
groups[groupName].forEach((unit: any) => {
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
getMap().addTemporaryMarker(position, unit.name, unit.coalition);
cloneUnit(unit.ID, position);
});
}
}
getInfoPopup().setText(`${this.#copiedUnits.length - 1} units pasted`);
}
else {
getInfoPopup().setText(`Unit cloning is disabled in ${getMissionHandler().getCommandModeOptions().commandMode} mode`);
}
}
createIADS(coalitionArea: CoalitionArea, types: {[key: string]: boolean}, eras: {[key: string]: boolean}, ranges: {[key: string]: boolean}, density: number, distribution: number) {
const activeTypes = Object.keys(types).filter((key: string) => { return types[key]; });
const activeEras = Object.keys(eras).filter((key: string) => { return eras[key]; });
const activeRanges = Object.keys(ranges).filter((key: string) => { return ranges[key]; });
citiesDatabase.forEach((city: {lat: number, lng: number, pop: number}) => {
if (polyContains(new LatLng(city.lat, city.lng), coalitionArea)) {
var pointsNumber = 2 + Math.pow(city.pop, 0.2) * density / 100;
for (let i = 0; i < pointsNumber; i++) {
var bearing = Math.random() * 360;
var distance = Math.random() * distribution * 100;
const latlng = bearingAndDistanceToLatLng(city.lat, city.lng, bearing, distance);
if (polyContains(latlng, coalitionArea)) {
const type = activeTypes[Math.floor(Math.random() * activeTypes.length)];
if (Math.random() < IADSDensities[type]) {
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, {type: type, eras: activeEras, ranges: activeRanges});
if (unitBlueprint) {
this.spawnUnits("GroundUnit", [{unitType: unitBlueprint.name, location: latlng}], coalitionArea.getCoalition(), true);
getMap().addTemporaryMarker(latlng, unitBlueprint.name, coalitionArea.getCoalition());
}
}
}
}
}
})
}
exportToFile() {
var unitsToExport: {[key: string]: any} = {};
for (let ID in this.#units) {
var unit = this.#units[ID];
if (!["Aircraft", "Helicopter"].includes(unit.getCategory())) {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
else
unitsToExport[unit.getGroupName()] = [data];
}
}
var a = document.createElement("a");
var file = new Blob([JSON.stringify(unitsToExport)], {type: 'text/plain'});
a.href = URL.createObjectURL(file);
a.download = 'export.json';
a.click();
}
importFromFile() {
var input = document.createElement("input");
input.type = "file";
input.addEventListener("change", (e: any) => {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function(e: any) {
var contents = e.target.result;
var groups = JSON.parse(contents);
for (let groupName in groups) {
if (groupName !== "" && groups[groupName].length > 0 && groups[groupName].every((unit: any) => {return unit.category == "GroundUnit";})) {
var units = groups[groupName].map((unit: any) => {return {unitType: unit.name, location: unit.position}});
getUnitsManager().spawnUnits("GroundUnit", units, groups[groupName][0].coalition, true);
}
}
};
reader.readAsText(file);
})
input.click();
}
spawnUnits(category: string, units: any, coalition: string = "blue", immediate: boolean = true, airbase: string = "") {
var spawnPoints = 0;
if (category === "Aircraft") {
if (airbase == "" && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
getInfoPopup().setText("Aircrafts can be air spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnAircrafts(units, coalition, airbase, immediate, spawnPoints);
} else if (category === "Helicopter") {
if (airbase == "" && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
getInfoPopup().setText("Helicopters can be air spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnHelicopters(units, coalition, airbase, immediate, spawnPoints);
} else if (category === "GroundUnit") {
if (getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
getInfoPopup().setText("Ground units can be spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnGroundUnits(units, coalition, immediate, spawnPoints);
} else if (category === "NavyUnit") {
if (getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
getInfoPopup().setText("Navy units can be spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnNavyUnits(units, coalition, immediate, spawnPoints);
}
if (spawnPoints <= getMissionHandler().getAvailableSpawnPoints()) {
getMissionHandler().setSpentSpawnPoints(spawnPoints);
return true;
} else {
getInfoPopup().setText("Not enough spawn points available!");
return false;
} }
} }
/***********************************************/ /***********************************************/
#onKeyUp(event: KeyboardEvent) { #onKeyUp(event: KeyboardEvent) {
if (!keyEventWasInInput(event) && event.key === "Delete" ) { if (!keyEventWasInInput(event)) {
this.selectedUnitsDelete(); if (event.key === "Delete")
this.selectedUnitsDelete();
else if (event.key === "a" && event.ctrlKey)
Object.values(this.getUnits()).filter((unit: Unit) => {return !unit.getHidden()}).forEach((unit: Unit) => unit.setSelected(true));
} }
} }
@@ -533,10 +743,10 @@ export class UnitsManager {
document.dispatchEvent(new CustomEvent("unitsDeselection", { detail: this.getSelectedUnits() })); document.dispatchEvent(new CustomEvent("unitsDeselection", { detail: this.getSelectedUnits() }));
} }
#showActionMessage(units: Unit[], message: string) { #showActionMessage(units: any[], message: string) {
if (units.length == 1) if (units.length == 1)
getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`); getInfoPopup().setText(`${units[0].getUnitName()} ${message}`);
else if (units.length > 1) else if (units.length > 1)
getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`); getInfoPopup().setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
} }
} }

View File

@@ -24,8 +24,14 @@
<%- include('panels/unitinfo.ejs') %> <%- include('panels/unitinfo.ejs') %>
<%- include('panels/mouseinfo.ejs') %> <%- include('panels/mouseinfo.ejs') %>
<%- include('panels/connectionstatus.ejs') %> <%- include('panels/connectionstatus.ejs') %>
<%- include('panels/serverstatus.ejs') %>
<%- include('panels/hotgroup.ejs') %> <%- include('panels/hotgroup.ejs') %>
<%- include('panels/navbar.ejs') %> <%- include('panels/logpanel.ejs') %>
<div id="toolbar-container">
<%- include('toolbars/primary.ejs') %>
<%- include('toolbars/commandmode.ejs') %>
</div>
<%- include('other/dialogs.ejs') %> <%- include('other/dialogs.ejs') %>
<%- include('other/popups.ejs') %> <%- include('other/popups.ejs') %>

View File

@@ -1,3 +0,0 @@
<div id="log-panel" class="ol-panel">
<!-- Log entries go here -->
</div>

View File

@@ -1,17 +1,30 @@
<div id="map-contextmenu" oncontextmenu="return false;"> <div id="map-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
<div id="active-coalition-label" data-active-coalition="blue"></div> <div id="active-coalition-label" data-coalition="blue"></div>
<div id="upper-bar" class="ol-panel"> <div class="upper-bar ol-panel">
<div id="coalition-switch" class="ol-switch"></div> <div id="coalition-switch" class="ol-switch ol-coalition-switch"></div>
<button data-active-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="contextMenuShow" <button data-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "aircraft" }' class="unit-spawn-button"></button> data-on-click-params='{ "type": "aircraft" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
<button data-active-coalition="blue" id="ground-unit-spawn-button" title="Spawn ground unit" data-on-click="contextMenuShow" <button data-coalition="blue" id="helicopter-spawn-button" title="Spawn helicopter" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "ground-unit" }' class="unit-spawn-button"></button> data-on-click-params='{ "type": "helicopter" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/helicopter.svg" inject-svg></button>
<button data-active-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="contextMenuShow" <button data-coalition="blue" id="groundunit-spawn-button" title="Spawn ground unit" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "smoke" }' class="unit-spawn-button"></button> data-on-click-params='{ "type": "groundunit" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/groundunit.svg" inject-svg></button>
<button data-active-coalition="blue" id="explosion-spawn-button" title="Explosion" data-on-click="contextMenuShow" <button data-coalition="blue" id="coalition-area-button" title="Edit coalition area" data-on-click="editCoalitionArea"
data-on-click-params='{ "type": "explosion" }' class="unit-spawn-button"></button> class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/other/edit.svg" inject-svg></button>
<button data-coalition="blue" id="more-options-button" title="More options" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "more" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/more.svg" inject-svg></button>
</div> </div>
<div id="aircraft-spawn-menu" class="ol-panel hide"> <div id="more-options-button-bar" class="upper-bar ol-panel hide">
<div id="coalition-switch" class="ol-switch ol-coalition-switch"></div>
<button data-coalition="blue" id="navyunit-spawn-button" title="Spawn navy unit" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "navyunit" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/navyunit.svg" inject-svg></button>
<button data-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "smoke" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/smoke.svg" inject-svg></button>
<button data-coalition="blue" id="explosion-spawn-button" title="Explosion" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "explosion" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/explosion.svg" inject-svg></button>
<button data-coalition="blue" id="polygon-draw-button" title="Enter polygon draw mode" data-on-click="toggleCoalitionAreaDraw"
data-on-click-params='{"type": "polygon"}' class="ol-contexmenu-button"><img src="resources/theme/images/buttons/tools/draw-polygon-solid.svg" inject-svg></button>
</div>
<div id="aircraft-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
<div class="ol-select-container"> <div class="ol-select-container">
<div id="aircraft-role-options" class="ol-select"> <div id="aircraft-role-options" class="ol-select">
<div class="ol-select-value">Aircraft role</div> <div class="ol-select-value">Aircraft role</div>
@@ -21,8 +34,8 @@
</div> </div>
</div> </div>
<div class="ol-select-container"> <div class="ol-select-container">
<div id="aircraft-type-options" class="ol-select"> <div id="aircraft-label-options" class="ol-select">
<div class="ol-select-value">Aircraft type</div> <div class="ol-select-value">Aircraft name</div>
<div class="ol-select-options"> <div class="ol-select-options">
<div>Select role first</div> <div>Select role first</div>
<!-- This is where all the aircraft types buttons will be shown--> <!-- This is where all the aircraft types buttons will be shown-->
@@ -30,7 +43,7 @@
</div> </div>
</div> </div>
<div class="ol-select-container"> <div class="ol-select-container">
<div id="loadout-options" class="ol-select"> <div id="aircraft-loadout-options" class="ol-select">
<div class="ol-select-value">Loadout</div> <div class="ol-select-value">Loadout</div>
<div class="ol-select-options"> <div class="ol-select-options">
<div>Select type first</div> <div>Select type first</div>
@@ -38,6 +51,15 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ol-select-container contextmenu-options-container">
<div>Group members</div>
<div id="aircraft-count-options" class="ol-select">
<div class="ol-select-value"></div>
<div class="ol-select-options">
<!-- This is where all the aircraft count buttons will be shown-->
</div>
</div>
</div>
<div id="aircraft-spawn-altitude-slider" class="ol-slider-container flight-control-ol-slider"> <div id="aircraft-spawn-altitude-slider" class="ol-slider-container flight-control-ol-slider">
<dl class="ol-data-grid"> <dl class="ol-data-grid">
<dt> Spawn altitude <dt> Spawn altitude
@@ -49,45 +71,137 @@
<input type="range" min="0" max="100" value="0" class="ol-slider"> <input type="range" min="0" max="100" value="0" class="ol-slider">
</div> </div>
<div id="loadout-preview"> <div id="aircraft-loadout-preview">
<img id="unit-image" class="hide"> <img id="aircraft-unit-image" class="hide">
<div id="loadout-list"> <div id="aircraft-loadout-list">
</div> </div>
</div> </div>
<button class="deploy-unit-button" title="" data-active-coalition="blue" data-on-click="contextMenuDeployAircraft" disabled>Deploy unit</button> <button class="deploy-unit-button" title="" data-coalition="blue" data-on-click="contextMenuDeployAircrafts" disabled>Deploy unit</button>
</div> </div>
<div id="ground-unit-spawn-menu" class="ol-panel hide"> <div id="helicopter-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
<div class="ol-select-container"> <div class="ol-select-container">
<div id="ground-unit-role-options" class="ol-select"> <div id="helicopter-role-options" class="ol-select">
<div class="ol-select-value">Ground unit role</div> <div class="ol-select-value">Helicopter role</div>
<div class="ol-select-options"> <div class="ol-select-options">
<!-- This is where all the ground unit roles buttons will be shown--> <!-- This is where all the helicopter roles buttons will be shown-->
</div> </div>
</div> </div>
</div> </div>
<div class="ol-select-container"> <div class="ol-select-container">
<div id="ground-unit-type-options" class="ol-select"> <div id="helicopter-label-options" class="ol-select">
<div class="ol-select-value">Ground unit type</div> <div class="ol-select-value">Helicopter name</div>
<div class="ol-select-options"> <div class="ol-select-options">
<div>Select role first</div> <div>Select role first</div>
<!-- This is where all the ground unit types buttons will be shown--> <!-- This is where all the helicopter types buttons will be shown-->
</div> </div>
</div> </div>
</div> </div>
<button class="deploy-unit-button" title="" data-active-coalition="blue" data-on-click="contextMenuDeployGroundUnit" disabled>Deploy unit</button> <div class="ol-select-container">
<div id="helicopter-loadout-options" class="ol-select">
<div class="ol-select-value">Loadout</div>
<div class="ol-select-options">
<div>Select type first</div>
<!-- This is where all the helicopter loadouts buttons will be shown-->
</div>
</div>
</div>
<div class="ol-select-container contextmenu-options-container">
<div>Group members</div>
<div id="helicopter-count-options" class="ol-select">
<div class="ol-select-value"></div>
<div class="ol-select-options">
<!-- This is where all the helicopter count buttons will be shown-->
</div>
</div>
</div>
<div id="helicopter-spawn-altitude-slider" class="ol-slider-container flight-control-ol-slider">
<dl class="ol-data-grid">
<dt> Spawn altitude
</dt>
<dd>
<div class="ol-slider-value"></div>
</dd>
</dl>
<input type="range" min="0" max="100" value="0" class="ol-slider">
</div>
<div id="helicopter-loadout-preview">
<img id="helicopter-unit-image" class="hide">
<div id="helicopter-loadout-list">
</div>
</div>
<button class="deploy-unit-button" title="" data-coalition="blue" data-on-click="contextMenuDeployHelicopters" disabled>Deploy unit</button>
</div> </div>
<div id="smoke-spawn-menu" class="ol-panel hide"> <div id="groundunit-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
<div class="ol-select-container">
<div id="groundunit-type-options" class="ol-select">
<div class="ol-select-value">Ground unit type</div>
<div class="ol-select-options">
<!-- This is where all the groundunit roles buttons will be shown-->
</div>
</div>
</div>
<div class="ol-select-container">
<div id="groundunit-label-options" class="ol-select">
<div class="ol-select-value">Ground unit name</div>
<div class="ol-select-options">
<div>Select role first</div>
<!-- This is where all the groundunit types buttons will be shown-->
</div>
</div>
</div>
<div class="ol-select-container contextmenu-options-container">
<div>Group members</div>
<div id="groundunit-count-options" class="ol-select">
<div class="ol-select-value"></div>
<div class="ol-select-options">
<!-- This is where all the groundunit count buttons will be shown-->
</div>
</div>
</div>
<button class="deploy-unit-button" title="" data-coalition="blue" data-on-click="contextMenuDeployGroundUnits" disabled>Deploy unit</button>
</div>
<div id="navyunit-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
<div class="ol-select-container">
<div id="navyunit-type-options" class="ol-select">
<div class="ol-select-value">Navy unit type</div>
<div class="ol-select-options">
<!-- This is where all the navyunit roles buttons will be shown-->
</div>
</div>
</div>
<div class="ol-select-container">
<div id="navyunit-label-options" class="ol-select">
<div class="ol-select-value">Navy unit name</div>
<div class="ol-select-options">
<div>Select role first</div>
<!-- This is where all the navyunit types buttons will be shown-->
</div>
</div>
</div>
<div class="ol-select-container contextmenu-options-container">
<div>Group members</div>
<div id="navyunit-count-options" class="ol-select">
<div class="ol-select-value"></div>
<div class="ol-select-options">
<!-- This is where all the navyunit count buttons will be shown-->
</div>
</div>
</div>
<button class="deploy-unit-button" title="" data-coalition="blue" data-on-click="contextMenuDeployNavyUnits" disabled>Deploy unit</button>
</div>
<div id="smoke-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
<button class="smoke-button" title="" data-smoke-color="white" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "white" }'>White smoke</button> <button class="smoke-button" title="" data-smoke-color="white" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "white" }'>White smoke</button>
<button class="smoke-button" title="" data-smoke-color="blue" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "blue" }'>Blue smoke</button> <button class="smoke-button" title="" data-smoke-color="blue" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "blue" }'>Blue smoke</button>
<button class="smoke-button" title="" data-smoke-color="red" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "red" }'>Red smoke</button> <button class="smoke-button" title="" data-smoke-color="red" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "red" }'>Red smoke</button>
<button class="smoke-button" title="" data-smoke-color="green" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "green" }'>Green smoke</button> <button class="smoke-button" title="" data-smoke-color="green" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "green" }'>Green smoke</button>
<button class="smoke-button" title="" data-smoke-color="orange" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "orange" }'>Orange smoke</button> <button class="smoke-button" title="" data-smoke-color="orange" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "orange" }'>Orange smoke</button>
</div> </div>
<div id="explosion-menu" class="ol-panel hide"> <div id="explosion-menu" class="ol-panel ol-contexmenu-panel hide">
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 50 }'>Small explosion</button> <button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 1 }'>Small explosion</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 100 }'>Medium explosion</button> <button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 2 }'>Medium explosion</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 200 }'>Big explosion</button> <button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 3 }'>Big explosion</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 400 }'>Huge explosion</button> <button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 4 }'>Huge explosion</button>
</div> </div>
</div> </div>
@@ -96,9 +210,7 @@
</div> </div>
<div id="airbase-contextmenu" class="ol-panel" oncontextmenu="return false;"> <div id="airbase-contextmenu" class="ol-panel" oncontextmenu="return false;">
<h3 id="airbase-name"></h3> <h3 id="airbase-name"></h3>
<div id="airbase-properties"></div> <div id="airbase-properties"></div>
<hr /> <hr />
@@ -106,7 +218,68 @@
<h4>Parking available:</h4> <h4>Parking available:</h4>
<div id="airbase-parking"></div> <div id="airbase-parking"></div>
<button id="spawn-airbase-aircraft-button" data-active-coalition="red" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button> <button id="spawn-airbase-aircraft-button" data-coalition="blue" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button>
<button id="land-here-button" title="Land here" data-on-click="contextMenuLandAirbase" class="hide">Land here</button> <button id="land-here-button" title="Land here" data-on-click="contextMenuLandAirbase" class="hide">Land here</button>
</div>
<div id="coalition-area-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
<div id="area-coalition-label" data-coalition="blue"></div>
<div class="upper-bar ol-panel">
<div id="coalition-area-switch" class="ol-switch ol-coalition-switch"></div>
<button data-coalition="blue" id="iads-button" title="Create Integrated Air Defense System" data-on-click="coalitionAreaContextMenuShow"
data-on-click-params='{ "type": "iads" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/sam.svg" inject-svg></button>
<!--<button data-coalition="blue" id="cap-button" title="Create Combat Air Patrols" data-on-click="coalitionAreaContextMenuShow"
data-on-click-params='{ "type": "cap" }' class="ol-contexmenu-button"></button>-->
<button data-coalition="blue" id="coalitionarea-back-button" title="Bring area to back" data-on-click="coalitionAreaBringToBack"
class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/other/back.svg" inject-svg></button>
<button data-coalition="blue" id="coalitionarea-delete-button" title="Delete area" data-on-click="coalitionAreaDelete"
class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/other/delete.svg" inject-svg></button>
</div>
<div id="iads-menu" class="ol-panel ol-contexmenu-panel hide">
<div id="iads-units-type-options" class="ol-select">
<div class="ol-select-value">Unit types</div>
<div class="ol-select-options">
<!-- This is where all the iads unit types checkboxes will be shown-->
</div>
</div>
<div class="ol-select-container">
<div id="iads-era-options" class="ol-select">
<div class="ol-select-value">Units eras</div>
<div class="ol-select-options">
<!-- This is where all the iads unit era buttons will be shown-->
</div>
</div>
</div>
<div class="ol-select-container">
<div id="iads-range-options" class="ol-select">
<div class="ol-select-value">Units ranges</div>
<div class="ol-select-options">
<!-- This is where all the iads unit range buttons will be shown-->
</div>
</div>
</div>
<!--
<div id="coalition-units-checkbox" class="ol-checkbox">
<label title="Use coalition specific units only (e.g. Patriot sites for blue coalition, SA-2s for red coalition)">
<input type="checkbox"/>
Coalition specific units
</label>
</div>
-->
<div id="iads-density-slider" class="ol-slider-container">
<dl class="ol-data-grid">
<dt> IADS density </dt> <dd> <div class="ol-slider-value"></div> </dd>
</dl>
<input title="An high density value will cause more units to be deployed" type="range" min="0" max="100" value="0" class="ol-slider">
</div>
<div id="iads-distribution-slider" class="ol-slider-container">
<dl class="ol-data-grid">
<dt> IADS distribution </dt> <dd> <div class="ol-slider-value"></div> </dd>
</dl>
<input title="If distrubution is low units will be concentrated around towns, otherwise they will spread around the map more evenly" type="range" min="0" max="100" value="0" class="ol-slider">
</div>
<button class="create-iads-button" title="" data-coalition="blue" data-on-click="contextMenuCreateIads">Add units to IADS</button>
</div>
</div> </div>

View File

@@ -3,7 +3,7 @@
<div id="app-summary"> <div id="app-summary">
<h2>DCS Olympus</h2> <h2>DCS Olympus</h2>
<h4>Dynamic Unit Command</h4> <h4>Dynamic Unit Command</h4>
<div class="app-version">Version <span class="app-version-number">v0.3.0</span></div> <div class="app-version">Version <span class="app-version-number">v0.4.0-alpha</span></div>
</div> </div>
<div id="authentication-form"> <div id="authentication-form">
@@ -12,7 +12,7 @@
<button id="connection-button" class="ol-button-apply" data-on-click="tryConnection">Connect</button> <button id="connection-button" class="ol-button-apply" data-on-click="tryConnection">Connect</button>
</div> </div>
<h5 id="connection-status"><br></h5> <h5 id="login-status"><br></h5>
<div id="legal-stuff"> <div id="legal-stuff">
<h5>DISCLAIMER</h5> <h5>DISCLAIMER</h5>
@@ -183,7 +183,6 @@
</div> </div>
<div id="custom-formation-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;"> <div id="custom-formation-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div> <div class="ol-dialog-close" data-on-click="closeDialog"></div>
@@ -232,3 +231,75 @@
<button class="ol-button-close" data-on-click="closeDialog">Close</button> <button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div> </div>
</div> </div>
<div id="command-mode-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Command mode settings</h3>
</div>
<div class="ol-dialog-content">
<div id="restrict-spawns" class="ol-checkbox">
<label title="If false, no spawn restrictions will be applied">
<input type="checkbox"/>
Restrict spawns
</label>
</div>
<div id="restrict-to-coalition" class="ol-checkbox">
<label title="If true, commanders will be allowed to only spawn units that belong to their coalition. E.g. blue commanders will be able to spawn F/A-18 Hornets, but not MiG-29s.">
<input type="checkbox"/>
Restrict units to coalition
</label>
</div>
<div class="ol-group">
<label>Setup time: </label>
<div class="ol-group">
<div id="setup-time" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="10">
</div>
<label>minutes</label>
</div>
</div>
<div class="ol-group">
<label>Available eras: </label>
<div id="command-mode-era-options" class="ol-select">
<div class="ol-select-value">Select eras</div>
<div class="ol-select-options">
<!-- This is where all the available era buttons will be shown-->
</div>
</div>
</div>
<div class="ol-group">
<h4>Spawn points</h4>
<hr>
</div>
<div class="ol-group">
<label>Blue spawn points: </label>
<div id="blue-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
<div class="ol-group">
<label>Red spawn points: </label>
<div id="red-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applycommandModeOptions">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>

View File

@@ -0,0 +1,5 @@
<div id="log-panel" oncontextmenu="return false;">
<div class="ol-panel" data-on-click="toggleLogPanel"><div id="log-panel-header">Server log</div><img src="/resources/theme/images/icons/chevron-down.svg" inject-svg></div>
<div class="ol-panel ol-scrollable closed">
</div>
</div>

View File

@@ -0,0 +1,10 @@
<div id="server-status-panel" class="ol-panel" oncontextmenu="return false;">
<dl class="ol-data-grid">
<dt>Server frame rate:</dt>
<dd id="server-frame-rate"></dd>
</dl>
<dl class="ol-data-grid">
<dt>Olympus load:</dt>
<dd id="server-load"></dd>
</dl>
</div>

View File

@@ -0,0 +1,6 @@
<nav id="command-mode-toolbar" class="ol-panel hide" oncontextmenu="return false;">
<span id="command-mode"></span>
<div id="spawn-points-container">Spawn points<span id="spawn-points"></span></div>
<span id="command-mode-phase"></span>
<button id="command-mode-settings-button" class="ol-button" data-on-click="showCommandModeDialog"><img src="/resources/theme/images/icons/gears-solid.svg" inject-svg>Settings</button>
</nav>

View File

@@ -6,7 +6,7 @@
<div class="ol-select-options"> <div class="ol-select-options">
<div id="toolbar-summary"> <div id="toolbar-summary">
<h3>DCS Olympus</h3> <h3>DCS Olympus</h3>
<div class="accent-green app-version-number">version v0.3.0</div> <div class="accent-green app-version-number">version v0.4.0-alpha</div>
</div> </div>
<div> <div>
<a href="https://www.discord.com" target="_blank">Discord</a> <a href="https://www.discord.com" target="_blank">Discord</a>
@@ -14,53 +14,64 @@
<div> <div>
<a href="https://github.com/Pax1601/DCSOlympus" target="_blank">Github</a> <a href="https://github.com/Pax1601/DCSOlympus" target="_blank">Github</a>
</div> </div>
<div data-on-click="exportToFile">
<button>Export to file</button>
</div>
<div data-on-click="importFromFile">
<button>Import from file</button>
</div>
<div data-on-click="reloadPage"> <div data-on-click="reloadPage">
<a href="" target="_blank" data-on-click="reloadPage">Restart Olympus</a> <a href="" target="_blank" data-on-click="reloadPage">Restart Olympus</a>
</div> </div>
</div> </div>
</div> </div>
<div id="map-type" class="ol-select"> <div class="ol-group">
<div class="ol-select-value map-source-dropdown"> <div id="map-type" class="ol-select">
<span>ArcGIS Satellite</span> <div class="ol-select-value">
<img src="resources/theme/images/icons/map-source.svg" inject-svg> ArcGIS Satellite
</div>
<div class="ol-select-options">
<!-- Here the available map sources will be listed-->
</div>
</div> </div>
<div class="ol-select-options">
<!-- Here the available map sources will be listed--> <div id="map-visibility-options" class="ol-select">
<div class="ol-select-value"><img src="resources/theme/images/icons/eye-solid.svg" inject-svg>Options</div>
<div class="ol-select-options">
<!-- This is where the advanced visibility options will be listed -->
</div>
</div> </div>
</div> </div>
<div id="unit-visibility-control" class="ol-group"> <div id="unit-visibility-control" class="ol-group ol-navbar-buttons-group">
<!-- Here the available visibility controls will be listed --> <!-- Here the available visibility controls will be listed -->
</div> </div>
<div id="coalition-visibility-control" class="ol-group ol-group-button-toggle"> <div id="coalition-visibility-control" class="ol-group ol-group-button-toggle">
<div> <div>
<button id="coalition-visibility-control-blue" data-on-click="toggleCoalitionVisibility" <button id="coalition-visibility-control-blue" data-on-click="toggleCoalitionVisibility"
data-on-click-params='{ "coalition": "blue" }'>View <span class="accent-bluefor">BLUEFOR</span></button> data-on-click-params='{ "coalition": "blue" }'><span class="accent-bluefor">BLUEFOR</span></button>
</div> </div>
<div> <div>
<button id="coalition-visibility-control-red" data-on-click="toggleCoalitionVisibility" <button id="coalition-visibility-control-red" data-on-click="toggleCoalitionVisibility"
data-on-click-params='{ "coalition": "red" }'>View <span class="accent-redfor">REDFOR</span></button> data-on-click-params='{ "coalition": "red" }'><span class="accent-redfor">REDFOR</span></button>
</div> </div>
<div> <div>
<button id="coalition-visibility-control-neutral" data-on-click="toggleCoalitionVisibility" <button id="coalition-visibility-control-neutral" data-on-click="toggleCoalitionVisibility"
data-on-click-params='{ "coalition": "neutral" }'>View <span data-on-click-params='{ "coalition": "neutral" }'><span class="accent-neutral">NEUTRAL</span></button>
class="accent-neutral">NEUTRAL</span></button>
</div> </div>
</div> </div>
<!--
<div id="atc-navbar-control" class="ol-group-container" data-feature-switch="atc"> <div id="atc-navbar-control" class="ol-group-container ol-navbar-buttons-group" data-feature-switch="atc">
<div class="ol-group-header">ATC</div>
<div class="ol-group"> <div class="ol-group">
<button data-on-click="toggleElements" <button data-on-click="toggleElements"
data-on-click-params='{"selector": "#strip-board-ground"}'>GND</button> data-on-click-params='{"selector": "#strip-board-ground"}' class="off"><img src="resources/theme/images/buttons/tools/ground.svg" inject-svg></button>
<button data-on-click="toggleElements" <button data-on-click="toggleElements"
data-on-click-params='{"selector": "#strip-board-tower"}'>TWR</button> data-on-click-params='{"selector": "#strip-board-tower"}' class="off"><img src="resources/theme/images/buttons/tools/tower.svg" inject-svg></button>
</div> </div>
</div> </div> -->
</nav> </nav>

View File

@@ -1047,7 +1047,7 @@
<dt>Open air</dt> <dt>Open air</dt>
<dd>5</dd> <dd>5</dd>
</dl> </dl>
<button id="spawn-airbase-aircraft-button" data-active-coalition="red" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button> <button id="spawn-airbase-aircraft-button" data-coalition="red" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button>
<button id="land-here-button" title="Land here" data-on-click="contextMenuLandAirbase" class="hide">Land here</button> <button id="land-here-button" title="Land here" data-on-click="contextMenuLandAirbase" class="hide">Land here</button>
</div> </div>

View File

@@ -1,5 +1,5 @@
#define nwjsFolder "C:\Users\dpass\Documents\nwjs\" #define nwjsFolder "C:\Users\dpass\Documents\nwjs\"
#define version "v0.3.0-alpha" #define version "v0.4.0-alpha"
[Setup] [Setup]
AppName=DCS Olympus AppName=DCS Olympus

View File

@@ -15,7 +15,7 @@ declare_plugin(self_ID,
shortName = "Olympus", shortName = "Olympus",
fileMenuName = "Olympus", fileMenuName = "Olympus",
version = "0.1.1-alpha", version = "v0.4.0-alpha",
state = "installed", state = "installed",
developerName= "DCS Refugees 767 squadron", developerName= "DCS Refugees 767 squadron",
info = _("DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available."), info = _("DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available."),

View File

@@ -4,6 +4,8 @@
"port": 30000 "port": 30000
}, },
"authentication": { "authentication": {
"password": "password" "gameMasterPassword": "password",
"blueCommanderPassword": "bluepassword",
"redCommanderPassword": "redpassword"
} }
} }

View File

@@ -1,15 +1,22 @@
local version = "v0.3.0-alpha" local version = "v0.4.0-alpha"
local debug = false local debug = false
Olympus.unitCounter = 1 Olympus.unitCounter = 1
Olympus.payloadRegistry = {} Olympus.payloadRegistry = {}
Olympus.groupIndex = 0 Olympus.groupIndex = 0
Olympus.groupStep = 40 Olympus.groupStep = 5
Olympus.OlympusDLL = nil Olympus.OlympusDLL = nil
Olympus.DLLsloaded = false Olympus.DLLsloaded = false
Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\' Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\'
Olympus.log = mist.Logger:new("Olympus", 'info')
Olympus.missionData = {}
Olympus.unitsData = {}
Olympus.unitNames = {}
Olympus.missionStartTime = DCS.getRealTime()
function Olympus.debug(message, displayFor) function Olympus.debug(message, displayFor)
if debug == true then if debug == true then
@@ -252,7 +259,51 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
if groupCon then if groupCon then
groupCon:setTask(missionTask) groupCon:setTask(missionTask)
end end
Olympus.debug("Olympus.move executed successfully on a Aircraft", 2) Olympus.debug("Olympus.move executed successfully on Aircraft", 2)
elseif category == "Helicopter" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
if altitudeType == "AGL" then
altitude = land.getHeight({x = endPoint.x, y = endPoint.z}) + altitude
end
local path = {}
if taskOptions and taskOptions['id'] == 'Land' then
path = {
[1] = mist.heli.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.heli.buildWP(endPoint, landing, speed, 0, 'AGL')
}
else
path = {
[1] = mist.heli.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.heli.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
end
-- If a task exists assign it to the controller
if taskOptions then
local task = Olympus.buildEnrouteTask(taskOptions)
if task then
path[1].task = task
path[2].task = task
end
end
-- Assign the mission task to the controller
local missionTask = {
id = 'Mission',
params = {
route = {
points = mist.utils.deepCopy(path),
},
},
}
local groupCon = group:getController()
if groupCon then
groupCon:setTask(missionTask)
end
Olympus.debug("Olympus.move executed successfully on Helicopter", 2)
elseif category == "GroundUnit" then elseif category == "GroundUnit" then
vars = vars =
{ {
@@ -270,7 +321,17 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
end end
mist.groupToRandomPoint(vars) mist.groupToRandomPoint(vars)
Olympus.debug("Olympus.move executed succesfully on a ground unit", 2) Olympus.debug("Olympus.move executed succesfully on GroundUnit", 2)
elseif category == "NavyUnit" then
vars =
{
group = group,
point = coord.LLtoLO(lat, lng, 0),
heading = 0,
speed = speed
}
mist.groupToRandomPoint(vars)
Olympus.debug("Olympus.move executed succesfully on NavyUnit", 2)
else else
Olympus.debug("Olympus.move not implemented yet for " .. category, 2) Olympus.debug("Olympus.move not implemented yet for " .. category, 2)
end end
@@ -303,105 +364,84 @@ function Olympus.explosion(intensity, lat, lng)
trigger.action.explosion(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)), intensity) trigger.action.explosion(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)), intensity)
end end
-- Spawns a single ground unit -- Spawns a new unit or group
function Olympus.spawnGroundUnit(coalition, unitType, lat, lng) function Olympus.spawnUnits(spawnTable)
Olympus.debug("Olympus.spawnGroundUnit " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..")", 2) Olympus.debug("Olympus.spawnUnits " .. Olympus.serializeTable(spawnTable), 2)
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
local unitTable = {} local unitTable = nil
local route = nil
local category = nil
if Olympus.hasKey(templates, unitType) then if spawnTable.category == 'Aircraft' then
for idx, value in pairs(templates[unitType].units) do unitTable = Olympus.generateAirUnitsTable(spawnTable.units)
unitTable[#unitTable + 1] = { route = Olympus.generateAirUnitsRoute(spawnTable)
["type"] = value.name, category = 'plane'
["x"] = spawnLocation.x + value.dx, elseif spawnTable.category == 'Helicopter' then
["y"] = spawnLocation.z + value.dy, unitTable = Olympus.generateAirUnitsTable(spawnTable.units)
["playerCanDrive"] = true, route = Olympus.generateAirUnitsRoute(spawnTable)
["heading"] = 0, category = 'helicopter'
["skill"] = "High" elseif spawnTable.category == 'GroundUnit' then
} unitTable = Olympus.generateGroundUnitsTable(spawnTable.units)
end category = 'vehicle'
else elseif spawnTable.category == 'NavyUnit' then
unitTable = unitTable = Olympus.generateNavyUnitsTable(spawnTable.units)
{ category = 'ship'
[1] =
{
["type"] = unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["playerCanDrive"] = true,
["heading"] = 0,
["skill"] = "High"
},
}
end end
local countryID = Olympus.getCountryIDByCoalition(coalition) Olympus.debug(Olympus.serializeTable(unitTable), 5)
local countryID = Olympus.getCountryIDByCoalition(spawnTable.coalition)
local vars = local vars =
{ {
units = unitTable, units = unitTable,
country = countryID, country = countryID,
category = 'vehicle', category = category,
route = route,
name = "Olympus-" .. Olympus.unitCounter, name = "Olympus-" .. Olympus.unitCounter,
task = 'CAP'
} }
mist.dynAdd(vars) mist.dynAdd(vars)
Olympus.unitCounter = Olympus.unitCounter + 1 Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.debug("Olympus.spawnGround completed succesfully", 2) Olympus.debug("Olympus.spawnUnits completed succesfully", 2)
end end
-- Spawns a single aircraft. Spawn options are: -- Generates unit table for a air unit.
-- payloadName: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType function Olympus.generateAirUnitsTable(units)
-- airbaseName: a string, if present the aircraft will spawn on the ground of the selected airbase local unitTable = {}
-- payload: a table, if present the unit will receive this specific payload. Overrides payloadName for idx, unit in pairs(units) do
function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions) local loadout = unit.loadout -- loadout: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType
local payloadName = spawnOptions["payloadName"] local payload = unit.payload -- payload: a table, if present the unit will receive this specific payload. Overrides loadout
local airbaseName = spawnOptions["airbaseName"]
local payload = spawnOptions["payload"]
Olympus.debug("Olympus.spawnAircraft " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..", " .. alt .. ")", 2) if payload == nil then
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)) if loadout and loadout ~= "" and Olympus.unitPayloads[unit.unitType][loadout] then
payload = Olympus.unitPayloads[unit.unitType][loadout]
if payload == nil then else
if payloadName and payloadName ~= "" and Olympus.unitPayloads[unitType][payloadName] then payload = {}
payload = Olympus.unitPayloads[unitType][payloadName] end
else
payload = {}
end end
end
local countryID = Olympus.getCountryIDByCoalition(coalition) local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
unitTable[#unitTable + 1] =
local unitTable =
{
[1] =
{ {
["type"] = unitType, ["type"] = unit.unitType,
["x"] = spawnLocation.x, ["x"] = spawnLocation.x,
["y"] = spawnLocation.z, ["y"] = spawnLocation.z,
["alt"] = alt, ["alt"] = unit.alt,
["alt_type"] = "BARO", ["alt_type"] = "BARO",
["skill"] = "Excellent", ["skill"] = "Excellent",
["payload"] = ["payload"] = { ["pylons"] = payload, ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100, },
{
["pylons"] = payload,
["fuel"] = 999999,
["flare"] = 60,
["ammo_type"] = 1,
["chaff"] = 60,
["gun"] = 100,
},
["heading"] = 0, ["heading"] = 0,
["callsign"] = ["callsign"] = { [1] = 1, [2] = 1, [3] = 1, ["name"] = "Olympus" .. Olympus.unitCounter.. "-" .. #unitTable + 1 },
{ ["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1
[1] = 1, }
[2] = 1, end
[3] = 1, return unitTable
["name"] = "Olympus" .. Olympus.unitCounter, end
},
["name"] = "Olympus-" .. Olympus.unitCounter function Olympus.generateAirUnitsRoute(spawnTable)
}, local airbaseName = spawnTable.airbaseName -- airbaseName: a string, if present the aircraft will spawn on the ground of the selected airbase
} local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(spawnTable.units[1].lat, spawnTable.units[1].lng, 0))
-- If a airbase is provided the first waypoint is set as a From runway takeoff. -- If a airbase is provided the first waypoint is set as a From runway takeoff.
local route = {} local route = {}
@@ -416,10 +456,9 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions)
[1] = [1] =
{ {
["action"] = "From Parking Area Hot", ["action"] = "From Parking Area Hot",
["task"] = ["tasks"] = {
{ [1] = {["number"] = 1, ["auto"] = true, ["id"] = "WrappedAction", ["enabled"] = true, ["params"] = {["action"] = {["id"] = "EPLRS", ["params"] = {["value"] = true}, }, }, },
["id"] = "ComboTask", [2] = {["number"] = 2, ["auto"] = false, ["id"] = "Orbit", ["enabled"] = true, ["params"] = {["pattern"] = "Circle"}, },
["params"] = {["tasks"] = {},},
}, },
["type"] = "TakeOffParkingHot", ["type"] = "TakeOffParkingHot",
["ETA"] = 0, ["ETA"] = 0,
@@ -442,69 +481,86 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions)
{ {
["alt"] = alt, ["alt"] = alt,
["alt_type"] = "BARO", ["alt_type"] = "BARO",
["task"] = ["tasks"] = {
{ [1] = {["number"] = 1, ["auto"] = true, ["id"] = "WrappedAction", ["enabled"] = true, ["params"] = {["action"] = {["id"] = "EPLRS", ["params"] = {["value"] = true}, }, }, },
["id"] = "ComboTask", [2] = {["number"] = 2, ["auto"] = false, ["id"] = "Orbit", ["enabled"] = true, ["params"] = {["pattern"] = "Circle"}, },
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "EPLRS",
["params"] =
{
["value"] = true
},
},
},
},
[2] =
{
["number"] = 2,
["auto"] = false,
["id"] = "Orbit",
["enabled"] = true,
["params"] =
{
["pattern"] = "Circle"
},
},
},
},
}, },
["type"] = "Turning Point", ["type"] = "Turning Point",
["x"] = spawnLocation.x, ["x"] = spawnLocation.x,
["y"] = spawnLocation.z, ["y"] = spawnLocation.z,
}, -- end of [1] },
}, -- end of ["points"] },
} -- end of ["route"] }
end
return route
end
-- Generates ground units table, either single or from template
function Olympus.generateGroundUnitsTable(units)
local unitTable = {}
for idx, unit in pairs(units) do
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
if Olympus.hasKey(templates, unit.unitType) then
for idx, value in pairs(templates[unit.unitType].units) do
unitTable[#unitTable + 1] =
{
["type"] = value.name,
["x"] = spawnLocation.x + value.dx,
["y"] = spawnLocation.z + value.dy,
["heading"] = 0,
["skill"] = "High",
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1
}
end
else
unitTable[#unitTable + 1] =
{
["type"] = unit.unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["heading"] = 0,
["skill"] = "High",
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1
}
end
end end
local vars = return unitTable
{ end
units = unitTable,
country = countryID,
category = 'airplane',
name = "Olympus-" .. Olympus.unitCounter,
route = route,
task = 'CAP',
}
local newGroup = mist.dynAdd(vars) -- Generates navy units table, either single or from template
function Olympus.generateNavyUnitsTable(units)
local unitTable = {}
for idx, unit in pairs(units) do
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
if Olympus.hasKey(templates, unit.unitType) then
for idx, value in pairs(templates[unit.unitType].units) do
unitTable[#unitTable + 1] =
{
["type"] = value.name,
["x"] = spawnLocation.x + value.dx,
["y"] = spawnLocation.z + value.dy,
["heading"] = 0,
["skill"] = "High",
["name"] = "NavyUnit-" .. Olympus.unitCounter .. "-" .. #unitTable + 1,
["transportable"] = { ["randomTransportable"] = false }
}
end
else
unitTable[#unitTable + 1] =
{
["type"] = unit.unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["heading"] = 0,
["skill"] = "High",
["name"] = "NavyUnit-" .. Olympus.unitCounter .. "-" .. #unitTable + 1,
["transportable"] = { ["randomTransportable"] = false }
}
end
end
-- Save the payload to be reused in case the unit is cloned. TODO: save by ID not by name (it works but I like consistency) return unitTable
Olympus.payloadRegistry[vars.name] = payload
Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.debug("Olympus.spawnAir completed successfully", 2)
end end
-- Clones a unit by ID. Will clone the unit with the same original payload as the source unit. TODO: only works on Olympus unit not ME units. -- Clones a unit by ID. Will clone the unit with the same original payload as the source unit. TODO: only works on Olympus unit not ME units.
@@ -513,15 +569,21 @@ function Olympus.clone(ID, lat, lng, category)
local unit = Olympus.getUnitByID(ID) local unit = Olympus.getUnitByID(ID)
if unit then if unit then
local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition()) local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition())
-- TODO: understand category in this script
if category == "Aircraft" then local spawnTable = {
local spawnOptions = { coalition = coalition,
payload = Olympus.payloadRegistry[unit:getName()] category = category,
units = {
[1] = {
lat = lat,
lng = lng,
alt = unit:getPoint().y,
unitType = unit:getTypeName(),
payload = Olympus.payloadRegistry[unit:getName()]
}
} }
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, unit:getPoint().y, spawnOptions) }
elseif category == "GroundUnit" then Olympus.spawnUnits(spawnTable)
Olympus.spawnGroundUnit(coalition, unit:getTypeName(), lat, lng)
end
end end
Olympus.debug("Olympus.clone completed successfully", 2) Olympus.debug("Olympus.clone completed successfully", 2)
end end
@@ -650,22 +712,9 @@ function Olympus.hasKey(tab, key)
return false return false
end end
function Olympus.setMissionData(arg, time) function Olympus.setUnitsData(arg, time)
local missionData = {} -- Units data
local units = {}
-- Bullseye data
local bullseyes = {}
for i = 0, 2 do
local bullseyeVec3 = coalition.getMainRefPoint(i)
local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3)
bullseyes[i] = {}
bullseyes[i]["latitude"] = bullseyeLatitude
bullseyes[i]["longitude"] = bullseyeLongitude
bullseyes[i]["coalition"] = Olympus.getCoalitionByCoalitionID(i)
end
-- Units tactical data
local unitsData = {}
local startIndex = Olympus.groupIndex local startIndex = Olympus.groupIndex
local endIndex = startIndex + Olympus.groupStep local endIndex = startIndex + Olympus.groupStep
@@ -680,31 +729,74 @@ function Olympus.setMissionData(arg, time)
-- Get the targets detected by the group controller -- Get the targets detected by the group controller
local controller = group:getController() local controller = group:getController()
local controllerTargets = controller:getDetectedTargets() local controllerTargets = controller:getDetectedTargets()
local contacts = {}
for i, target in ipairs(controllerTargets) do
for det, enum in pairs(Controller.Detection) do
if target.object ~= nil then
target["detectionMethod"] = det
contacts[#contacts + 1] = target
end
end
end
-- Update the units position
for index, unit in pairs(group:getUnits()) do for index, unit in pairs(group:getUnits()) do
local unitController = unit:getController() local unitController = unit:getController()
local table = {} local table = {}
table["contacts"] = {} table["category"] = "None"
for i, target in ipairs(controllerTargets) do -- Get the object category in Olympus name
for det, enum in pairs(Controller.Detection) do local objectCategory = unit:getCategory()
if target.object ~= nil then if objectCategory == Object.Category.UNIT then
local detected = unitController:isTargetDetected(target.object, enum) if unit:getDesc().category == Unit.Category.AIRPLANE then
table["category"] = "Aircraft"
if detected then elseif unit:getDesc().category == Unit.Category.HELICOPTER then
target["detectionMethod"] = det table["category"] = "Helicopter"
table["contacts"][#table["contacts"] + 1] = target elseif unit:getDesc().category == Unit.Category.GROUND_UNIT then
end table["category"] = "GroundUnit"
end elseif unit:getDesc().category == Unit.Category.SHIP then
table["category"] = "NavyUnit"
end
elseif objectCategory == Object.Category.WEAPON then
if unit:getDesc().category == Weapon.Category.MISSILE then
table["category"] = "Missile"
elseif unit:getDesc().category == Weapon.Category.ROCKET then
table["category"] = "Missile"
elseif unit:getDesc().category == Unit.Category.BOMB then
table["category"] = "Bomb"
end end
end end
table["hasTask"] = controller:hasTask() -- If the category is handled by Olympus, get the data
if table["category"] ~= "None" then
-- Compute unit position and heading
local lat, lng, alt = coord.LOtoLL(unit:getPoint())
local position = unit:getPosition()
local heading = math.atan2( position.x.z, position.x.x )
table["ammo"] = unit:getAmmo() -- Fill the data table
table["fuel"] = unit:getFuel() table["name"] = unit:getTypeName()
table["life"] = unit:getLife() / unit:getLife0() table["unitName"] = unit:getName()
unitsData[unit:getObjectID()] = table table["groupName"] = group:getName()
table["isHuman"] = (unit:getPlayerName() ~= nil)
table["coalitionID"] = unit:getCoalition()
table["hasTask"] = controller:hasTask()
table["ammo"] = unit:getAmmo() --TODO remove a lot of stuff we don't really need
table["fuel"] = unit:getFuel()
table["life"] = unit:getLife() / unit:getLife0()
table["contacts"] = contacts
table["position"] = {}
table["position"]["lat"] = lat
table["position"]["lng"] = lng
table["position"]["alt"] = alt
table["speed"] = mist.vec.mag(unit:getVelocity())
table["heading"] = heading
table["country"] = unit:getCountry()
table["isAlive"] = (unit:getLife() > 1) and unit:isExist()
units[unit:getObjectID()] = table
Olympus.unitNames[unit:getObjectID()] = unit:getName() -- Used to find what units are dead, since they will not be in mist.DBs.groupsByName
end
end end
end end
end end
@@ -720,6 +812,34 @@ function Olympus.setMissionData(arg, time)
end end
end end
-- All the units that can't be retrieved by getByName are dead
for ID, name in pairs(Olympus.unitNames) do
local unit = Unit.getByName(name)
if unit == nil then
units[ID] = {isAlive = false}
Olympus.unitNames[ID] = nil
end
end
-- Assemble unitsData table
Olympus.unitsData["units"] = units
Olympus.OlympusDLL.setUnitsData()
return time + 0.05
end
function Olympus.setMissionData(arg, time)
-- Bullseye data
local bullseyes = {}
for i = 0, 2 do
local bullseyeVec3 = coalition.getMainRefPoint(i)
local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3)
bullseyes[i] = {}
bullseyes[i]["latitude"] = bullseyeLatitude
bullseyes[i]["longitude"] = bullseyeLongitude
bullseyes[i]["coalition"] = Olympus.getCoalitionByCoalitionID(i)
end
-- Airbases data -- Airbases data
local base = world.getAirbases() local base = world.getAirbases()
local airbases = {} local airbases = {}
@@ -736,20 +856,21 @@ function Olympus.setMissionData(arg, time)
airbases[i] = info airbases[i] = info
end end
-- Mission
local mission = {} local mission = {}
mission.theatre = env.mission.theatre mission.theatre = env.mission.theatre
mission.elapsedTime = DCS.getRealTime() mission.dateAndTime = {
mission.time = mist.time.getDHMS(timer.getAbsTime()) ["elapsedTime"] = DCS.getRealTime() - Olympus.missionStartTime,
mission.startTime = env.mission.start_time ["time"] = mist.time.getDHMS(timer.getAbsTime()),
mission.date = env.mission.date ["startTime"] = env.mission.start_time,
["date"] = env.mission.date
}
-- Assemble missionData table -- Assemble table
missionData["bullseyes"] = bullseyes Olympus.missionData["bullseyes"] = bullseyes
missionData["unitsData"] = unitsData Olympus.missionData["airbases"] = airbases
missionData["airbases"] = airbases Olympus.missionData["mission"] = mission
missionData["mission"] = mission
Olympus.missionData = missionData
Olympus.OlympusDLL.setMissionData() Olympus.OlympusDLL.setMissionData()
return time + 1 return time + 1
end end
@@ -763,6 +884,7 @@ else
Olympus.notify('Failed to load '..OlympusName, 20) Olympus.notify('Failed to load '..OlympusName, 20)
end end
timer.scheduleFunction(Olympus.setUnitsData, {}, timer.getTime() + 0.05)
timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1) timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1)
Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true) Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true)

View File

@@ -1,4 +1,4 @@
local version = 'v0.3.0-alpha' local version = 'v0.4.0-alpha'
Olympus = {} Olympus = {}
Olympus.OlympusDLL = nil Olympus.OlympusDLL = nil

View File

@@ -1351,4 +1351,141 @@ templates =
}, -- end of [18] }, -- end of [18]
}, -- end of ["units"] }, -- end of ["units"]
}, -- end of ["SA-10 SAM Battery"] }, -- end of ["SA-10 SAM Battery"]
["SA-5 SAM Battery"] =
{
["type"] = "vehicle",
["name"] = "SA-5 SAM Battery",
["country"] = 0,
["units"] =
{
[1] =
{
["dx"] = 0,
["dy"] = 0,
["name"] = "RPC_5N62V",
["skill"] = "High",
["heading"] = 4.7123889803847,
}, -- end of [1]
[2] =
{
["dx"] = 0.69314285699511,
["dy"] = 127.97571428004,
["name"] = "RLS_19J6",
["skill"] = "High",
["heading"] = 0,
},
[3] =
{
["dx"] = 83.349983285123,
["dy"] = -1.3806866992963,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 3.1415926535898,
}, -- end of [5]
[4] =
{
["dx"] = 82.498640577192,
["dy"] = 16.104647497996,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 3.3161255787892,
}, -- end of [6]
[5] =
{
["dx"] = 82.547616217693,
["dy"] = -18.227276489837,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 2.9670597283904,
}, -- end of [7]
[6] =
{
["dx"] = -82.640406328603,
["dy"] = -0.41562629467808,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 0,
}, -- end of [8]
[7] =
{
["dx"] = -81.939684967569,
["dy"] = 17.115632734494,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 6.1086523819802,
}, -- end of [9]
[8] =
{
["dx"] = -81.939684967569,
["dy"] = -17.99454369233,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 0.17453292519943,
}, -- end of [10]
[9] =
{
["dx"] = -9.0858776818495,
["dy"] = 187.67713509151,
["name"] = "generator_5i57",
["skill"] = "High",
["heading"] = 1.5707963267949,
}, -- end of [11]
[10] =
{
["dx"] = 0.83760223048739,
["dy"] = 187.51811292395,
["name"] = "generator_5i57",
["skill"] = "High",
["heading"] = 1.5707963267949,
}, -- end of [12]
[11] =
{
["dx"] = -59.823818980018,
["dy"] = 168.63468487991,
["name"] = "ATZ-5",
["skill"] = "High",
["heading"] = 0,
}, -- end of [13]
[12] =
{
["dx"] = -59.823818980018,
["dy"] = 179.2654343833,
["name"] = "ATZ-5",
["skill"] = "High",
["heading"] = 0,
}, -- end of [14]
[13] =
{
["dx"] = 20.947679329896,
["dy"] = -62.811427216162,
["name"] = "GAZ-66",
["skill"] = "High",
["heading"] = 1.5707963267949,
}, -- end of [15]
[14] =
{
["dx"] = 66.751355714747,
["dy"] = 151.35592090525,
["name"] = "ATZ-60_Maz",
["skill"] = "High",
["heading"] = 3.9269908169872,
}, -- end of [16]
[15] =
{
["dx"] = 59.63926918729,
["dy"] = 158.46800743265,
["name"] = "ATZ-60_Maz",
["skill"] = "High",
["heading"] = 3.9269908169872,
}, -- end of [17]
[16] =
{
["dx"] = -16.327227612433,
["dy"] = -62.472874663305,
["name"] = "KAMAZ Truck",
["skill"] = "High",
["heading"] = 1.5707963267949,
}, -- end of [18]
}, -- end of ["units"]
} -- end of ["SA-5 SAM Battery"]
} -- end of templates } -- end of templates

View File

@@ -1,8 +0,0 @@
{
"files.associations": {
"*.ejs": "html",
"xstring": "cpp",
"vector": "cpp",
"list": "cpp"
}
}

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