Compare commits
45 Commits
v0.2.0-alp
...
v0.2.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98a6053a85 | ||
|
|
7d3debfa7a | ||
|
|
847234633a | ||
|
|
47d9ff2e10 | ||
|
|
e69294b98c | ||
|
|
cf3cdcbd44 | ||
|
|
7545e848d6 | ||
|
|
d146a47e71 | ||
|
|
91f8996fba | ||
|
|
57b74bd1b1 | ||
|
|
865be6283c | ||
|
|
e0636b53f0 | ||
|
|
987c8ee41c | ||
|
|
e54a70ffa8 | ||
|
|
fcf1e2d94a | ||
|
|
35648b67f5 | ||
|
|
f0dc581261 | ||
|
|
7ecfc5afef | ||
|
|
e3b88d4e35 | ||
|
|
669ee510d3 | ||
|
|
b8edc500c4 | ||
|
|
dc57028a3c | ||
|
|
9eb01654c6 | ||
|
|
8539314984 | ||
|
|
cb24a19d17 | ||
|
|
bd7700efce | ||
|
|
a23bdd4b33 | ||
|
|
89a71ef04e | ||
|
|
640f764354 | ||
|
|
ee08d9d48e | ||
|
|
bf365270ae | ||
|
|
6ec2235114 | ||
|
|
8f7071510b | ||
|
|
1f72612255 | ||
|
|
94c5765bc6 | ||
|
|
04c7fc3741 | ||
|
|
40bcd46677 | ||
|
|
ab736adabe | ||
|
|
f4fd516d92 | ||
|
|
4c3cfc608b | ||
|
|
6ae23509a6 | ||
|
|
20077417af | ||
|
|
673e00c0e8 | ||
|
|
197159fc2a | ||
|
|
1ca0029407 |
@@ -1,7 +0,0 @@
|
||||
RTB
|
||||
tanker
|
||||
scenario dropdown
|
||||
explosion
|
||||
wrong name for ground units
|
||||
improve map zIndex
|
||||
human symbol if user
|
||||
@@ -3,6 +3,7 @@ var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
var fs = require('fs');
|
||||
var basicAuth = require('express-basic-auth')
|
||||
|
||||
var atcRouter = require('./routes/api/atc');
|
||||
var indexRouter = require('./routes/index');
|
||||
@@ -26,7 +27,8 @@ app.set('view engine', 'ejs');
|
||||
|
||||
let rawdata = fs.readFileSync('../olympus.json');
|
||||
let config = JSON.parse(rawdata);
|
||||
app.get('/config', (req, res) => res.send(config));
|
||||
if (config["server"] != undefined)
|
||||
app.get('/config', (req, res) => res.send(config["server"]));
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@@ -38,3 +40,8 @@ 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' }
|
||||
}))
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
start cmd /k "npm run start"
|
||||
start cmd /k "watchify .\src\index.ts --debug -p [ tsify --noImplicitAny ] -o .\public\javascripts\bundle.js"
|
||||
start cmd /k "watchify .\src\index.ts --debug -o .\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const DEMO_UNIT_DATA = {
|
||||
["1"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
AI: false,
|
||||
name: "KC-135",
|
||||
unitName: "Olympus 1-1",
|
||||
groupName: "Group 1",
|
||||
@@ -18,7 +18,7 @@ const DEMO_UNIT_DATA = {
|
||||
},
|
||||
missionData: {
|
||||
fuel: 50,
|
||||
flags: {human: false},
|
||||
flags: {Human: false},
|
||||
ammo: [
|
||||
{
|
||||
count: 4,
|
||||
@@ -47,6 +47,7 @@ const DEMO_UNIT_DATA = {
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Holding",
|
||||
currentState: "Idle",
|
||||
activePath: undefined,
|
||||
targetSpeed: 400,
|
||||
targetAltitude: 3000,
|
||||
@@ -68,7 +69,7 @@ const DEMO_UNIT_DATA = {
|
||||
["2"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "F-5E",
|
||||
name: "KC-135",
|
||||
unitName: "Olympus 1-2",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
@@ -643,6 +644,7 @@ class DemoDataGenerator {
|
||||
|
||||
logs(req, res){
|
||||
var ret = {logs: {}};
|
||||
ret.time = Date.now();
|
||||
res.send(JSON.stringify(ret));
|
||||
};
|
||||
|
||||
@@ -667,6 +669,7 @@ class DemoDataGenerator {
|
||||
coalition: "blue"
|
||||
}
|
||||
}};
|
||||
ret.time = Date.now();
|
||||
res.send(JSON.stringify(ret));
|
||||
};
|
||||
|
||||
@@ -685,11 +688,13 @@ class DemoDataGenerator {
|
||||
longitude: -115.7
|
||||
}
|
||||
}};
|
||||
ret.time = Date.now();
|
||||
res.send(JSON.stringify(ret));
|
||||
};
|
||||
|
||||
mission(req, res){
|
||||
var ret = {mission: {theatre: "Nevada"}};
|
||||
ret.time = Date.now();
|
||||
res.send(JSON.stringify(ret));
|
||||
}
|
||||
|
||||
|
||||
4200
client/package-lock.json
generated
@@ -2,12 +2,12 @@
|
||||
"name": "DCSOlympus",
|
||||
"node-main": "./bin/www",
|
||||
"main": "http://localhost:3000",
|
||||
"version": "0.1.1-alpha",
|
||||
"version": "v0.2.1-alpha",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"copy": "copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet.css",
|
||||
"start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"",
|
||||
"watch": "watchify .\\src\\index.ts --debug -p [ tsify --noImplicitAny ] -o .\\public\\javascripts\\bundle.js"
|
||||
"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": {
|
||||
"@types/geojson": "^7946.0.10",
|
||||
@@ -16,16 +16,24 @@
|
||||
"debug": "~2.6.9",
|
||||
"ejs": "^3.1.8",
|
||||
"express": "~4.16.1",
|
||||
"geomag": "^1.0.0",
|
||||
"leaflet": "^1.9.3",
|
||||
"leaflet-control-mini-map": "^0.4.0",
|
||||
"leaflet.nauticscale": "^1.1.0",
|
||||
"milsymbol": "^2.0.0",
|
||||
"morgan": "~1.9.1",
|
||||
"save": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@types/gtag.js": "^0.0.12",
|
||||
"@types/node": "^18.16.1",
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"babelify": "^10.0.0",
|
||||
"browserify": "^17.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"esmify": "^2.1.1",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tsify": "^5.0.4",
|
||||
|
||||
|
Before Width: | Height: | Size: 8.5 KiB |
BIN
client/public/images/icon-temporary.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
@@ -70,8 +70,6 @@
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#aircraft-spawn-menu .ol-select.is-open .ol-select-options {
|
||||
max-height: 300px;
|
||||
}
|
||||
@@ -131,6 +129,16 @@
|
||||
background-color: var(--primary-red)
|
||||
}
|
||||
|
||||
[data-active-coalition="neutral"].toggle-fill,
|
||||
[data-active-coalition="neutral"].unit-spawn-button:hover,
|
||||
[data-active-coalition="neutral"].unit-spawn-button.is-open,
|
||||
[data-active-coalition="neutral"]#active-coalition-label,
|
||||
[data-active-coalition="neutral"].deploy-unit-button,
|
||||
[data-active-coalition="neutral"]#spawn-airbase-aircraft-button
|
||||
{
|
||||
background-color: var(--primary-neutral)
|
||||
}
|
||||
|
||||
[data-active-coalition="blue"].deploy-unit-button:disabled {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--primary-blue);
|
||||
@@ -141,6 +149,21 @@
|
||||
border: 1px solid var(--primary-red);
|
||||
cursor: default;
|
||||
}
|
||||
[data-active-coalition="neutral"].deploy-unit-button:disabled {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--primary-neutral);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-active-coalition="blue"].toggle-fill::after {
|
||||
transform: translateX(0);
|
||||
}
|
||||
[data-active-coalition="red"].toggle-fill::after {
|
||||
transform: translateX(var(--height));
|
||||
}
|
||||
[data-active-coalition="neutral"].toggle-fill::after {
|
||||
transform: translateX(calc(var(--height) / 2));
|
||||
}
|
||||
|
||||
[data-active-coalition="blue"]#active-coalition-label::after {
|
||||
content: "Create blue unit";
|
||||
@@ -148,6 +171,9 @@
|
||||
[data-active-coalition="red"]#active-coalition-label::after {
|
||||
content: "Create red unit";
|
||||
}
|
||||
[data-active-coalition="neutral"]#active-coalition-label::after {
|
||||
content: "Create neutral unit";
|
||||
}
|
||||
|
||||
#loadout-preview {
|
||||
display: flex;
|
||||
@@ -167,6 +193,7 @@
|
||||
|
||||
#unit-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
filter: invert(100%);
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
@@ -329,4 +356,39 @@
|
||||
row-gap: 5px;
|
||||
width: 180px;
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
--width: 40px;
|
||||
--height: calc(var(--width) / 2);
|
||||
--border-radius: calc(var(--height) / 2);
|
||||
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-fill {
|
||||
position: relative;
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
border-radius: var(--border-radius);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.toggle-fill::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2;
|
||||
left: 2;
|
||||
height: calc(var(--height) - 4px);
|
||||
width: calc(var(--height) - 4px);
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
|
||||
border-radius: var(--border-radius);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
#olympus-toolbar-summary {
|
||||
background-image: url("/images/icon-round.png");
|
||||
background-position: 25px 20px;
|
||||
background-position: 20px 22px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 36px 36px;
|
||||
background-size: 45px 45px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-indent: 44px;
|
||||
text-indent: 60px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
dl.ol-data-grid {
|
||||
|
||||
@@ -10,31 +10,28 @@
|
||||
@import url("unitinfopanel.css");
|
||||
@import url("popup.css");
|
||||
|
||||
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
html {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
html * {
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
}
|
||||
|
||||
body {
|
||||
display:grid;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -43,7 +40,6 @@ a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
button {
|
||||
background-color: var(--background-steel);
|
||||
border: 1px solid var(--background-steel);
|
||||
@@ -68,26 +64,40 @@ form {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form > div {
|
||||
form>div {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
|
||||
.pill {
|
||||
background-color: var( --background-dark-steel );
|
||||
background-color: var(--background-dark-steel);
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: 4px 8px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.ol-scrollable {
|
||||
overflow-y: scroll;
|
||||
scrollbar-color: white transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.ol-scrollable::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
width: var(--border-radius-md);
|
||||
}
|
||||
|
||||
|
||||
.ol-scrollable::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
border-radius: 100px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.ol-select .ol-scrollable {
|
||||
scrollbar-color: white var(--background-grey);
|
||||
}
|
||||
|
||||
.ol-select .ol-scrollable::-webkit-scrollbar-track {
|
||||
background-color: var(--background-grey);
|
||||
}
|
||||
|
||||
.ol-scrollable::-webkit-scrollbar-thumb {
|
||||
@@ -97,7 +107,6 @@ form > div {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
.ol-panel {
|
||||
background-color: var(--background-steel);
|
||||
border-radius: 15px;
|
||||
@@ -125,6 +134,14 @@ form > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ol-ellipsed {
|
||||
display: inline-block;
|
||||
width: calc(100%);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ol-select {
|
||||
position: relative;
|
||||
color: var(--nav-text);
|
||||
@@ -139,6 +156,7 @@ form > div {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ol-select:not(.ol-select-image)>.ol-select-value {
|
||||
@@ -146,14 +164,14 @@ form > div {
|
||||
background-color: var(--background-grey);
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: 1em 30px 1em 20px;
|
||||
width: 100%;
|
||||
width: calc(100%);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ol-select.narrow:not(.ol-select-image)>.ol-select-value {
|
||||
opacity: .9;
|
||||
padding:4px 30px 4px 15px;
|
||||
padding: 4px 30px 4px 15px;
|
||||
}
|
||||
|
||||
.ol-select:not(.ol-select-image)>.ol-select-value svg {
|
||||
@@ -167,55 +185,51 @@ form > div {
|
||||
}
|
||||
|
||||
.ol-select>.ol-select-options {
|
||||
border-radius: var( --border-radius-md );
|
||||
border-radius: var(--border-radius-md);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
max-height: 0;
|
||||
translate: 0 -2px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.ol-select-options.scrollbar-visible {
|
||||
border-top-right-radius: 0px !important;
|
||||
border-bottom-right-radius: 0px !important;
|
||||
}
|
||||
|
||||
.ol-select.ol-select-image>.ol-select-options {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
.ol-select.is-open > .ol-select-options {
|
||||
.ol-select.is-open>.ol-select-options {
|
||||
max-height: 382px;
|
||||
overflow: visible;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
min-width: 100%;
|
||||
z-index:9999;
|
||||
z-index: 9999;
|
||||
translate: 0px 5px;
|
||||
}
|
||||
|
||||
|
||||
.ol-select.is-open[data-position="top"] > .ol-select-options {
|
||||
top:0;
|
||||
translate:0 -100%;
|
||||
.ol-select.is-open[data-position="top"]>.ol-select-options {
|
||||
top: 0;
|
||||
translate: 0 -100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ol-select>.ol-select-options > div {
|
||||
.ol-select>.ol-select-options>div {
|
||||
background-color: var(--background-grey);
|
||||
box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
padding: 4px 25px;
|
||||
padding: 2px 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ol-select>.ol-select-options>div:first-of-type {
|
||||
border-top-left-radius: var(--border-radius-md);
|
||||
border-top-right-radius: var(--border-radius-md);
|
||||
padding-top: 16px;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.ol-select>.ol-select-options>div:last-of-type {
|
||||
border-bottom-left-radius: var(--border-radius-md);
|
||||
border-bottom-right-radius: var(--border-radius-md);
|
||||
padding-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.ol-select>.ol-select-options div hr {
|
||||
@@ -224,25 +238,29 @@ form > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ol-select>.ol-select-options > div a, .ol-select>.ol-select-options > div button {
|
||||
.ol-select>.ol-select-options>div a,
|
||||
.ol-select>.ol-select-options>div button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
color: white;
|
||||
display:block;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
padding: 6px 2px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
.ol-select>.ol-select-options > div a:hover, .ol-select>.ol-select-options > div button:hover {
|
||||
text-decoration: underline;
|
||||
.ol-select>.ol-select-options>div a:hover,
|
||||
.ol-select>.ol-select-options>div button:hover {
|
||||
background-color: #FFF3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.ol-panel-list {
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
@@ -276,32 +294,30 @@ form > div {
|
||||
max-width: 16px;
|
||||
}
|
||||
|
||||
|
||||
.ol-panel-board {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.ol-panel-board > .panel-section {
|
||||
.ol-panel-board>.panel-section {
|
||||
border-right: 1px solid #555;
|
||||
margin:10px 0;
|
||||
padding:0 30px;
|
||||
margin: 10px 0;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.ol-panel-board > .panel-section:first-child {
|
||||
padding-left:20px;
|
||||
.ol-panel-board>.panel-section:first-child {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.ol-panel-board > .panel-section:last-child {
|
||||
padding-right:20px;
|
||||
.ol-panel-board>.panel-section:last-child {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.ol-panel-board > .panel-section:last-of-type {
|
||||
.ol-panel-board>.panel-section:last-of-type {
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
@@ -336,7 +352,6 @@ button.ol-button-warning {
|
||||
color: var(--primary-red);
|
||||
}
|
||||
|
||||
|
||||
nav.ol-panel {
|
||||
column-gap: 20px;
|
||||
display: flex;
|
||||
@@ -348,7 +363,6 @@ nav.ol-panel> :last-child {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
|
||||
.ol-panel .ol-group {
|
||||
border-radius: var(--border-radius-sm);
|
||||
column-gap: 10px;
|
||||
@@ -368,7 +382,6 @@ nav.ol-panel> :last-child {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
.ol-panel .ol-group-button-toggle {
|
||||
align-items: center;
|
||||
column-gap: 15px;
|
||||
@@ -382,7 +395,7 @@ nav.ol-panel> :last-child {
|
||||
background-position: 5px 50%;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
display:flex;
|
||||
display: flex;
|
||||
justify-items: left;
|
||||
text-indent: 5px;
|
||||
}
|
||||
@@ -393,38 +406,36 @@ nav.ol-panel> :last-child {
|
||||
content: "";
|
||||
filter: invert(100%);
|
||||
-webkit-filter: invert(100%);
|
||||
height:16px;
|
||||
width:16px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
|
||||
.ol-panel .ol-group-button-toggle button.off::before {
|
||||
background-image: url("/images/icons/square-regular.svg");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.highlight-primary {
|
||||
background-color: var(--secondary-light-grey);
|
||||
}
|
||||
|
||||
.highlight-coalition, .highlight-neutral {
|
||||
background-color: var(--primary-grey);
|
||||
.highlight-coalition,
|
||||
.highlight-neutral {
|
||||
background-color: var(--primary-neutral);
|
||||
color: var(--secondary-gunmetal-grey)
|
||||
}
|
||||
|
||||
.highlight-coalition[data-coalition="blue"], .highlight-bluefor {
|
||||
.highlight-coalition[data-coalition="blue"],
|
||||
.highlight-bluefor {
|
||||
background-color: var(--primary-blue);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.highlight-coalition[data-coalition="red"], .highlight-redfor {
|
||||
.highlight-coalition[data-coalition="red"],
|
||||
.highlight-redfor {
|
||||
background-color: var(--primary-red);
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.accent-green {
|
||||
color: var(--accent-green);
|
||||
font-weight: var(--font-weight-bolder);
|
||||
@@ -446,7 +457,7 @@ nav.ol-panel> :last-child {
|
||||
}
|
||||
|
||||
.accent-neutral {
|
||||
color: var(--primary-grey);
|
||||
color: var(--primary-neutral);
|
||||
font-weight: var(--font-weight-bolder);
|
||||
}
|
||||
|
||||
@@ -465,7 +476,6 @@ nav.ol-panel> :last-child {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.slider-container {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -530,23 +540,20 @@ nav.ol-panel> :last-child {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
text-align: center;
|
||||
color: var(--primary-grey);
|
||||
color: var(--primary-neutral);
|
||||
font-size: 12px;
|
||||
z-index: 2000;
|
||||
font-weight: var(--font-weight-bolder);
|
||||
}
|
||||
|
||||
|
||||
.ol-sortable .handle {
|
||||
background-image: url( "/images/icons/grip-lines-solid.svg" );
|
||||
cursor:ns-resize;
|
||||
filter:invert();
|
||||
height:12px;
|
||||
width:12px;
|
||||
background-image: url("/images/icons/grip-lines-solid.svg");
|
||||
cursor: ns-resize;
|
||||
filter: invert();
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#unit-selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -622,223 +629,231 @@ body[data-hide-navyunit] #unit-visibility-control-navyunit {
|
||||
|
||||
#atc-navbar-control {
|
||||
align-items: center;
|
||||
display:flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#atc-navbar-control button {
|
||||
background:#ffffff20;
|
||||
border-radius: var( --border-radius-sm );
|
||||
padding:4px;
|
||||
background: #ffffff20;
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
|
||||
.toggle {
|
||||
--width: 40px;
|
||||
--height: calc(var(--width) / 2);
|
||||
--border-radius: calc(var(--height) / 2);
|
||||
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-fill {
|
||||
position: relative;
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
border-radius: var(--border-radius);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.toggle-fill::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2;
|
||||
left: 2;
|
||||
height: calc(var(--height) - 4px);
|
||||
width: calc(var(--height) - 4px);
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
|
||||
border-radius: var(--border-radius);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.toggle-input:checked ~ .toggle-fill::after {
|
||||
transform: translateX(var(--height));
|
||||
}
|
||||
|
||||
|
||||
#roe-buttons-container button {
|
||||
background-color:transparent;
|
||||
border:1px solid var( --accent-light-blue );
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--accent-light-blue);
|
||||
}
|
||||
|
||||
#roe-buttons-container button.selected, #reaction-to-threat-buttons-container button.selected {
|
||||
#roe-buttons-container button.selected,
|
||||
#reaction-to-threat-buttons-container button.selected {
|
||||
background-color: white;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
#roe-buttons-container button::before, #reaction-to-threat-buttons-container button::before {
|
||||
background-position:center;
|
||||
#roe-buttons-container button::before,
|
||||
#reaction-to-threat-buttons-container button::before {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
content: "";
|
||||
display:block;
|
||||
height:24px;
|
||||
width:24px;
|
||||
display: block;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#roe-buttons-container button[title="Hold"]::before {
|
||||
background-image: url( "/themes/olympus/images/icons_roe_stop_light.svg");
|
||||
background-image: url("/themes/olympus/images/icons_roe_stop_light.svg");
|
||||
}
|
||||
|
||||
#roe-buttons-container button[title="Hold"].selected::before {
|
||||
background-image: url( "/themes/olympus/images/icons_roe_stop_dark.svg");
|
||||
background-image: url("/themes/olympus/images/icons_roe_stop_dark.svg");
|
||||
}
|
||||
|
||||
/**/
|
||||
|
||||
#roe-buttons-container button[title="Return"]::before {
|
||||
background-image: url( "/themes/olympus/images/icons_roe_defend_light.svg");
|
||||
background-image: url("/themes/olympus/images/icons_roe_defend_light.svg");
|
||||
}
|
||||
|
||||
#roe-buttons-container button[title="Return"].selected::before {
|
||||
background-image: url( "/themes/olympus/images/icons_roe_defend_dark.svg");
|
||||
background-image: url("/themes/olympus/images/icons_roe_defend_dark.svg");
|
||||
}
|
||||
|
||||
/**/
|
||||
|
||||
#roe-buttons-container button[title="Designated"]::before {
|
||||
background-image: url( "/themes/olympus/images/icons_roe_target_light.svg");
|
||||
background-image: url("/themes/olympus/images/icons_roe_target_light.svg");
|
||||
}
|
||||
|
||||
#roe-buttons-container button[title="Designated"].selected::before {
|
||||
background-image: url( "/themes/olympus/images/icons_roe_target_dark.svg");
|
||||
background-image: url("/themes/olympus/images/icons_roe_target_dark.svg");
|
||||
}
|
||||
|
||||
/**/
|
||||
|
||||
#roe-buttons-container button[title="Free"]::before {
|
||||
background-image: url( "/themes/olympus/images/icons_roe_free_light.svg");
|
||||
background-image: url("/themes/olympus/images/icons_roe_free_light.svg");
|
||||
}
|
||||
|
||||
#roe-buttons-container button[title="Free"].selected::before {
|
||||
background-image: url( "/themes/olympus/images/icons_roe_free_dark.svg");
|
||||
background-image: url("/themes/olympus/images/icons_roe_free_dark.svg");
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
|
||||
#reaction-to-threat-buttons-container button[title="None"]::before {
|
||||
background-image: url( "/themes/olympus/images/icons_threat_nothing_light.svg");
|
||||
background-image: url("/themes/olympus/images/icons_threat_nothing_light.svg");
|
||||
}
|
||||
|
||||
#reaction-to-threat-buttons-container button[title="None"].selected::before {
|
||||
background-image: url( "/themes/olympus/images/icons_threat_nothing_dark.svg");
|
||||
background-image: url("/themes/olympus/images/icons_threat_nothing_dark.svg");
|
||||
}
|
||||
|
||||
|
||||
/**/
|
||||
|
||||
|
||||
#reaction-to-threat-buttons-container button[title="Passive"]::before {
|
||||
background-image: url( "/themes/olympus/images/icons_threat_cms_light.svg");
|
||||
background-image: url("/themes/olympus/images/icons_threat_cms_light.svg");
|
||||
}
|
||||
|
||||
#reaction-to-threat-buttons-container button[title="Passive"].selected::before {
|
||||
background-image: url( "/themes/olympus/images/icons_threat_cms_dark.svg");
|
||||
background-image: url("/themes/olympus/images/icons_threat_cms_dark.svg");
|
||||
}
|
||||
|
||||
|
||||
/**/
|
||||
|
||||
|
||||
#reaction-to-threat-buttons-container button[title="Evade"]::before {
|
||||
background-image: url( "/themes/olympus/images/icons_threat_defend_light.svg");
|
||||
background-image: url("/themes/olympus/images/icons_threat_defend_light.svg");
|
||||
}
|
||||
|
||||
#reaction-to-threat-buttons-container button[title="Evade"].selected::before {
|
||||
background-image: url( "/themes/olympus/images/icons_threat_defend_dark.svg");
|
||||
background-image: url("/themes/olympus/images/icons_threat_defend_dark.svg");
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
|
||||
#splash-screen {
|
||||
background-image: url( "/images/splash/splash_pic_ship.png" );
|
||||
background-position:100% 50%;
|
||||
background-size:320px;
|
||||
border-radius: var( --border-radius-lg );
|
||||
display:none;
|
||||
background-image: url("/images/splash/splash_pic_ship.png");
|
||||
background-position: 100% 50%;
|
||||
background-size: 60%;
|
||||
border-radius: var(--border-radius-lg);
|
||||
overflow: hidden;
|
||||
width:700px;
|
||||
width: 1200px;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
#splash-content {
|
||||
background-color: var( --background-steel );
|
||||
background-color: var(--background-steel);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding:20px;
|
||||
position:relative;
|
||||
row-gap:10px;
|
||||
width:55%;
|
||||
z-index:10;
|
||||
padding: 30px;
|
||||
position: relative;
|
||||
row-gap: 10px;
|
||||
width: 50%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#splash-content::after {
|
||||
background-color: var( --background-steel );
|
||||
background-color: var(--background-steel);
|
||||
content: "";
|
||||
display: block;
|
||||
height:250px;
|
||||
height: 800px;
|
||||
position: absolute;
|
||||
right:0;
|
||||
top:0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: rotate(-23deg);
|
||||
transform-origin: top right;
|
||||
width:200px;
|
||||
width: 200px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#splash-content #app-summary {
|
||||
background-image: url( "/images/olympus-500x500.png" );
|
||||
background-image: url("/images/olympus-500x500.png");
|
||||
background-position: 0 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size:75px 75px;
|
||||
background-size: 75px 75px;
|
||||
content: "";
|
||||
display:flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 75px;
|
||||
text-indent: 85px;
|
||||
}
|
||||
|
||||
#splash-content #app-summary > * {
|
||||
height:fit-content;
|
||||
#splash-content #app-summary>* {
|
||||
height: fit-content;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
width:fit-content;
|
||||
width: fit-content;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#splash-content .app-version {
|
||||
font-size:11px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#splash-content #legal-stuff h4 {
|
||||
text-transform:uppercase;
|
||||
#splash-content #legal-stuff h5 {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#splash-content #legal-stuff p {
|
||||
font-size:10px;
|
||||
font-size: 10px;
|
||||
color:#FFF7;
|
||||
width: 120%;
|
||||
}
|
||||
|
||||
#splash-content.ol-dialog-content {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.feature-splashScreen #splash-screen {
|
||||
display:flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#gray-out {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: 9999;
|
||||
background-color: #000A;
|
||||
}
|
||||
|
||||
#authentication-form {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
column-gap: 10px;
|
||||
margin: 10px 0px;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#authentication-form>div {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
row-gap: 4px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#authentication-form>div>input {
|
||||
height: 35px;
|
||||
border-radius: var(--border-radius-sm);
|
||||
border: 0px solid transparent;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#splash-content a {
|
||||
color: #FFFB;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#connection-status[data-status="connecting"]::before {
|
||||
content: "Connecting...";
|
||||
animation: blinker 1s linear infinite;
|
||||
}
|
||||
|
||||
#connection-status[data-status="failed"]::before {
|
||||
content: "Incorrect username/password!";
|
||||
color: var(--primary-red);
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
display:block !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,96 +13,96 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
}
|
||||
|
||||
#unit-control-panel h3 {
|
||||
margin-bottom:8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container {
|
||||
align-items: left;
|
||||
border-radius: var( --border-radius-md );
|
||||
display:flex;
|
||||
border-radius: var(--border-radius-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 136px;
|
||||
overflow-y:auto;
|
||||
overflow-y: auto;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container button {
|
||||
align-items: center;
|
||||
border-radius: var( --border-radius-lg );
|
||||
display:flex;
|
||||
border-radius: var(--border-radius-lg);
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
height:30px;
|
||||
padding:8px 0;
|
||||
height: 30px;
|
||||
padding: 8px 0;
|
||||
position: relative;
|
||||
width:90%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container button::before {
|
||||
background-color: var( --primary-grey );
|
||||
border-radius: var( --border-radius-md );
|
||||
content: attr( data-short-label );
|
||||
margin:0 5px;
|
||||
padding:4px 6px;
|
||||
background-color: var( --primary-neutral );
|
||||
border-radius: 999px;
|
||||
content: attr(data-short-label);
|
||||
margin: 0 5px;
|
||||
padding: 4px 6px;
|
||||
white-space: nowrap;
|
||||
width:fit-content;
|
||||
width: 30px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#unit-control-panel #selected-units-container button[data-coalition="blue"]::before {
|
||||
background-color: var( --accent-light-blue );
|
||||
background-color: var(--accent-light-blue);
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container button[data-coalition="red"]::before {
|
||||
background-color: var( --accent-light-red );
|
||||
color:var(--secondary-red-outline)
|
||||
background-color: var(--accent-light-red);
|
||||
color: var(--secondary-red-outline)
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container button::after {
|
||||
border-radius: var( --border-radius-sm );
|
||||
content: attr( data-callsign );
|
||||
border-radius: var(--border-radius-sm);
|
||||
content: attr(data-callsign);
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding:4px;
|
||||
padding-left:0;
|
||||
padding: 4px;
|
||||
padding-left: 0;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width:fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
|
||||
#unit-control-panel #selected-units-container button:hover::after {
|
||||
overflow: visible;
|
||||
text-overflow:initial;
|
||||
text-overflow: initial;
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container button:hover::after {
|
||||
background-color: var( --background-grey );
|
||||
background-color: var(--background-grey);
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container button[data-coalition="blue"]:hover::after {
|
||||
background-color: var( --primary-blue );
|
||||
background-color: var(--primary-blue);
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container button[data-coalition="red"]:hover::after {
|
||||
background-color: var( --primary-red );
|
||||
background-color: var(--primary-red);
|
||||
}
|
||||
|
||||
#unit-control-panel h4 {
|
||||
margin-bottom:8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#unit-control-panel #threat,
|
||||
#unit-control-panel #roe {
|
||||
margin-top:12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
#advanced-settings-dialog {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#advanced-settings-dialog > .ol-dialog-content {
|
||||
#advanced-settings-dialog>.ol-dialog-content {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
@@ -111,6 +111,6 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
#advanced-settings-dialog > .ol-dialog-content > .ol-group {
|
||||
#advanced-settings-dialog>.ol-dialog-content>.ol-group {
|
||||
justify-content: space-between;
|
||||
}
|
||||
@@ -1,46 +1,48 @@
|
||||
:root {
|
||||
/* Generic marker settings */
|
||||
--unit-centre-x: calc( var( --unit-width ) / 2 );
|
||||
--unit-centre-y: calc( var( --unit-height ) / 2 );
|
||||
|
||||
--unit-hotgroup-height: 10px;
|
||||
--unit-hotgroup-width: var( --unit-hotgroup-height );
|
||||
|
||||
|
||||
/* Air units' marker settings */
|
||||
--unit-aircraft-label-x: calc( var( --unit-centre-x ) - ( var( --unit-aircraft-width ) / 2 ) + ( var( --unit-stroke-width ) / 2 ) );
|
||||
--unit-aircraft-label-y: calc( var( --unit-centre-y ) - ( var( --unit-aircraft-height ) / 2 ) + ( var( --unit-stroke-width ) / 2 ) );
|
||||
/* Generic marker settings */
|
||||
--unit-centre-x: calc(var(--unit-width) / 2);
|
||||
--unit-centre-y: calc(var(--unit-height) / 2);
|
||||
|
||||
--unit-hotgroup-height: 10px;
|
||||
--unit-hotgroup-width: var(--unit-hotgroup-height);
|
||||
|
||||
|
||||
/* Air units' marker settings */
|
||||
--unit-aircraft-label-x: calc(var(--unit-centre-x) - (var(--unit-aircraft-width) / 2) + (var(--unit-stroke-width) / 2));
|
||||
--unit-aircraft-label-y: calc(var(--unit-centre-y) - (var(--unit-aircraft-height) / 2) + (var(--unit-stroke-width) / 2));
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] {
|
||||
align-items: center;
|
||||
cursor:pointer;
|
||||
display:flex;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position:relative;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-selected-spotlight {
|
||||
background-color: var( --unit-spotlight-fill );
|
||||
background-color: var(--unit-spotlight-fill);
|
||||
border-radius: 50%;
|
||||
display:none;
|
||||
padding: var( --unit-spotlight-radius );
|
||||
display: none;
|
||||
padding: var(--unit-spotlight-radius);
|
||||
position: absolute;
|
||||
z-index:1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-vvi {
|
||||
align-self: center;
|
||||
background:var( --secondary-gunmetal-grey );
|
||||
display:flex;
|
||||
background: var(--secondary-gunmetal-grey);
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
transform-origin: bottom;
|
||||
translate:0 -50%;
|
||||
padding-bottom: calc( ( var( --unit-aircraft-width ) / 2 ) + var( --unit-stroke-width ) );
|
||||
position:absolute;
|
||||
width: var( --unit-aircraft-vvi-width );
|
||||
translate: 0 -50%;
|
||||
padding-bottom: calc((var(--unit-aircraft-width) / 2) + var(--unit-stroke-width));
|
||||
position: absolute;
|
||||
width: var(--unit-aircraft-vvi-width);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
@@ -48,32 +50,32 @@
|
||||
[data-object|="unit"] .unit-hotgroup {
|
||||
align-content: center;
|
||||
background-color: black;
|
||||
border-radius: var( --border-radius-xs );
|
||||
display:none;
|
||||
height: var( --unit-hotgroup-height );
|
||||
border-radius: var(--border-radius-xs);
|
||||
display: none;
|
||||
height: var(--unit-hotgroup-height);
|
||||
justify-content: center;
|
||||
position:absolute;
|
||||
transform: rotate( -45deg );
|
||||
translate:0 -275%;
|
||||
width: var( --unit-hotgroup-width );
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
translate: 0 -275%;
|
||||
width: var(--unit-hotgroup-width);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-hotgroup-id {
|
||||
background-color: transparent;
|
||||
color:white;
|
||||
color: white;
|
||||
font-size: 9px;
|
||||
font-weight: bolder;
|
||||
transform:rotate( 45deg );
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-marker-border {
|
||||
border-radius: var( --border-radius-sm );
|
||||
display:none;
|
||||
height: calc( var( --unit-aircraft-height ) + ( var( --unit-label-border-width ) * 2 ) );
|
||||
position:absolute;
|
||||
width: calc( var( --unit-aircraft-width ) + ( var( --unit-label-border-width ) * 2 ) );
|
||||
z-index:2;
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: none;
|
||||
height: calc(var(--unit-aircraft-height) + (var(--unit-label-border-width) * 2));
|
||||
position: absolute;
|
||||
width: calc(var(--unit-aircraft-width) + (var(--unit-label-border-width) * 2));
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,9 +87,9 @@
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
position:absolute;
|
||||
position: absolute;
|
||||
transform-origin: center;
|
||||
z-index:3;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,43 +97,43 @@
|
||||
/* Air */
|
||||
|
||||
[data-object|="unit-aircraft"] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-neutral-url );
|
||||
height: var( --unit-aircraft-marker-height );
|
||||
width: var( --unit-aircraft-marker-width );
|
||||
background-image: var(--unit-aircraft-marker-neutral-url);
|
||||
height: var(--unit-aircraft-marker-height);
|
||||
width: var(--unit-aircraft-marker-width);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"]:hover .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-neutral-hover-url );
|
||||
background-image: var(--unit-aircraft-marker-neutral-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-neutral-selected-url );
|
||||
background-image: var(--unit-aircraft-marker-neutral-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="blue"] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-blue-url );
|
||||
background-image: var(--unit-aircraft-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="blue"]:hover .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-blue-hover-url );
|
||||
background-image: var(--unit-aircraft-marker-blue-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="blue"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-blue-selected-url );
|
||||
background-image: var(--unit-aircraft-marker-blue-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="red"] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-red-url );
|
||||
background-image: var(--unit-aircraft-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="red"]:hover .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-red-hover-url );
|
||||
background-image: var(--unit-aircraft-marker-red-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="red"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-red-selected-url );
|
||||
background-image: var(--unit-aircraft-marker-red-selected-url);
|
||||
}
|
||||
|
||||
|
||||
@@ -140,194 +142,195 @@
|
||||
/* Ground vehicles (not SAMs) */
|
||||
|
||||
[data-object|="unit-groundunit"] .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-neutral-url );
|
||||
height: var( --unit-groundunit-marker-height );
|
||||
width: var( --unit-groundunit-marker-width );
|
||||
background-image: var(--unit-groundunit-marker-neutral-url);
|
||||
height: var(--unit-groundunit-marker-height);
|
||||
width: var(--unit-groundunit-marker-width);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"]:hover .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-neutral-hover-url );
|
||||
background-image: var(--unit-groundunit-marker-neutral-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-neutral-selected-url );
|
||||
background-image: var(--unit-groundunit-marker-neutral-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-groundunit"][data-coalition="blue"] .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-blue-url );
|
||||
background-image: var(--unit-groundunit-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"][data-coalition="blue"]:hover .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-blue-hover-url );
|
||||
background-image: var(--unit-groundunit-marker-blue-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"][data-coalition="blue"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-blue-selected-url );
|
||||
background-image: var(--unit-groundunit-marker-blue-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-groundunit"][data-coalition="red"] .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-red-url );
|
||||
background-image: var(--unit-groundunit-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"][data-coalition="red"]:hover .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-red-hover-url );
|
||||
background-image: var(--unit-groundunit-marker-red-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"][data-coalition="red"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-groundunit-marker-red-selected-url );
|
||||
background-image: var(--unit-groundunit-marker-red-selected-url);
|
||||
}
|
||||
|
||||
|
||||
/* SAMs */
|
||||
|
||||
[data-object|="unit-sam"] .unit-selected-spotlight {
|
||||
translate:0 2px;
|
||||
translate: 0 2px;
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"] .unit-marker {
|
||||
background-image: var( --unit-sam-marker-neutral-url );
|
||||
height: var( --unit-sam-marker-height );
|
||||
width: var( --unit-sam-marker-width );
|
||||
background-image: var(--unit-sam-marker-neutral-url);
|
||||
height: var(--unit-sam-marker-height);
|
||||
width: var(--unit-sam-marker-width);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-sam"]:hover .unit-marker {
|
||||
background-image: var( --unit-sam-marker-neutral-hover-url );
|
||||
background-image: var(--unit-sam-marker-neutral-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-sam-marker-neutral-selected-url );
|
||||
background-image: var(--unit-sam-marker-neutral-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-sam"][data-coalition="blue"] .unit-marker {
|
||||
background-image: var( --unit-sam-marker-blue-url );
|
||||
background-image: var(--unit-sam-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"][data-coalition="blue"]:hover .unit-marker {
|
||||
background-image: var( --unit-sam-marker-blue-hover-url );
|
||||
background-image: var(--unit-sam-marker-blue-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"][data-coalition="blue"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-sam-marker-blue-selected-url );
|
||||
background-image: var(--unit-sam-marker-blue-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-sam"][data-coalition="red"] .unit-marker {
|
||||
background-image: var( --unit-sam-marker-red-url );
|
||||
background-image: var(--unit-sam-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"][data-coalition="red"]:hover .unit-marker {
|
||||
background-image: var( --unit-sam-marker-red-hover-url );
|
||||
background-image: var(--unit-sam-marker-red-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"][data-coalition="red"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-sam-marker-red-selected-url );
|
||||
background-image: var(--unit-sam-marker-red-selected-url);
|
||||
}
|
||||
|
||||
|
||||
/* navyunit */
|
||||
|
||||
[data-object|="unit-navyunit"] .unit-selected-spotlight {
|
||||
translate:0 -2px;
|
||||
translate: 0 -2px;
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"] .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-neutral-url );
|
||||
height: var( --unit-navyunit-marker-height );
|
||||
width: var( --unit-navyunit-marker-width );
|
||||
background-image: var(--unit-navyunit-marker-neutral-url);
|
||||
height: var(--unit-navyunit-marker-height);
|
||||
width: var(--unit-navyunit-marker-width);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-navyunit"]:hover .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-neutral-hover-url );
|
||||
background-image: var(--unit-navyunit-marker-neutral-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-neutral-selected-url );
|
||||
background-image: var(--unit-navyunit-marker-neutral-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-navyunit"][data-coalition="blue"] .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-blue-url );
|
||||
background-image: var(--unit-navyunit-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"][data-coalition="blue"]:hover .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-blue-hover-url );
|
||||
background-image: var(--unit-navyunit-marker-blue-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"][data-coalition="blue"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-blue-selected-url );
|
||||
background-image: var(--unit-navyunit-marker-blue-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-navyunit"][data-coalition="red"] .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-red-url );
|
||||
background-image: var(--unit-navyunit-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"][data-coalition="red"]:hover .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-red-hover-url );
|
||||
background-image: var(--unit-navyunit-marker-red-hover-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"][data-coalition="red"][data-is-selected] .unit-marker {
|
||||
background-image: var( --unit-navyunit-marker-red-selected-url );
|
||||
background-image: var(--unit-navyunit-marker-red-selected-url);
|
||||
}
|
||||
|
||||
|
||||
/* Building */
|
||||
|
||||
[data-object|="unit-building"] .unit-marker {
|
||||
background-image: var( --unit-building-marker-neutral-url );
|
||||
height: var( --unit-building-marker-height );
|
||||
width: var( --unit-building-marker-width );
|
||||
background-image: var(--unit-building-marker-neutral-url);
|
||||
height: var(--unit-building-marker-height);
|
||||
width: var(--unit-building-marker-width);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-building"][data-coalition="blue"] .unit-marker {
|
||||
background-image: var( --unit-building-marker-blue-url );
|
||||
background-image: var(--unit-building-marker-blue-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-building"][data-coalition="red"] .unit-marker {
|
||||
background-image: var( --unit-building-marker-red-url );
|
||||
background-image: var(--unit-building-marker-red-url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Weapons */
|
||||
|
||||
[data-object|="unit-missile"], [data-object|="unit-bomb"] {
|
||||
[data-object|="unit-missile"],
|
||||
[data-object|="unit-bomb"] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-object|="unit-missile"] .unit-marker {
|
||||
background-image: var( --unit-missile-marker-neutral-url );
|
||||
height: var( --unit-missile-marker-height );
|
||||
width: var( --unit-missile-marker-width );
|
||||
background-image: var(--unit-missile-marker-neutral-url);
|
||||
height: var(--unit-missile-marker-height);
|
||||
width: var(--unit-missile-marker-width);
|
||||
}
|
||||
|
||||
[data-object|="unit-missile"][data-coalition="blue"] .unit-marker {
|
||||
background-image: var( --unit-missile-marker-blue-url );
|
||||
background-image: var(--unit-missile-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-missile"][data-coalition="red"] .unit-marker {
|
||||
background-image: var( --unit-missile-marker-red-url );
|
||||
background-image: var(--unit-missile-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-bomb"] .unit-marker {
|
||||
background-image: var( --unit-bomb-marker-neutral-url );
|
||||
height: var( --unit-bomb-marker-height );
|
||||
width: var( --unit-bomb-marker-width );
|
||||
background-image: var(--unit-bomb-marker-neutral-url);
|
||||
height: var(--unit-bomb-marker-height);
|
||||
width: var(--unit-bomb-marker-width);
|
||||
}
|
||||
|
||||
[data-object|="unit-bomb"][data-coalition="blue"] .unit-marker {
|
||||
background-image: var( --unit-bomb-marker-blue-url );
|
||||
background-image: var(--unit-bomb-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-bomb"][data-coalition="red"] .unit-marker {
|
||||
background-image: var( --unit-bomb-marker-red-url );
|
||||
background-image: var(--unit-bomb-marker-red-url);
|
||||
}
|
||||
|
||||
|
||||
@@ -336,12 +339,12 @@
|
||||
********************************************/
|
||||
|
||||
[data-object|="unit"] .unit-short-label {
|
||||
color: var( --secondary-gunmetal-grey );
|
||||
color: var(--secondary-gunmetal-grey);
|
||||
font-size: var(--unit-font-size);
|
||||
font-weight: var(--unit-font-weight);
|
||||
line-height: normal;
|
||||
position: absolute;
|
||||
z-index:10;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"] .unit-short-label {
|
||||
@@ -349,81 +352,81 @@
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"] .unit-short-label {
|
||||
translate:0 50%;
|
||||
translate: 0 25%;
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"] .unit-short-label {
|
||||
translate:0 -50%;
|
||||
translate: 0 -50%;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-fuel {
|
||||
background:white;
|
||||
border: var( --unit-aircraft-fuel-border-width ) solid var( --secondary-dark-steel );
|
||||
border-radius: var( --border-radius-sm );
|
||||
display:none;
|
||||
height: var( --unit-aircraft-fuel-height );
|
||||
background: white;
|
||||
border: var(--unit-aircraft-fuel-border-width) solid var(--secondary-dark-steel);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: none;
|
||||
height: var(--unit-aircraft-fuel-height);
|
||||
position: absolute;
|
||||
translate:var( --unit-aircraft-fuel-x ) var( --unit-aircraft-fuel-y );
|
||||
width: var( --unit-aircraft-fuel-width );
|
||||
translate: var(--unit-aircraft-fuel-x) var(--unit-aircraft-fuel-y);
|
||||
width: var(--unit-aircraft-fuel-width);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-fuel-level {
|
||||
background-color: var( --secondary-light-grey );
|
||||
height:100%;
|
||||
width:100%;
|
||||
background-color: var(--secondary-light-grey);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-ammo {
|
||||
column-gap: var( --unit-aircraft-ammo-spacing );
|
||||
display:none;
|
||||
height:fit-content;
|
||||
position:absolute;
|
||||
translate:var( --unit-aircraft-ammo-x ) var( --unit-aircraft-ammo-y );
|
||||
width:fit-content;
|
||||
column-gap: var(--unit-aircraft-ammo-spacing);
|
||||
display: none;
|
||||
height: fit-content;
|
||||
position: absolute;
|
||||
translate: var(--unit-aircraft-ammo-x) var(--unit-aircraft-ammo-y);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-ammo > * {
|
||||
[data-object|="unit"] .unit-ammo>* {
|
||||
background-color: white;
|
||||
border: var( --unit-aircraft-ammo-border-width ) solid var( --secondary-dark-steel );
|
||||
border: var(--unit-aircraft-ammo-border-width) solid var(--secondary-dark-steel);
|
||||
border-radius: 50%;
|
||||
padding: var( --unit-aircraft-ammo-radius );
|
||||
padding: var(--unit-aircraft-ammo-radius);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-summary {
|
||||
pointer-events: none;
|
||||
column-gap: 6px;
|
||||
color:white;
|
||||
display:flex;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size:11px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
justify-content: right;
|
||||
line-height: 12px;
|
||||
position:absolute;
|
||||
position: absolute;
|
||||
row-gap: 1px;
|
||||
text-shadow:
|
||||
-1px -1px 0 #000,
|
||||
-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;
|
||||
width:fit-content;
|
||||
z-index:20;
|
||||
width: fit-content;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
[data-hide-labels] [data-object|="unit"] .unit-summary {
|
||||
display:none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-summary > * {
|
||||
padding:1px;
|
||||
[data-object|="unit"] .unit-summary>* {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-summary .unit-callsign {
|
||||
color:white;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
transform-origin: right;
|
||||
@@ -433,35 +436,35 @@
|
||||
|
||||
[data-object|="unit"] .unit-summary .unit-callsign:hover {
|
||||
direction: rtl;
|
||||
overflow:visible;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[data-object|="unit"][data-pilot|="ai"]:hover .unit-ammo,
|
||||
[data-object|="unit"][data-pilot|="ai"]:hover .unit-fuel {
|
||||
display:flex;
|
||||
[data-object|="unit"]:hover .unit-ammo,
|
||||
[data-object|="unit"]:hover .unit-fuel {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup,
|
||||
[data-object|="unit"][data-pilot|="ai"][data-is-selected] .unit-ammo,
|
||||
[data-object|="unit"][data-pilot|="ai"][data-is-selected] .unit-fuel,
|
||||
[data-object|="unit"][data-is-selected] .unit-ammo,
|
||||
[data-object|="unit"][data-is-selected] .unit-fuel,
|
||||
[data-object|="unit"][data-is-selected] .unit-selected-spotlight {
|
||||
display:flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-has-fox-1] .unit-ammo-fox-1,
|
||||
[data-object|="unit"][data-has-fox-2] .unit-ammo-fox-2,
|
||||
[data-object|="unit"][data-has-fox-3] .unit-ammo-fox-3,
|
||||
[data-object|="unit"][data-has-other-ammo] .unit-ammo-other {
|
||||
background-color: var( --secondary-gunmetal-grey );
|
||||
background-color: var(--secondary-gunmetal-grey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[data-object|="unit"][data-coalition="blue"][data-is-selected] .unit-short-label {
|
||||
color: var( --secondary-blue-text );
|
||||
color: var(--secondary-blue-text);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-coalition="blue"] .unit-fuel-level,
|
||||
@@ -469,16 +472,16 @@
|
||||
[data-object|="unit"][data-coalition="blue"][data-has-fox-2] .unit-ammo-fox-2,
|
||||
[data-object|="unit"][data-coalition="blue"][data-has-fox-3] .unit-ammo-fox-3,
|
||||
[data-object|="unit"][data-coalition="blue"][data-has-other-ammo] .unit-ammo-other {
|
||||
background-color: var( --primary-blue );
|
||||
background-color: var(--primary-blue);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-coalition="blue"] .unit-vvi {
|
||||
background-color: var( --secondary-blue-outline );
|
||||
background-color: var(--secondary-blue-outline);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"][data-coalition="red"][data-is-selected] .unit-short-label {
|
||||
color: var( --secondary-red-text );
|
||||
color: var(--secondary-red-text);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-coalition="red"] .unit-fuel-level,
|
||||
@@ -486,97 +489,104 @@
|
||||
[data-object|="unit"][data-coalition="red"][data-has-fox-2] .unit-ammo-fox-2,
|
||||
[data-object|="unit"][data-coalition="red"][data-has-fox-3] .unit-ammo-fox-3,
|
||||
[data-object|="unit"][data-coalition="red"][data-has-other-ammo] .unit-ammo-other {
|
||||
background-color: var( --primary-red );
|
||||
background-color: var(--primary-red);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-coalition="blue"] .unit-vvi {
|
||||
background-color: var( --secondary-red-outline );
|
||||
background-color: var(--secondary-red-outline);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-pilot|="ai"][data-has-low-fuel] .unit-fuel {
|
||||
[data-object|="unit"][data-has-low-fuel] .unit-fuel {
|
||||
animation: pulse 1.5s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-state {
|
||||
background-repeat: no-repeat;
|
||||
position:absolute;
|
||||
height:var( --unit-aircraft-state-height );
|
||||
width:var( --unit-aircraft-state-width );
|
||||
position: absolute;
|
||||
height: var(--unit-aircraft-state-height);
|
||||
width: var(--unit-aircraft-state-width);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="rtb"] .unit-state {
|
||||
background-image: var( --unit-aircraft-state-rtb );
|
||||
background-image: var(--unit-aircraft-state-rtb);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="land"] .unit-state {
|
||||
background-image: var( --unit-aircraft-state-rtb );
|
||||
background-image: var(--unit-aircraft-state-rtb);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="idle"] .unit-state {
|
||||
background-image: var( --unit-aircraft-state-idle );
|
||||
background-image: var(--unit-aircraft-state-idle);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="attack"] .unit-state {
|
||||
background-image: var( --unit-aircraft-state-attack );
|
||||
background-image: var(--unit-aircraft-state-attack);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="follow"] .unit-state {
|
||||
background-image: var( --unit-aircraft-state-follow );
|
||||
background-image: var(--unit-aircraft-state-follow);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="refuel"] .unit-state {
|
||||
background-image: var( --unit-aircraft-state-refuel );
|
||||
background-image: var(--unit-aircraft-state-refuel);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="human"] .unit-state {
|
||||
background-image: var(--unit-aircraft-state-human);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="dcs"] .unit-state {
|
||||
background-image: var(--unit-aircraft-state-dcs);
|
||||
}
|
||||
|
||||
/*** DEAD ***/
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] {
|
||||
[data-object|="unit-aircraft"][ data-is-dead] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-neutral-dead-url );
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-marker {
|
||||
background-image: var(--unit-aircraft-marker-neutral-dead-url);
|
||||
background-position: 50% 50%;
|
||||
background-size: auto 32px;
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead ][data-coalition="blue"] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-blue-dead-url );
|
||||
[data-object|="unit-aircraft"][ data-is-dead][data-coalition="blue"] .unit-marker {
|
||||
background-image: var(--unit-aircraft-marker-blue-dead-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead ][data-coalition="red"] .unit-marker {
|
||||
background-image: var( --unit-aircraft-marker-red-dead-url );
|
||||
[data-object|="unit-aircraft"][ data-is-dead][data-coalition="red"] .unit-marker {
|
||||
background-image: var(--unit-aircraft-marker-red-dead-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-selected-spotlight,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-short-label,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-vvi,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-hotgroup,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-hotgroup-id,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-state,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-fuel,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-ammo,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ]:hover .unit-fuel,
|
||||
[data-object|="unit-aircraft"][ data-is-dead ]:hover .unit-ammo {
|
||||
display:none !important;
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-selected-spotlight,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-short-label,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-vvi,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-hotgroup,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-hotgroup-id,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-state,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-fuel,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-ammo,
|
||||
[data-object|="unit-aircraft"][ data-is-dead]:hover .unit-fuel,
|
||||
[data-object|="unit-aircraft"][ data-is-dead]:hover .unit-ammo {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-summary > * {
|
||||
display:none;
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead ] .unit-summary .unit-callsign {
|
||||
display:block;
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-summary>* {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-summary .unit-callsign {
|
||||
display: block;
|
||||
}
|
||||
@@ -1,10 +1,83 @@
|
||||
<svg width="63" height="63" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z" fill="white"/>
|
||||
<path d="M35.3291 61L37.3291 61L37.3291 59L37.3291 49.2532L37.3291 47.2532L35.3291 47.2532L27.6709 47.2532L25.6709 47.2532L25.6709 49.2532L25.6709 59L25.6709 61L27.6709 61L35.3291 61Z" fill="#247BE2" stroke="white" stroke-width="4"/>
|
||||
<path d="M47.2529 35.3291V37.3291H49.2529H58.9998H60.9998V35.3291V27.6709V25.6709L58.9998 25.6709H49.2529H47.2529V27.6709V35.3291Z" fill="#247BE2" stroke="white" stroke-width="4"/>
|
||||
<path d="M35.3291 15.7471L37.3291 15.7471L37.3291 13.7471L37.3291 4.00023L37.3291 2.00023L35.3291 2.00023L27.6709 2.00023L25.6709 2.00023L25.6709 4.00023L25.6709 13.7471L25.6709 15.7471L27.6709 15.7471L35.3291 15.7471Z" fill="#247BE2" stroke="white" stroke-width="4"/>
|
||||
<path d="M2 35.3291V37.3291H4H13.7468H15.7468V35.3291L15.7468 27.6709V25.6709L13.7468 25.6709H4H2V27.6709L2 35.3291Z" fill="#247BE2" stroke="white" stroke-width="4"/>
|
||||
<circle cx="31.5001" cy="31.5001" r="15.4494" fill="white" stroke="#247BE2" stroke-width="6"/>
|
||||
<line x1="25.7895" y1="23.9132" x2="36.5242" y2="38.6028" stroke="#247BE2" stroke-width="2" stroke-linecap="square"/>
|
||||
<line x1="38.9357" y1="33.9313" x2="25.9313" y2="33.0643" stroke="#247BE2" stroke-width="2" stroke-linecap="square"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="icon_airbase_blue.svg"
|
||||
id="svg18"
|
||||
version="1.1"
|
||||
fill="none"
|
||||
viewBox="0 0 63 63"
|
||||
height="63"
|
||||
width="63">
|
||||
<metadata
|
||||
id="metadata24">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs22" />
|
||||
<sodipodi:namedview
|
||||
inkscape:current-layer="svg18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:cy="31.5"
|
||||
inkscape:cx="31.5"
|
||||
inkscape:zoom="13.492063"
|
||||
showgrid="false"
|
||||
id="namedview20"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<path
|
||||
id="path2"
|
||||
fill="white"
|
||||
d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z"
|
||||
clip-rule="evenodd"
|
||||
fill-rule="evenodd" />
|
||||
<circle
|
||||
id="circle12"
|
||||
stroke-width="6"
|
||||
stroke="#247BE2"
|
||||
fill="white"
|
||||
r="15.4494"
|
||||
cy="31.5001"
|
||||
cx="31.5001" />
|
||||
<line
|
||||
id="line14"
|
||||
stroke-linecap="square"
|
||||
stroke-width="2"
|
||||
stroke="#247BE2"
|
||||
y2="38.6028"
|
||||
x2="36.5242"
|
||||
y1="23.9132"
|
||||
x1="25.7895" />
|
||||
<line
|
||||
id="line16"
|
||||
stroke-linecap="square"
|
||||
stroke-width="2"
|
||||
stroke="#247BE2"
|
||||
y2="33.0643"
|
||||
x2="25.9313"
|
||||
y1="33.9313"
|
||||
x1="38.9357" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1,10 +1,83 @@
|
||||
<svg width="63" height="63" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z" fill="white"/>
|
||||
<path d="M35.3291 61L37.3291 61L37.3291 59L37.3291 49.2532L37.3291 47.2532L35.3291 47.2532L27.6709 47.2532L25.6709 47.2532L25.6709 49.2532L25.6709 59L25.6709 61L27.6709 61L35.3291 61Z" fill="#CFD9E8" stroke="white" stroke-width="4"/>
|
||||
<path d="M47.2529 35.3291V37.3291H49.2529H58.9998H60.9998V35.3291V27.6709V25.6709L58.9998 25.6709H49.2529H47.2529V27.6709V35.3291Z" fill="#CFD9E8" stroke="white" stroke-width="4"/>
|
||||
<path d="M35.3291 15.7471L37.3291 15.7471L37.3291 13.7471L37.3291 4.00023L37.3291 2.00023L35.3291 2.00023L27.6709 2.00023L25.6709 2.00023L25.6709 4.00023L25.6709 13.7471L25.6709 15.7471L27.6709 15.7471L35.3291 15.7471Z" fill="#CFD9E8" stroke="white" stroke-width="4"/>
|
||||
<path d="M2 35.3291V37.3291H4H13.7468H15.7468V35.3291L15.7468 27.6709V25.6709L13.7468 25.6709H4H2V27.6709L2 35.3291Z" fill="#CFD9E8" stroke="white" stroke-width="4"/>
|
||||
<circle cx="31.5001" cy="31.5001" r="15.4494" fill="white" stroke="#CFD9E8" stroke-width="6"/>
|
||||
<line x1="25.7895" y1="23.9132" x2="36.5242" y2="38.6028" stroke="#CFD9E8" stroke-width="2" stroke-linecap="square"/>
|
||||
<line x1="38.9357" y1="33.9313" x2="25.9313" y2="33.0643" stroke="#CFD9E8" stroke-width="2" stroke-linecap="square"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="icon_airbase_neutral.svg"
|
||||
id="svg18"
|
||||
version="1.1"
|
||||
fill="none"
|
||||
viewBox="0 0 63 63"
|
||||
height="63"
|
||||
width="63">
|
||||
<metadata
|
||||
id="metadata24">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs22" />
|
||||
<sodipodi:namedview
|
||||
inkscape:current-layer="svg18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:cy="31.5"
|
||||
inkscape:cx="31.5"
|
||||
inkscape:zoom="13.492063"
|
||||
showgrid="false"
|
||||
id="namedview20"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<path
|
||||
id="path2"
|
||||
fill="white"
|
||||
d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z"
|
||||
clip-rule="evenodd"
|
||||
fill-rule="evenodd" />
|
||||
<circle
|
||||
id="circle12"
|
||||
stroke-width="6"
|
||||
stroke="#CFD9E8"
|
||||
fill="white"
|
||||
r="15.4494"
|
||||
cy="31.5001"
|
||||
cx="31.5001" />
|
||||
<line
|
||||
id="line14"
|
||||
stroke-linecap="square"
|
||||
stroke-width="2"
|
||||
stroke="#CFD9E8"
|
||||
y2="38.6028"
|
||||
x2="36.5242"
|
||||
y1="23.9132"
|
||||
x1="25.7895" />
|
||||
<line
|
||||
id="line16"
|
||||
stroke-linecap="square"
|
||||
stroke-width="2"
|
||||
stroke="#CFD9E8"
|
||||
y2="33.0643"
|
||||
x2="25.9313"
|
||||
y1="33.9313"
|
||||
x1="38.9357" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1,10 +1,83 @@
|
||||
<svg width="63" height="63" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z" fill="white"/>
|
||||
<path d="M35.3291 61L37.3291 61L37.3291 59L37.3291 49.2532L37.3291 47.2532L35.3291 47.2532L27.6709 47.2532L25.6709 47.2532L25.6709 49.2532L25.6709 59L25.6709 61L27.6709 61L35.3291 61Z" fill="#ff5858" stroke="white" stroke-width="4"/>
|
||||
<path d="M47.2529 35.3291V37.3291H49.2529H58.9998H60.9998V35.3291V27.6709V25.6709L58.9998 25.6709H49.2529H47.2529V27.6709V35.3291Z" fill="#ff5858" stroke="white" stroke-width="4"/>
|
||||
<path d="M35.3291 15.7471L37.3291 15.7471L37.3291 13.7471L37.3291 4.00023L37.3291 2.00023L35.3291 2.00023L27.6709 2.00023L25.6709 2.00023L25.6709 4.00023L25.6709 13.7471L25.6709 15.7471L27.6709 15.7471L35.3291 15.7471Z" fill="#ff5858" stroke="white" stroke-width="4"/>
|
||||
<path d="M2 35.3291V37.3291H4H13.7468H15.7468V35.3291L15.7468 27.6709V25.6709L13.7468 25.6709H4H2V27.6709L2 35.3291Z" fill="#ff5858" stroke="white" stroke-width="4"/>
|
||||
<circle cx="31.5001" cy="31.5001" r="15.4494" fill="white" stroke="#ff5858" stroke-width="6"/>
|
||||
<line x1="25.7895" y1="23.9132" x2="36.5242" y2="38.6028" stroke="#ff5858" stroke-width="2" stroke-linecap="square"/>
|
||||
<line x1="38.9357" y1="33.9313" x2="25.9313" y2="33.0643" stroke="#ff5858" stroke-width="2" stroke-linecap="square"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="icon_airbase_red.svg"
|
||||
id="svg18"
|
||||
version="1.1"
|
||||
fill="none"
|
||||
viewBox="0 0 63 63"
|
||||
height="63"
|
||||
width="63">
|
||||
<metadata
|
||||
id="metadata24">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs22" />
|
||||
<sodipodi:namedview
|
||||
inkscape:current-layer="svg18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:cy="31.5"
|
||||
inkscape:cx="31.5"
|
||||
inkscape:zoom="13.492063"
|
||||
showgrid="false"
|
||||
id="namedview20"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<path
|
||||
id="path2"
|
||||
fill="white"
|
||||
d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z"
|
||||
clip-rule="evenodd"
|
||||
fill-rule="evenodd" />
|
||||
<circle
|
||||
id="circle12"
|
||||
stroke-width="6"
|
||||
stroke="#ff5858"
|
||||
fill="white"
|
||||
r="15.4494"
|
||||
cy="31.5001"
|
||||
cx="31.5001" />
|
||||
<line
|
||||
id="line14"
|
||||
stroke-linecap="square"
|
||||
stroke-width="2"
|
||||
stroke="#ff5858"
|
||||
y2="38.6028"
|
||||
x2="36.5242"
|
||||
y1="23.9132"
|
||||
x1="25.7895" />
|
||||
<line
|
||||
id="line16"
|
||||
stroke-linecap="square"
|
||||
stroke-width="2"
|
||||
stroke="#ff5858"
|
||||
y2="33.0643"
|
||||
x2="25.9313"
|
||||
y1="33.9313"
|
||||
x1="38.9357" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
71
client/public/themes/olympus/images/state_dcs.svg
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="19"
|
||||
height="15"
|
||||
viewBox="0 0 19 15"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="state_dcs.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="19.610428"
|
||||
inkscape:cx="1.0453622"
|
||||
inkscape:cy="12.21289"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<rect
|
||||
style="fill:#262626;fill-opacity:1;stroke:none;stroke-width:5.63192;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect4324"
|
||||
width="5.226213"
|
||||
height="7.0879617"
|
||||
x="1.5176032"
|
||||
y="4.8923955"
|
||||
rx="1.6057953" />
|
||||
<rect
|
||||
style="fill:#262626;fill-opacity:1;stroke:none;stroke-width:5.74938;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect4324-6"
|
||||
width="5.5898962"
|
||||
height="6.9061198"
|
||||
x="11.848835"
|
||||
y="4.9978404"
|
||||
rx="1.7175397" />
|
||||
<rect
|
||||
style="fill:#262626;fill-opacity:1;stroke:none;stroke-width:4.02396;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect4324-7"
|
||||
width="3.214294"
|
||||
height="5.8832622"
|
||||
x="7.7873521"
|
||||
y="0.59372008"
|
||||
rx="1.5281793" />
|
||||
<rect
|
||||
style="fill:#262626;fill-opacity:1;stroke:none;stroke-width:3.84388;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect4100"
|
||||
width="12.76161"
|
||||
height="12.015139"
|
||||
x="3.0333979"
|
||||
y="1.9770601"
|
||||
rx="2.5930805" />
|
||||
<path
|
||||
d="m 9.3852027,1.9350755 c 0.358221,0 0.6476303,0.2894099 0.6476303,0.6476303 v 1.2952618 h 2.428616 c 0.805492,0 1.457171,0.6516785 1.457171,1.4571695 v 5.5048619 c 0,0.805491 -0.651679,1.457169 -1.457171,1.457169 H 6.3089565 c -0.8054909,0 -1.4571694,-0.651678 -1.4571694,-1.457169 V 5.3351371 c 0,-0.805491 0.6516785,-1.4571695 1.4571694,-1.4571695 H 8.7375714 V 2.5827058 c 0,-0.3582204 0.2894111,-0.6476303 0.6476313,-0.6476303 z M 7.1184944,9.7066454 c -0.1780979,0 -0.3238152,0.145716 -0.3238152,0.3238146 0,0.178099 0.1457173,0.323817 0.3238152,0.323817 h 0.6476303 c 0.1780991,0 0.3238164,-0.145718 0.3238164,-0.323817 0,-0.1780986 -0.1457173,-0.3238146 -0.3238164,-0.3238146 z m 1.9428933,0 c -0.178099,0 -0.3238163,0.145716 -0.3238163,0.3238146 0,0.178099 0.1457173,0.323817 0.3238163,0.323817 h 0.64763 c 0.178099,0 0.3238153,-0.145718 0.3238153,-0.323817 0,-0.1780986 -0.1457163,-0.3238146 -0.3238153,-0.3238146 z m 1.9428923,0 c -0.178098,0 -0.323815,0.145716 -0.323815,0.3238146 0,0.178099 0.145717,0.323817 0.323815,0.323817 h 0.647631 c 0.178098,0 0.323816,-0.145718 0.323816,-0.323817 0,-0.1780986 -0.145718,-0.3238146 -0.323816,-0.3238146 z M 8.2518487,7.1161213 a 0.8095391,0.8095391 0 1 0 -1.6190782,0 0.8095391,0.8095391 0 1 0 1.6190782,0 z m 3.0762473,0.809538 a 0.80953865,0.80953865 0 1 0 0,-1.6190771 0.80953865,0.80953865 0 1 0 0,1.6190771 z M 3.8803405,6.4684911 h 0.323815 v 3.8857859 h -0.323815 c -0.5363195,0 -0.9714467,-0.4351276 -0.9714467,-0.9714486 V 7.4399365 c 0,-0.5363182 0.4351272,-0.9714454 0.9714467,-0.9714454 z m 11.0097255,0 c 0.536319,0 0.971446,0.4351272 0.971446,0.9714454 v 1.9428919 c 0,0.536321 -0.435127,0.9714486 -0.971446,0.9714486 H 14.566249 V 6.4684911 Z"
|
||||
id="path3711"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.0202384" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
51
client/public/themes/olympus/images/state_human.svg
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="19"
|
||||
height="15"
|
||||
viewBox="0 0 19 15"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="state_human.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="27.733334"
|
||||
inkscape:cx="1.8930288"
|
||||
inkscape:cy="5.3185095"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<ellipse
|
||||
style="fill:#262626;fill-opacity:1;stroke:none;stroke-width:12.2489;stroke-dashoffset:5.6;paint-order:stroke fill markers"
|
||||
id="path1149"
|
||||
cx="9.2425327"
|
||||
cy="4.9208517"
|
||||
rx="5.0100894"
|
||||
ry="4.9463482" />
|
||||
<path
|
||||
style="fill:none;stroke:#262626;stroke-width:3.3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 4.5129051,12.490473 C 3.7913718,12.480652 3.327463,12.244253 3.2890664,11.649084 3.2075443,9.1160904 4.4151773,8.6100056 5.7622404,8.0795609 l 6.6154506,-0.012246 c 2.45028,0.2890709 3.047775,1.846629 3.047775,3.4797821 0.0127,0.360443 -0.331282,0.901957 -1.478805,0.943376 z"
|
||||
id="path1359"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<path
|
||||
d="m 9.2659737,7.6041687 c 1.6415273,0 2.9733343,-1.2969197 2.9733343,-2.8954494 0,-1.5985298 -1.331807,-2.8954501 -2.9733343,-2.8954501 -1.6415283,0 -2.973335,1.2969203 -2.973335,2.8954501 0,1.5985297 1.3318067,2.8954494 2.973335,2.8954494 z m 2.6429633,0.6434347 h -1.137714 c -0.458391,0.2050939 -0.9683982,0.3217163 -1.5052493,0.3217163 -0.5368511,0 -1.0447965,-0.1166224 -1.5052498,-0.3217163 H 6.6230093 c -1.4598251,0 -2.6429652,1.152148 -2.6429652,2.5737386 v 0.321716 c 0,0.532843 0.4439358,0.965149 0.991112,0.965149 H 13.56079 c 0.547177,0 0.991112,-0.432306 0.991112,-0.965149 v -0.321716 c 0,-1.4215906 -1.18314,-2.5737386 -2.642965,-2.5737386 z"
|
||||
id="path916"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.0203759" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1256
client/public/themes/olympus/images/task_tanker.svg
Normal file
|
After Width: | Height: | Size: 91 KiB |
@@ -3,35 +3,35 @@
|
||||
/** Colours **/
|
||||
|
||||
/*** Coalition: neutral **/
|
||||
--primary-grey : #CFD9E8;
|
||||
--secondary-neutral : #111111;
|
||||
--primary-neutral : #949ba7;
|
||||
--secondary-neutral-outline : #111111;
|
||||
--secondary-neutral-text : #111111;
|
||||
|
||||
/*** Coalition: blue **/
|
||||
--primary-blue : #247be2;
|
||||
--secondary-blue-outline : #082e44;
|
||||
--secondary-blue-text : #017DC1;
|
||||
--primary-blue : #247be2;
|
||||
--secondary-blue-outline : #082e44;
|
||||
--secondary-blue-text : #017DC1;
|
||||
|
||||
/*** Coalition: red **/
|
||||
--primary-red : #ff5858;
|
||||
--secondary-red-outline : #262222;
|
||||
--secondary-red-text : #D42121;
|
||||
--primary-red : #ff5858;
|
||||
--secondary-red-outline : #262222;
|
||||
--secondary-red-text : #D42121;
|
||||
|
||||
--accent-green : #8bff63;
|
||||
--accent-light-blue : #5ca7ff;
|
||||
--accent-light-red : #F5B6B6;
|
||||
|
||||
--accent-green : #8bff63;
|
||||
--accent-light-blue : #5ca7ff;
|
||||
--accent-light-red : #F5B6B6;
|
||||
--background-grey : #3d4651;
|
||||
--background-slate-blue : #363c43;
|
||||
--background-offwhite : #f2f2f3;
|
||||
--background-steel : #202831;
|
||||
|
||||
--background-grey : #3d4651;
|
||||
--background-slate-blue : #363c43;
|
||||
--background-offwhite : #f2f2f3;
|
||||
--background-steel : #202831;
|
||||
--secondary-dark-steel : #181e25;
|
||||
--secondary-gunmetal-grey : #2f2f2f;
|
||||
--secondary-light-grey : #797e83;
|
||||
--secondary-yellow : #ffd46893;
|
||||
|
||||
--secondary-dark-steel : #181e25;
|
||||
--secondary-gunmetal-grey : #2f2f2f;
|
||||
--secondary-light-grey : #797e83;
|
||||
--secondary-yellow : #ffd46893;
|
||||
|
||||
--background-hover : #f2f2f333;
|
||||
--background-hover : #f2f2f333;
|
||||
|
||||
--nav-text : #ECECEC;
|
||||
|
||||
@@ -132,6 +132,8 @@
|
||||
--unit-aircraft-state-attack: url( "/themes/olympus/images/state_attack.svg" );
|
||||
--unit-aircraft-state-follow: url( "/themes/olympus/images/state_follow.svg" );
|
||||
--unit-aircraft-state-refuel: url( "/themes/olympus/images/state_refuel.svg" );
|
||||
--unit-aircraft-state-human: url( "/themes/olympus/images/state_human.svg" );
|
||||
--unit-aircraft-state-dcs: url( "/themes/olympus/images/state_dcs.svg" );
|
||||
|
||||
/*** Ground ***/
|
||||
--unit-groundunit-marker-height: 50px;
|
||||
|
||||
6
client/src/@types/unit.d.ts
vendored
@@ -29,11 +29,7 @@ interface MissionData {
|
||||
}
|
||||
|
||||
interface FormationData {
|
||||
formation: string;
|
||||
isLeader: boolean;
|
||||
isWingman: boolean;
|
||||
leaderID: number;
|
||||
wingmenIDs: number[];
|
||||
}
|
||||
|
||||
interface TaskData {
|
||||
@@ -44,14 +40,12 @@ interface TaskData {
|
||||
targetAltitude: number;
|
||||
isTanker: boolean;
|
||||
isAWACS: boolean;
|
||||
TACANOn: boolean;
|
||||
TACANChannel: number;
|
||||
TACANXY: string;
|
||||
TACANCallsign: string;
|
||||
radioFrequency: number;
|
||||
radioCallsign: number;
|
||||
radioCallsignNumber: number;
|
||||
radioAMFM: string;
|
||||
}
|
||||
|
||||
interface OptionsData {
|
||||
|
||||
@@ -8,7 +8,7 @@ export class AICFormation_Single extends AICFormation implements AICFormationInt
|
||||
"name" = "single";
|
||||
"numGroups" = 1;
|
||||
"summary" = "One contact on its own";
|
||||
"unitBreakdown" = [];
|
||||
"unitBreakdown": string[] = [];
|
||||
|
||||
constructor() {
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class ATCDataHandler {
|
||||
|
||||
startUpdates() {
|
||||
|
||||
this.#updateInterval = setInterval( () => {
|
||||
this.#updateInterval = window.setInterval( () => {
|
||||
|
||||
const aBoardIsVisible = this.#atc.getBoards().some( board => board.boardIsVisible() );
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ export abstract class ATCBoard {
|
||||
});
|
||||
|
||||
|
||||
setInterval( () => {
|
||||
window.setInterval( () => {
|
||||
this.updateClock();
|
||||
}, 1000 );
|
||||
|
||||
@@ -206,7 +206,7 @@ export abstract class ATCBoard {
|
||||
this.#strips[ flightId ].element.remove();
|
||||
this.#strips[ flightId ].isDeleted = true;
|
||||
|
||||
setTimeout( () => {
|
||||
window.setTimeout( () => {
|
||||
delete this.#strips[ flightId ];
|
||||
}, 10000 );
|
||||
|
||||
@@ -326,7 +326,7 @@ export abstract class ATCBoard {
|
||||
|
||||
resetSuggestions();
|
||||
|
||||
searchTimeout = setTimeout( () => {
|
||||
searchTimeout = window.setTimeout( () => {
|
||||
|
||||
const searchString = unitName.value.toLowerCase();
|
||||
|
||||
@@ -408,7 +408,7 @@ export abstract class ATCBoard {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#updateInterval = setInterval( () => {
|
||||
this.#updateInterval = window.setInterval( () => {
|
||||
|
||||
this.update();
|
||||
|
||||
|
||||
@@ -49,12 +49,12 @@ export class ContextMenu {
|
||||
if (this.#x + this.#container.offsetWidth < window.innerWidth)
|
||||
this.#container.style.left = this.#x + "px";
|
||||
else
|
||||
this.#container.style.left = window.innerWidth - this.#container.offsetWidth + "px";
|
||||
this.#container.style.left = window.innerWidth - this.#container.offsetWidth - 10 + "px";
|
||||
|
||||
if (this.#y + this.#container.offsetHeight < window.innerHeight)
|
||||
this.#container.style.top = this.#y + "px";
|
||||
else
|
||||
this.#container.style.top = window.innerHeight - this.#container.offsetHeight + "px";
|
||||
this.#container.style.top = window.innerHeight - this.#container.offsetHeight - 10 + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,17 +19,19 @@ export class Dropdown {
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
this.#value.addEventListener( "click", ev => {
|
||||
this.#element.classList.toggle( "is-open" );
|
||||
this.#value.addEventListener("click", (ev) => {
|
||||
this.#element.classList.toggle("is-open");
|
||||
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
|
||||
this.#clip();
|
||||
});
|
||||
|
||||
this.#options.classList.add( "ol-scrollable" );
|
||||
document.addEventListener("click", (ev) => {
|
||||
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#element.contains(ev.target as Node))) {
|
||||
this.#element.classList.remove("is-open");
|
||||
}
|
||||
});
|
||||
|
||||
// Commented out since it is a bit frustrating, particularly when the dropdown opens towards the top and not to the bottom
|
||||
//this.#element.addEventListener("mouseleave", ev => {
|
||||
// this.#close();
|
||||
//});
|
||||
this.#options.classList.add( "ol-scrollable" );
|
||||
}
|
||||
|
||||
setOptions(optionsList: string[])
|
||||
@@ -46,7 +48,7 @@ export class Dropdown {
|
||||
|
||||
button.addEventListener("click", (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
this.#value.innerText = option;
|
||||
this.#value.innerHTML = `<div class = "ol-ellipsed"> ${option} </div>`;
|
||||
this.#close();
|
||||
this.#callback(option, e);
|
||||
this.#index = idx;
|
||||
@@ -69,7 +71,7 @@ export class Dropdown {
|
||||
if (idx < this.#optionsList.length)
|
||||
{
|
||||
var option = this.#optionsList[idx];
|
||||
this.#value.innerText = option;
|
||||
this.#value.innerHTML = `<div class = "ol-ellipsed"> ${option} </div>`;
|
||||
this.#index = idx;
|
||||
this.#close();
|
||||
this.#callback(option);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getActiveCoalition, setActiveCoalition } from "..";
|
||||
import { getActiveCoalition, getMap, setActiveCoalition } from "..";
|
||||
import { spawnAircraft, spawnGroundUnit, spawnSmoke } from "../server/server";
|
||||
import { aircraftDatabase } from "../units/aircraftdatabase";
|
||||
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
|
||||
@@ -25,7 +25,8 @@ export class MapContextMenu extends ContextMenu {
|
||||
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('change', (e) => this.#onSwitch(e));
|
||||
this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('click', (e) => this.#onToggleLeftClick(e));
|
||||
this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('contextmenu', (e) => this.#onToggleRightClick(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));
|
||||
@@ -41,14 +42,20 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.hide();
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions)
|
||||
{
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng);
|
||||
spawnAircraft(this.#spawnOptions);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeployGroundUnit", () => {
|
||||
this.hide();
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions)
|
||||
{
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng);
|
||||
spawnGroundUnit(this.#spawnOptions);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
|
||||
@@ -97,15 +104,28 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#spawnOptions.latlng = latlng;
|
||||
}
|
||||
|
||||
#onSwitch(e: any) {
|
||||
#onToggleLeftClick(e: any) {
|
||||
if (this.getContainer() != null) {
|
||||
if (e.srcElement.checked)
|
||||
if (e.srcElement.dataset.activeCoalition == "blue")
|
||||
setActiveCoalition("neutral");
|
||||
else if (e.srcElement.dataset.activeCoalition == "neutral")
|
||||
setActiveCoalition("red");
|
||||
else
|
||||
setActiveCoalition("blue");
|
||||
}
|
||||
}
|
||||
|
||||
#onToggleRightClick(e: any) {
|
||||
if (this.getContainer() != null) {
|
||||
if (e.srcElement.dataset.activeCoalition == "red")
|
||||
setActiveCoalition("neutral");
|
||||
else if (e.srcElement.dataset.activeCoalition == "neutral")
|
||||
setActiveCoalition("blue");
|
||||
else
|
||||
setActiveCoalition("red");
|
||||
}
|
||||
}
|
||||
|
||||
/********* Aircraft spawn menu *********/
|
||||
#setAircraftRole(role: string) {
|
||||
this.#spawnOptions.role = role;
|
||||
@@ -135,7 +155,6 @@ export class MapContextMenu extends ContextMenu {
|
||||
image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`;
|
||||
image.classList.toggle("hide", false);
|
||||
}
|
||||
|
||||
this.clip();
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export class FeatureSwitches {
|
||||
}),
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
"defaultEnabled": true,
|
||||
"label": "Show splash screen",
|
||||
"masterSwitch": true,
|
||||
"name": "splashScreen"
|
||||
@@ -116,36 +116,24 @@ export class FeatureSwitches {
|
||||
|
||||
|
||||
#testSwitches() {
|
||||
|
||||
for ( const featureSwitch of this.#featureSwitches ) {
|
||||
|
||||
if ( featureSwitch.isEnabled() ) {
|
||||
|
||||
if ( typeof featureSwitch.onEnabled === "function" ) {
|
||||
featureSwitch.onEnabled();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
document.querySelectorAll( "[data-feature-switch='" + featureSwitch.name + "']" ).forEach( el => {
|
||||
|
||||
if ( featureSwitch.removeArtifactsIfDisabled === false ) {
|
||||
el.remove();
|
||||
} else {
|
||||
el.classList.add( "hide" );
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
document.body.classList.toggle( "feature-" + featureSwitch.name, featureSwitch.isEnabled() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
savePreferences() {
|
||||
|
||||
let preferences:any = {};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AIC } from "./aic/aic";
|
||||
import { ATC } from "./atc/atc";
|
||||
import { FeatureSwitches } from "./featureswitches";
|
||||
import { LogPanel } from "./panels/logpanel";
|
||||
import { getAirbases, getBullseye as getBullseyes, getConfig, getMission, getUnits, setAddress, toggleDemoEnabled } from "./server/server";
|
||||
import { getAirbases, getBullseye, getConfig, getFreezed, getMission, getUnits, setAddress, setCredentials, setFreezed, startUpdate, toggleDemoEnabled } from "./server/server";
|
||||
import { UnitDataTable } from "./units/unitdatatable";
|
||||
import { keyEventWasInInput } from "./other/utils";
|
||||
import { Popup } from "./popups/popup";
|
||||
@@ -31,20 +31,16 @@ var logPanel: LogPanel;
|
||||
|
||||
var infoPopup: Popup;
|
||||
|
||||
var connected: boolean = false;
|
||||
var paused: boolean = false;
|
||||
var activeCoalition: string = "blue";
|
||||
|
||||
var sessionHash: string | null = null;
|
||||
var unitDataTable: UnitDataTable;
|
||||
|
||||
var featureSwitches;
|
||||
|
||||
function setup() {
|
||||
|
||||
featureSwitches = new FeatureSwitches();
|
||||
|
||||
/* Initialize base functionalitites*/
|
||||
/* Initialize base functionalitites */
|
||||
map = new Map('map-container');
|
||||
unitsManager = new UnitsManager();
|
||||
missionHandler = new MissionHandler();
|
||||
@@ -59,13 +55,16 @@ function setup() {
|
||||
/* Popups */
|
||||
infoPopup = new Popup("info-popup");
|
||||
|
||||
/* Controls */
|
||||
new Dropdown("app-icon", () => { });
|
||||
|
||||
/* Unit data table */
|
||||
unitDataTable = new UnitDataTable("unit-data-table");
|
||||
|
||||
/* AIC */
|
||||
let aicFeatureSwitch = featureSwitches.getSwitch("aic");
|
||||
if (aicFeatureSwitch?.isEnabled()) {
|
||||
aic = new AIC();
|
||||
// TODO: add back buttons
|
||||
}
|
||||
|
||||
/* ATC */
|
||||
@@ -75,90 +74,29 @@ function setup() {
|
||||
atc.startUpdates();
|
||||
}
|
||||
|
||||
|
||||
new Dropdown( "app-icon", () => {} );
|
||||
|
||||
/* Setup event handlers */
|
||||
setupEvents();
|
||||
|
||||
getConfig(readConfig)
|
||||
/* Load the config file */
|
||||
getConfig(readConfig);
|
||||
}
|
||||
|
||||
function readConfig(config: any)
|
||||
{
|
||||
if (config && config["server"] != undefined && config["server"]["address"] != undefined && config["server"]["port"] != undefined)
|
||||
{
|
||||
const address = config["server"]["address"];
|
||||
const port = config["server"]["port"];
|
||||
if ((typeof address === 'string' || address instanceof String) && typeof port == 'number')
|
||||
{
|
||||
setAddress(window.location.hostname, <number>port);
|
||||
}
|
||||
|
||||
/* On the first connection, force request of full data */
|
||||
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
|
||||
getBullseyes((data: BullseyesData) => getMissionData()?.update(data));
|
||||
getMission((data: any) => {getMissionData()?.update(data)});
|
||||
getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */);
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
startPeriodicUpdate();
|
||||
function readConfig(config: any) {
|
||||
if (config && config["address"] != undefined && config["port"] != undefined) {
|
||||
const address = config["address"];
|
||||
const port = config["port"];
|
||||
if (typeof address === 'string' && typeof port == 'number')
|
||||
setAddress(address == "*" ? window.location.hostname : address, <number>port);
|
||||
}
|
||||
else {
|
||||
throw new Error('Could not read configuration file!');
|
||||
}
|
||||
}
|
||||
|
||||
function startPeriodicUpdate() {
|
||||
requestUpdate();
|
||||
requestRefresh();
|
||||
}
|
||||
|
||||
function requestUpdate() {
|
||||
/* Main update rate = 250ms is minimum time, equal to server update time. */
|
||||
getUnits((data: UnitsData) => {
|
||||
if (!getPaused()){
|
||||
getUnitsManager()?.update(data);
|
||||
checkSessionHash(data.sessionHash);
|
||||
}
|
||||
}, false);
|
||||
setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
|
||||
|
||||
getConnectionStatusPanel()?.update(getConnected());
|
||||
}
|
||||
|
||||
function requestRefresh() {
|
||||
/* Main refresh rate = 5000ms. */
|
||||
getUnits((data: UnitsData) => {
|
||||
if (!getPaused()){
|
||||
getUnitsManager()?.update(data);
|
||||
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
|
||||
getBullseyes((data: BullseyesData) => getMissionData()?.update(data));
|
||||
getMission((data: any) => {getMissionData()?.update(data)});
|
||||
|
||||
// Update the list of existing units
|
||||
getUnitDataTable()?.update();
|
||||
|
||||
checkSessionHash(data.sessionHash);
|
||||
}
|
||||
}, true);
|
||||
setTimeout(() => requestRefresh(), 5000);
|
||||
}
|
||||
|
||||
function checkSessionHash(newSessionHash: string) {
|
||||
if (sessionHash != null) {
|
||||
if (newSessionHash != sessionHash)
|
||||
location.reload();
|
||||
}
|
||||
else
|
||||
sessionHash = newSessionHash;
|
||||
}
|
||||
|
||||
function setupEvents() {
|
||||
/* Generic clicks */
|
||||
document.addEventListener("click", (ev) => {
|
||||
if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) {
|
||||
|
||||
const target = ev.target;
|
||||
|
||||
if (target.classList.contains("olympus-dialog-close")) {
|
||||
@@ -166,7 +104,7 @@ function setupEvents() {
|
||||
}
|
||||
|
||||
const triggerElement = target.closest("[data-on-click]");
|
||||
|
||||
|
||||
if (triggerElement instanceof HTMLElement) {
|
||||
const eventName: string = triggerElement.dataset.onClick || "";
|
||||
let params = JSON.parse(triggerElement.dataset.onClickParams || "{}");
|
||||
@@ -183,55 +121,79 @@ function setupEvents() {
|
||||
|
||||
/* Keyup events */
|
||||
document.addEventListener("keyup", ev => {
|
||||
|
||||
if ( keyEventWasInInput( ev ) ) {
|
||||
if (keyEventWasInInput(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ev.code) {
|
||||
case "KeyL":
|
||||
document.body.toggleAttribute("data-hide-labels");
|
||||
break;
|
||||
case "KeyD":
|
||||
case "KeyT":
|
||||
toggleDemoEnabled();
|
||||
break;
|
||||
case "Quote":
|
||||
unitDataTable.toggle();
|
||||
break
|
||||
case "Space":
|
||||
setPaused(!getPaused());
|
||||
setFreezed(!getFreezed());
|
||||
break;
|
||||
case "KeyW":
|
||||
case "KeyA":
|
||||
case "KeyS":
|
||||
case "KeyD":
|
||||
case "ArrowLeft":
|
||||
case "ArrowRight":
|
||||
case "ArrowUp":
|
||||
case "ArrowDown":
|
||||
getMap().handleMapPanning(ev);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
const unitName = document.getElementById( "unit-name" );
|
||||
if ( unitName instanceof HTMLInputElement ) {
|
||||
unitName.addEventListener( "change", ev => {
|
||||
unitName.setAttribute( "disabled", "true" );
|
||||
unitName.setAttribute( "readonly", "true" );
|
||||
|
||||
// Do something with this:
|
||||
});
|
||||
|
||||
document.addEventListener( "editUnitName", ev => {
|
||||
unitName.removeAttribute( "disabled" );
|
||||
unitName.removeAttribute( "readonly" );
|
||||
unitName.focus();
|
||||
});
|
||||
}
|
||||
//*/
|
||||
|
||||
document.addEventListener( "closeDialog", (ev: CustomEventInit) => {
|
||||
ev.detail._element.closest( ".ol-dialog" ).classList.add( "hide" );
|
||||
/* Keydown events */
|
||||
document.addEventListener("keydown", ev => {
|
||||
if (keyEventWasInInput(ev)) {
|
||||
return;
|
||||
}
|
||||
switch (ev.code) {
|
||||
case "KeyW":
|
||||
case "KeyA":
|
||||
case "KeyS":
|
||||
case "KeyD":
|
||||
case "ArrowLeft":
|
||||
case "ArrowRight":
|
||||
case "ArrowUp":
|
||||
case "ArrowDown":
|
||||
getMap().handleMapPanning(ev);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener( "toggleElements", (ev: CustomEventInit) => {
|
||||
document.querySelectorAll( ev.detail.selector ).forEach( el => {
|
||||
el.classList.toggle( "hide" );
|
||||
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
|
||||
ev.detail._element.closest(".ol-dialog").classList.add("hide");
|
||||
});
|
||||
|
||||
document.addEventListener("toggleElements", (ev: CustomEventInit) => {
|
||||
document.querySelectorAll(ev.detail.selector).forEach(el => {
|
||||
el.classList.toggle("hide");
|
||||
})
|
||||
});
|
||||
|
||||
document.addEventListener("tryConnection", () => {
|
||||
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
|
||||
const username = (<HTMLInputElement> (form?.querySelector("#username"))).value;
|
||||
const password = (<HTMLInputElement> (form?.querySelector("#password"))).value;
|
||||
setCredentials(username, btoa("admin" + ":" + password));
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
startUpdate();
|
||||
|
||||
setConnectionStatus("connecting");
|
||||
})
|
||||
|
||||
document.addEventListener("reloadPage", () => {
|
||||
location.reload();
|
||||
})
|
||||
}
|
||||
|
||||
export function getMap() {
|
||||
@@ -279,23 +241,10 @@ export function getActiveCoalition() {
|
||||
return activeCoalition;
|
||||
}
|
||||
|
||||
export function setConnected(newConnected: boolean) {
|
||||
if (connected != newConnected)
|
||||
newConnected? getInfoPopup().setText("Connected to DCS Olympus server"): getInfoPopup().setText("Disconnected from DCS Olympus server");
|
||||
connected = newConnected;
|
||||
}
|
||||
|
||||
export function getConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
export function setPaused(newPaused: boolean) {
|
||||
paused = newPaused;
|
||||
paused? getInfoPopup().setText("Paused"): getInfoPopup().setText("Unpaused");
|
||||
}
|
||||
|
||||
export function getPaused() {
|
||||
return paused;
|
||||
export function setConnectionStatus(status: string) {
|
||||
const el = document.querySelector("#connection-status") as HTMLElement;
|
||||
if (el)
|
||||
el.dataset["status"] = status;
|
||||
}
|
||||
|
||||
export function getInfoPopup() {
|
||||
|
||||
@@ -45,25 +45,28 @@ export var BoxSelect = Handler.extend({
|
||||
},
|
||||
|
||||
_onMouseDown: function (e: any) {
|
||||
if (((e.which !== 1) && (e.button !== 0))) { return false; }
|
||||
if ((e.which == 1 && e.button == 0 && e.shiftKey)) {
|
||||
|
||||
// Clear the deferred resetState if it hasn't executed yet, otherwise it
|
||||
// will interrupt the interaction and orphan a box element in the container.
|
||||
this._clearDeferredResetState();
|
||||
this._resetState();
|
||||
// Clear the deferred resetState if it hasn't executed yet, otherwise it
|
||||
// will interrupt the interaction and orphan a box element in the container.
|
||||
this._clearDeferredResetState();
|
||||
this._resetState();
|
||||
|
||||
DomUtil.disableTextSelection();
|
||||
DomUtil.disableImageDrag();
|
||||
DomUtil.disableTextSelection();
|
||||
DomUtil.disableImageDrag();
|
||||
|
||||
this._startPoint = this._map.mouseEventToContainerPoint(e);
|
||||
this._startPoint = this._map.mouseEventToContainerPoint(e);
|
||||
|
||||
//@ts-ignore
|
||||
DomEvent.on(document, {
|
||||
contextmenu: DomEvent.stop,
|
||||
mousemove: this._onMouseMove,
|
||||
mouseup: this._onMouseUp,
|
||||
keydown: this._onKeyDown
|
||||
}, this);
|
||||
//@ts-ignore
|
||||
DomEvent.on(document, {
|
||||
contextmenu: DomEvent.stop,
|
||||
mousemove: this._onMouseMove,
|
||||
mouseup: this._onMouseUp,
|
||||
keydown: this._onKeyDown
|
||||
}, this);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_onMouseMove: function (e: any) {
|
||||
@@ -114,7 +117,7 @@ export var BoxSelect = Handler.extend({
|
||||
if (!this._moved) { return; }
|
||||
// Postpone to next JS tick so internal click event handling
|
||||
// still see it as "moved".
|
||||
setTimeout(Util.bind(this._resetState, this), 0);
|
||||
window.setTimeout(Util.bind(this._resetState, this), 0);
|
||||
var bounds = new LatLngBounds(
|
||||
this._map.containerPointToLatLng(this._startPoint),
|
||||
this._map.containerPointToLatLng(this._point));
|
||||
|
||||
@@ -1,25 +1,56 @@
|
||||
import * as L from "leaflet"
|
||||
import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map";
|
||||
|
||||
import { getUnitsManager } from "..";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
import { MapContextMenu } from "../controls/mapcontextmenu";
|
||||
import { MapContextMenu, SpawnOptions } from "../controls/mapcontextmenu";
|
||||
import { UnitContextMenu } from "../controls/unitcontextmenu";
|
||||
import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Airbase } from "../missionhandler/airbase";
|
||||
import { Unit } from "../units/unit";
|
||||
|
||||
// TODO a bit of a hack, this module is provided as pure javascript only
|
||||
require("../../node_modules/leaflet.nauticscale/dist/leaflet.nauticscale.js")
|
||||
|
||||
export const IDLE = "IDLE";
|
||||
export const MOVE_UNIT = "MOVE_UNIT";
|
||||
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
|
||||
|
||||
var temporaryIcon = new L.Icon({
|
||||
iconUrl: 'images/icon-temporary.png',
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 26]
|
||||
});
|
||||
|
||||
export class ClickableMiniMap extends MiniMap {
|
||||
constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) {
|
||||
super(layer, options);
|
||||
}
|
||||
|
||||
getMap() {
|
||||
//@ts-ignore needed to access not exported member. A bit of a hack, required to access click events
|
||||
return this._miniMap;
|
||||
}
|
||||
}
|
||||
|
||||
export class Map extends L.Map {
|
||||
#state: string;
|
||||
#layer: L.TileLayer | null = null;
|
||||
#preventLeftClick: boolean = false;
|
||||
#leftClickTimer: number = 0;
|
||||
#deafultPanDelta: number = 100;
|
||||
#panInterval: number | null = null;
|
||||
#panLeft: boolean = false;
|
||||
#panRight: boolean = false;
|
||||
#panUp: boolean = false;
|
||||
#panDown: boolean = false;
|
||||
#lastMousePosition: L.Point = new L.Point(0, 0);
|
||||
#centerUnit: Unit | null = null;
|
||||
#miniMap: ClickableMiniMap | null = null;
|
||||
#miniMapLayerGroup: L.LayerGroup;
|
||||
#temporaryMarkers: L.Marker[] = [];
|
||||
|
||||
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
|
||||
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
|
||||
@@ -30,19 +61,67 @@ export class Map extends L.Map {
|
||||
constructor(ID: string) {
|
||||
/* Init the leaflet map */
|
||||
//@ts-ignore
|
||||
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: false });
|
||||
this.setView([37.23, -115.8], 12);
|
||||
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.setLayer("ArcGIS Satellite");
|
||||
|
||||
/* Minimap */
|
||||
/* Draw the limits of the maps in the minimap*/
|
||||
var latlngs = [[ // NTTR
|
||||
new L.LatLng(39.7982463, -119.985425),
|
||||
new L.LatLng(34.4037128, -119.7806729),
|
||||
new L.LatLng(34.3483316, -112.4529351),
|
||||
new L.LatLng(39.7372411, -112.1130805),
|
||||
new L.LatLng(39.7982463, -119.985425)
|
||||
],
|
||||
[ // Syria
|
||||
new L.LatLng(37.3630556, 29.2686111),
|
||||
new L.LatLng(31.8472222, 29.8975),
|
||||
new L.LatLng(32.1358333, 42.1502778),
|
||||
new L.LatLng(37.7177778, 42.3716667),
|
||||
new L.LatLng(37.3630556, 29.2686111)
|
||||
],
|
||||
[ // Caucasus
|
||||
new L.LatLng(39.6170191, 27.634935),
|
||||
new L.LatLng(38.8735863, 47.1423108),
|
||||
new L.LatLng(47.3907982, 49.3101946),
|
||||
new L.LatLng(48.3955879, 26.7753625),
|
||||
new L.LatLng(39.6170191, 27.634935)
|
||||
],
|
||||
[ // Persian Gulf
|
||||
new L.LatLng(32.9355285, 46.5623682),
|
||||
new L.LatLng(21.729393, 47.572675),
|
||||
new L.LatLng(21.8501348, 63.9734737),
|
||||
new L.LatLng(33.131584, 64.7313594),
|
||||
new L.LatLng(32.9355285, 46.5623682)
|
||||
],
|
||||
[ // Marianas
|
||||
new L.LatLng(22.09, 135.0572222),
|
||||
new L.LatLng(10.5777778, 135.7477778),
|
||||
new L.LatLng(10.7725, 149.3918333),
|
||||
new L.LatLng(22.5127778, 149.5427778),
|
||||
new L.LatLng(22.09, 135.0572222)
|
||||
]
|
||||
];
|
||||
|
||||
var minimapLayer = new L.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { minZoom: 0, maxZoom: 13 });
|
||||
this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]);
|
||||
var miniMapPolyline = new L.Polyline(latlngs, { color: '#202831' });
|
||||
miniMapPolyline.addTo(this.#miniMapLayerGroup);
|
||||
|
||||
/* Scale */
|
||||
//@ts-ignore TODO more hacking because the module is provided as a pure javascript module only
|
||||
L.control.scalenautic({ position: "topright", maxWidth: 300, nautic: true, metric: true, imperial: false }).addTo(this);
|
||||
|
||||
/* Init the state machine */
|
||||
this.#state = IDLE;
|
||||
|
||||
/* Register event handles */
|
||||
this.on("click", (e: any) => this.#onClick(e));
|
||||
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
|
||||
this.on("zoomstart", (e: any) => this.#onZoom(e));
|
||||
this.on("drag", (e: any) => this.centerOnUnit(null));
|
||||
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
|
||||
this.on("zoomstart", (e: any) => this.#onZoom(e));
|
||||
this.on("drag", (e: any) => this.centerOnUnit(null));
|
||||
this.on("contextmenu", (e: any) => this.#onContextMenu(e));
|
||||
this.on('selectionend', (e: any) => this.#onSelectionEnd(e));
|
||||
this.on('mousedown', (e: any) => this.#onMouseDown(e));
|
||||
@@ -54,7 +133,7 @@ export class Map extends L.Map {
|
||||
document.body.toggleAttribute("data-hide-" + ev.detail.coalition);
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
|
||||
document.body.toggleAttribute("data-hide-" + ev.detail.category);
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
@@ -64,8 +143,13 @@ export class Map extends L.Map {
|
||||
if (this.#centerUnit != null && ev.detail == this.#centerUnit)
|
||||
this.#panToUnit(this.#centerUnit);
|
||||
});
|
||||
|
||||
|
||||
this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers())
|
||||
|
||||
this.#panInterval = window.setInterval(() => {
|
||||
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));
|
||||
}, 20);
|
||||
}
|
||||
|
||||
setLayer(layerName: string) {
|
||||
@@ -120,10 +204,10 @@ export class Map extends L.Map {
|
||||
setState(state: string) {
|
||||
this.#state = state;
|
||||
if (this.#state === IDLE) {
|
||||
L.DomUtil.removeClass(this.getContainer(),'crosshair-cursor-enabled');
|
||||
L.DomUtil.removeClass(this.getContainer(), 'crosshair-cursor-enabled');
|
||||
}
|
||||
else if (this.#state === MOVE_UNIT) {
|
||||
L.DomUtil.addClass(this.getContainer(),'crosshair-cursor-enabled');
|
||||
L.DomUtil.addClass(this.getContainer(), 'crosshair-cursor-enabled');
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("mapStateChanged"));
|
||||
}
|
||||
@@ -133,8 +217,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
/* Context Menus */
|
||||
hideAllContextMenus()
|
||||
{
|
||||
hideAllContextMenus() {
|
||||
this.hideMapContextMenu();
|
||||
this.hideUnitContextMenu();
|
||||
this.hideAirbaseContextMenu();
|
||||
@@ -153,7 +236,7 @@ export class Map extends L.Map {
|
||||
document.dispatchEvent(new CustomEvent("mapContextMenu"));
|
||||
}
|
||||
|
||||
getMapContextMenu(){
|
||||
getMapContextMenu() {
|
||||
return this.#mapContextMenu;
|
||||
}
|
||||
|
||||
@@ -164,7 +247,7 @@ export class Map extends L.Map {
|
||||
this.#unitContextMenu.show(x, y, e.latlng);
|
||||
}
|
||||
|
||||
getUnitContextMenu(){
|
||||
getUnitContextMenu() {
|
||||
return this.#unitContextMenu;
|
||||
}
|
||||
|
||||
@@ -180,7 +263,7 @@ export class Map extends L.Map {
|
||||
this.#airbaseContextMenu.setAirbase(airbase);
|
||||
}
|
||||
|
||||
getAirbaseContextMenu(){
|
||||
getAirbaseContextMenu() {
|
||||
return this.#airbaseContextMenu;
|
||||
}
|
||||
|
||||
@@ -198,14 +281,12 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
/* Spawn from air base */
|
||||
spawnFromAirbase(e: any)
|
||||
{
|
||||
spawnFromAirbase(e: any) {
|
||||
//this.#aircraftSpawnMenu(e);
|
||||
}
|
||||
|
||||
centerOnUnit(ID: number | null){
|
||||
if (ID != null)
|
||||
{
|
||||
centerOnUnit(ID: number | null) {
|
||||
if (ID != null) {
|
||||
this.options.scrollWheelZoom = 'center';
|
||||
this.#centerUnit = getUnitsManager().getUnitByID(ID);
|
||||
}
|
||||
@@ -215,12 +296,120 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
|
||||
setTheatre(theatre: string) {
|
||||
var bounds = new L.LatLngBounds([-90, -180], [90, 180]);
|
||||
var miniMapZoom = 5;
|
||||
if (theatre == "Syria")
|
||||
bounds = new L.LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]);
|
||||
else if (theatre == "MarianaIslands")
|
||||
bounds = new L.LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]);
|
||||
else if (theatre == "Nevada")
|
||||
bounds = new L.LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805])
|
||||
else if (theatre == "PersianGulf")
|
||||
bounds = new L.LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594])
|
||||
else if (theatre == "Falklands") {
|
||||
// TODO
|
||||
}
|
||||
else if (theatre == "Caucasus") {
|
||||
bounds = new L.LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946])
|
||||
miniMapZoom = 4;
|
||||
}
|
||||
|
||||
this.setView(bounds.getCenter(), 8);
|
||||
//this.setMaxBounds(bounds);
|
||||
|
||||
if (this.#miniMap)
|
||||
this.#miniMap.remove();
|
||||
|
||||
//@ts-ignore // Needed because some of the inputs are wrong in the original module interface
|
||||
this.#miniMap = new ClickableMiniMap(this.#miniMapLayerGroup, { position: "topright", width: 192 * 1.5, height: 108 * 1.5, zoomLevelFixed: miniMapZoom, centerFixed: bounds.getCenter() }).addTo(this);
|
||||
this.#miniMap.disableInteractivity();
|
||||
this.#miniMap.getMap().on("click", (e: any) => {
|
||||
if (this.#miniMap)
|
||||
this.setView(e.latlng);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
getMiniMapLayerGroup() {
|
||||
return this.#miniMapLayerGroup;
|
||||
}
|
||||
|
||||
handleMapPanning(e: any) {
|
||||
if (e.type === "keyup"){
|
||||
switch (e.code) {
|
||||
case "KeyA":
|
||||
case "ArrowLeft":
|
||||
this.#panLeft = false;
|
||||
break;
|
||||
case "KeyD":
|
||||
case "ArrowRight":
|
||||
this.#panRight = false;
|
||||
break;
|
||||
case "KeyW":
|
||||
case "ArrowUp":
|
||||
this.#panUp = false;
|
||||
break;
|
||||
case "KeyS":
|
||||
case "ArrowDown":
|
||||
this.#panDown = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (e.code)
|
||||
{
|
||||
case 'KeyA':
|
||||
case 'ArrowLeft':
|
||||
this.#panLeft = true;
|
||||
break;
|
||||
case 'KeyD':
|
||||
case 'ArrowRight':
|
||||
this.#panRight = true;
|
||||
break;
|
||||
case 'KeyW':
|
||||
case 'ArrowUp':
|
||||
this.#panUp = true;
|
||||
break;
|
||||
case 'KeyS':
|
||||
case 'ArrowDown':
|
||||
this.#panDown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addTemporaryMarker(latlng: L.LatLng) {
|
||||
var marker = new L.Marker(latlng, {icon: temporaryIcon});
|
||||
marker.addTo(this);
|
||||
this.#temporaryMarkers.push(marker);
|
||||
}
|
||||
|
||||
removeTemporaryMarker(latlng: L.LatLng) {
|
||||
var d: number | null = null;
|
||||
var closest: L.Marker | null = null;
|
||||
var i: number = 0;
|
||||
this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => {
|
||||
var t = latlng.distanceTo(marker.getLatLng());
|
||||
if (d == null || t < d) {
|
||||
d = t;
|
||||
closest = marker;
|
||||
i = idx;
|
||||
}
|
||||
});
|
||||
if (closest)
|
||||
{
|
||||
this.removeLayer(closest);
|
||||
delete this.#temporaryMarkers[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
#onClick(e: any) {
|
||||
if (!this.#preventLeftClick) {
|
||||
this.hideAllContextMenus();
|
||||
if (this.#state === IDLE) {
|
||||
|
||||
|
||||
}
|
||||
else if (this.#state === MOVE_UNIT) {
|
||||
this.setState(IDLE);
|
||||
@@ -230,7 +419,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
#onDoubleClick(e: any) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
#onContextMenu(e: any) {
|
||||
@@ -248,44 +437,34 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
|
||||
#onSelectionEnd(e: any)
|
||||
{
|
||||
#onSelectionEnd(e: any) {
|
||||
clearTimeout(this.#leftClickTimer);
|
||||
this.#preventLeftClick = true;
|
||||
this.#leftClickTimer = setTimeout(() => {
|
||||
this.#preventLeftClick = false;
|
||||
this.#leftClickTimer = window.setTimeout(() => {
|
||||
this.#preventLeftClick = false;
|
||||
}, 200);
|
||||
getUnitsManager().selectFromBounds(e.selectionBounds);
|
||||
}
|
||||
|
||||
#onMouseDown(e: any)
|
||||
{
|
||||
#onMouseDown(e: any) {
|
||||
this.hideAllContextMenus();
|
||||
if ((e.originalEvent.which == 1) && (e.originalEvent.button == 0))
|
||||
this.dragging.disable();
|
||||
}
|
||||
|
||||
#onMouseUp(e: any)
|
||||
{
|
||||
if ((e.originalEvent.which == 1) && (e.originalEvent.button == 0))
|
||||
this.dragging.enable();
|
||||
#onMouseUp(e: any) {
|
||||
}
|
||||
|
||||
#onMouseMove(e: any)
|
||||
{
|
||||
#onMouseMove(e: any) {
|
||||
this.#lastMousePosition.x = e.originalEvent.x;
|
||||
this.#lastMousePosition.y = e.originalEvent.y;
|
||||
}
|
||||
|
||||
#onZoom(e: any)
|
||||
{
|
||||
#onZoom(e: any) {
|
||||
if (this.#centerUnit != null)
|
||||
this.#panToUnit(this.#centerUnit);
|
||||
}
|
||||
|
||||
#panToUnit(unit: Unit)
|
||||
{
|
||||
#panToUnit(unit: Unit) {
|
||||
var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude);
|
||||
this.setView(unitPosition, this.getZoom(), {animate: false});
|
||||
this.setView(unitPosition, this.getZoom(), { animate: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Marker, LatLng, Icon } from "leaflet";
|
||||
import { getInfoPopup, getMap, getUnitsManager } from "..";
|
||||
import { getInfoPopup, getMap } from "..";
|
||||
import { Airbase } from "./airbase";
|
||||
|
||||
var bullseyeIcons = [
|
||||
@@ -43,21 +43,10 @@ export class MissionHandler
|
||||
|
||||
if ("mission" in data)
|
||||
{
|
||||
if (data.mission.theatre != this.#theatre)
|
||||
if (data.mission != null && data.mission.theatre != this.#theatre)
|
||||
{
|
||||
this.#theatre = data.mission.theatre
|
||||
if (this.#theatre == "Syria")
|
||||
getMap().setView(new LatLng(34.5, 36.0), 8);
|
||||
else if (this.#theatre == "MarianaIslands")
|
||||
getMap().setView(new LatLng(16.7, 145.7), 7);
|
||||
else if (this.#theatre == "Nevada")
|
||||
getMap().setView(new LatLng(37.1, -115.2), 8);
|
||||
else if (this.#theatre == "PersianGulf")
|
||||
getMap().setView(new LatLng(26.5, 55.3), 8);
|
||||
else if (this.#theatre == "Falklands")
|
||||
getMap().setView(new LatLng(-50.6, -42.7), 7);
|
||||
else if (this.#theatre == "Caucasus")
|
||||
getMap().setView(new LatLng(42.1, 42.3), 8);
|
||||
this.#theatre = data.mission.theatre;
|
||||
getMap().setTheatre(this.#theatre);
|
||||
|
||||
getInfoPopup().setText("Map set to " + this.#theatre);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export class MouseInfoPanel extends Panel {
|
||||
|
||||
el.dataset.bearing = zeroAppend(Math.floor(bear), 3);
|
||||
el.dataset.distance = zeroAppend(Math.floor(dist*0.000539957), 3);
|
||||
el.dataset.distanceUnits = "nm";
|
||||
el.dataset.distanceUnits = "NM";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ export class MouseInfoPanel extends Panel {
|
||||
bng = "360";
|
||||
}
|
||||
|
||||
let data = [ `${bng}°`, `${Math.floor(dist*0.000539957)}nm`, `${reciprocal}°` ];
|
||||
let data = [ `${bng}°`, `${Math.floor(dist*0.000539957)}NM`, `${reciprocal}°` ];
|
||||
|
||||
if ( bear < 180 ) {
|
||||
data = data.reverse();
|
||||
@@ -229,7 +229,7 @@ export class MouseInfoPanel extends Panel {
|
||||
|
||||
pos.dataset.bearing = "---";
|
||||
pos.dataset.distance = "---";
|
||||
pos.dataset.distanceUnits = "nm";
|
||||
pos.dataset.distanceUnits = "NM";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,9 @@ export class Panel {
|
||||
this.#visible = true;
|
||||
}
|
||||
|
||||
protected onHide() {}
|
||||
|
||||
hide() {
|
||||
this.#element.classList.toggle("hide", true);
|
||||
this.#visible = false;
|
||||
|
||||
this.onHide();
|
||||
}
|
||||
|
||||
toggle() {
|
||||
|
||||
@@ -72,8 +72,9 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
this.#advancedSettingsDialog = <HTMLElement> document.querySelector("#advanced-settings-dialog");
|
||||
|
||||
document.addEventListener("unitUpdated", (e: CustomEvent<Unit>) => { if (e.detail.getSelected()) this.update() });
|
||||
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => { this.show(); this.update() });
|
||||
window.setInterval(() => {this.update();}, 25);
|
||||
|
||||
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => { this.show(); this.addButtons();});
|
||||
document.addEventListener("clearSelection", () => { this.hide() });
|
||||
document.addEventListener("applyAdvancedSettings", () => {this.#applyAdvancedSettings();})
|
||||
document.addEventListener("showAdvancedSettings", () => {
|
||||
@@ -84,13 +85,65 @@ export class UnitControlPanel extends Panel {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
|
||||
// Do this after panel is hidden (make sure there's a reset)
|
||||
protected onHide() {
|
||||
hide() {
|
||||
super.hide();
|
||||
|
||||
this.#expectedAltitude = -1;
|
||||
this.#expectedSpeed = -1;
|
||||
}
|
||||
|
||||
addButtons() {
|
||||
var units = getUnitsManager().getSelectedUnits();
|
||||
if (units.length < 20) {
|
||||
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
|
||||
let database: UnitDatabase | null;
|
||||
if (unit instanceof Aircraft)
|
||||
database = aircraftDatabase;
|
||||
else if (unit instanceof GroundUnit)
|
||||
database = groundUnitsDatabase;
|
||||
else
|
||||
database = null; // TODO add databases for other unit types
|
||||
|
||||
var button = document.createElement("button");
|
||||
var callsign = unit.getBaseData().unitName || "";
|
||||
|
||||
button.setAttribute("data-short-label", database?.getByName(unit.getBaseData().name)?.shortLabel || unit.getBaseData().name);
|
||||
button.setAttribute("data-callsign", callsign);
|
||||
|
||||
button.setAttribute("data-coalition", unit.getMissionData().coalition);
|
||||
button.classList.add("pill", "highlight-coalition")
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
getUnitsManager().deselectAllUnits();
|
||||
getUnitsManager().selectUnit(unit.ID, true);
|
||||
});
|
||||
return (button);
|
||||
}));
|
||||
} else {
|
||||
var el = document.createElement("div");
|
||||
el.innerText = "Too many units selected"
|
||||
this.getElement().querySelector("#selected-units-container")?.replaceChildren(el);
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.getVisible()){
|
||||
var units = getUnitsManager().getSelectedUnits();
|
||||
this.getElement().querySelector("#advanced-settings-div")?.classList.toggle("hide", units.length != 1);
|
||||
if (this.getElement() != null && units.length > 0) {
|
||||
this.#showFlightControlSliders(units);
|
||||
|
||||
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value))
|
||||
});
|
||||
|
||||
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update function will only be allowed to update the sliders once it's matched the expected value for the first time (due to lag of Ajax request)
|
||||
#updateCanSetAltitudeSlider(altitude: number) {
|
||||
@@ -109,46 +162,6 @@ export class UnitControlPanel extends Panel {
|
||||
return false;
|
||||
}
|
||||
|
||||
update() {
|
||||
var units = getUnitsManager().getSelectedUnits();
|
||||
|
||||
this.getElement().querySelector("#advanced-settings-div")?.classList.toggle("hide", units.length != 1);
|
||||
|
||||
if (this.getElement() != null && units.length > 0) {
|
||||
this.#showFlightControlSliders(units);
|
||||
|
||||
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
|
||||
let database: UnitDatabase | null;
|
||||
if (unit instanceof Aircraft)
|
||||
database = aircraftDatabase;
|
||||
else if (unit instanceof GroundUnit)
|
||||
database = groundUnitsDatabase;
|
||||
else
|
||||
database = null; // TODO add databases for other unit types
|
||||
|
||||
var button = document.createElement("button");
|
||||
var callsign = unit.getBaseData().unitName || "";
|
||||
|
||||
button.setAttribute("data-short-label", database?.getByName(unit.getBaseData().name)?.shortLabel || "");
|
||||
button.setAttribute("data-callsign", callsign);
|
||||
|
||||
button.setAttribute("data-coalition", unit.getMissionData().coalition);
|
||||
button.classList.add("pill", "highlight-coalition")
|
||||
|
||||
button.addEventListener("click", () => getUnitsManager().selectUnit(unit.ID, true));
|
||||
return (button);
|
||||
}));
|
||||
|
||||
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value))
|
||||
});
|
||||
|
||||
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#showFlightControlSliders(units: Unit[]) {
|
||||
if (getUnitsManager().getSelectedUnitsType() !== undefined)
|
||||
this.#airspeedSlider.show()
|
||||
@@ -215,7 +228,7 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
// Default values for "normal" units
|
||||
this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]);
|
||||
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign);
|
||||
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1);
|
||||
|
||||
// Input values
|
||||
var tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")
|
||||
@@ -237,7 +250,7 @@ export class UnitControlPanel extends Panel {
|
||||
this.#radioDecimalsDropdown.setValue("." + radioDecimals);
|
||||
|
||||
// Make sure its in the valid range
|
||||
if (!this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign))
|
||||
if (!this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1))
|
||||
this.#radioCallsignDropdown.selectValue(0);
|
||||
|
||||
// Set options for tankers
|
||||
@@ -245,7 +258,7 @@ export class UnitControlPanel extends Panel {
|
||||
if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){
|
||||
this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.remove("hide");
|
||||
this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"]);
|
||||
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign);
|
||||
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1);
|
||||
}
|
||||
else {
|
||||
this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.add("hide");
|
||||
@@ -255,7 +268,7 @@ export class UnitControlPanel extends Panel {
|
||||
if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){
|
||||
this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.remove("hide");
|
||||
this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"]);
|
||||
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign);
|
||||
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1);
|
||||
} else {
|
||||
this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.add("hide");
|
||||
}
|
||||
@@ -267,12 +280,14 @@ export class UnitControlPanel extends Panel {
|
||||
{
|
||||
const isTanker = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.checked? true: false;
|
||||
const isAWACS = this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.checked? true: false;
|
||||
|
||||
const TACANChannel = Number(this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input")?.value);
|
||||
const TACANXY = this.#TACANXYDropdown.getValue();
|
||||
const TACANCallsign = <string> this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input")?.value
|
||||
|
||||
const radioMHz = Number(this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input")?.value);
|
||||
const radioDecimals = this.#radioDecimalsDropdown.getValue();
|
||||
const radioCallsign = this.#radioCallsignDropdown.getIndex();
|
||||
const radioCallsign = this.#radioCallsignDropdown.getIndex() + 1;
|
||||
const radioCallsignNumber = Number(this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input")?.value);
|
||||
|
||||
var radioFrequency = (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000;
|
||||
|
||||
@@ -42,7 +42,7 @@ export class UnitInfoPanel extends Panel {
|
||||
|
||||
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => this.#onUnitsSelection(e.detail));
|
||||
document.addEventListener("unitsDeselection", (e: CustomEvent<Unit[]>) => this.#onUnitsDeselection(e.detail));
|
||||
document.addEventListener("clearSelection", () => this.#onUnitsDeselection([]));
|
||||
document.addEventListener("clearSelection", (e: CustomEvent<Unit[]>) => this.#onUnitsDeselection([]));
|
||||
document.addEventListener("unitUpdated", (e: CustomEvent<Unit>) => this.#onUnitUpdate(e.detail));
|
||||
|
||||
this.hide();
|
||||
@@ -54,7 +54,7 @@ export class UnitInfoPanel extends Panel {
|
||||
const baseData = unit.getBaseData();
|
||||
|
||||
/* Set the unit info */
|
||||
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || "";
|
||||
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
|
||||
this.#unitName.innerText = baseData.unitName;
|
||||
this.#unitControl.innerText = ( ( baseData.AI ) ? "AI" : "Human" ) + " controlled";
|
||||
// this.#groupName.innerText = baseData.groupName;
|
||||
|
||||
@@ -17,10 +17,10 @@ export class Popup extends Panel {
|
||||
|
||||
clearTimeout(this.#visibilityTimer);
|
||||
clearTimeout(this.#hideTimer);
|
||||
this.#visibilityTimer = setTimeout(() => {
|
||||
this.#visibilityTimer = window.setTimeout(() => {
|
||||
this.getElement().classList.remove("visible");
|
||||
this.getElement().classList.add("invisible");
|
||||
this.#hideTimer = setTimeout(() => this.hide(), 2000);
|
||||
this.#hideTimer = window.setTimeout(() => this.hide(), 2000);
|
||||
}, this.#fadeTime);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as L from 'leaflet'
|
||||
import { setConnected } from '..';
|
||||
import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..';
|
||||
import { SpawnOptions } from '../controls/mapcontextmenu';
|
||||
|
||||
var connected: boolean = false;
|
||||
var freezed: boolean = false;
|
||||
|
||||
var REST_ADDRESS = "http://localhost:30000/olympus";
|
||||
var DEMO_ADDRESS = window.location.href + "demo";
|
||||
const UNITS_URI = "units";
|
||||
@@ -10,29 +13,46 @@ const AIRBASES_URI = "airbases";
|
||||
const BULLSEYE_URI = "bullseyes";
|
||||
const MISSION_URI = "mission";
|
||||
|
||||
var username = "";
|
||||
var credentials = "";
|
||||
|
||||
var sessionHash: string | null = null;
|
||||
var lastUpdateTime = 0;
|
||||
var demoEnabled = false;
|
||||
|
||||
export function toggleDemoEnabled()
|
||||
{
|
||||
export function toggleDemoEnabled() {
|
||||
demoEnabled = !demoEnabled;
|
||||
}
|
||||
|
||||
export function GET(callback: CallableFunction, uri: string){
|
||||
export function setCredentials(newUsername: string, newCredentials: string) {
|
||||
username = newUsername;
|
||||
credentials = newCredentials;
|
||||
}
|
||||
|
||||
export function GET(callback: CallableFunction, uri: string, options?: string) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("GET", `${demoEnabled? DEMO_ADDRESS: REST_ADDRESS}/${uri}`, true);
|
||||
xmlHttp.open("GET", `${demoEnabled? DEMO_ADDRESS: REST_ADDRESS}/${uri}${options? options: ''}`, true);
|
||||
if (credentials)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
|
||||
xmlHttp.onload = function (e) {
|
||||
var data = JSON.parse(xmlHttp.responseText);
|
||||
if (parseInt(data.time) > lastUpdateTime)
|
||||
{
|
||||
callback(data);
|
||||
lastUpdateTime = parseInt(data.time);
|
||||
if (isNaN(lastUpdateTime))
|
||||
lastUpdateTime = 0;
|
||||
setConnected(true);
|
||||
if (xmlHttp.status == 200) {
|
||||
var data = JSON.parse(xmlHttp.responseText);
|
||||
if (uri !== UNITS_URI || parseInt(data.time) > lastUpdateTime)
|
||||
{
|
||||
callback(data);
|
||||
lastUpdateTime = parseInt(data.time);
|
||||
if (isNaN(lastUpdateTime))
|
||||
lastUpdateTime = 0;
|
||||
setConnected(true);
|
||||
}
|
||||
} else if (xmlHttp.status == 401) {
|
||||
console.error("Incorrect username/password");
|
||||
setConnectionStatus("failed");
|
||||
} else {
|
||||
setConnected(false);
|
||||
}
|
||||
};
|
||||
xmlHttp.onerror = function () {
|
||||
xmlHttp.onerror = function (res) {
|
||||
console.error("An error occurred during the XMLHttpRequest");
|
||||
setConnected(false);
|
||||
};
|
||||
@@ -40,13 +60,15 @@ export function GET(callback: CallableFunction, uri: string){
|
||||
}
|
||||
|
||||
export function POST(request: object, callback: CallableFunction){
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", demoEnabled? DEMO_ADDRESS: REST_ADDRESS);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("PUT", demoEnabled? DEMO_ADDRESS: REST_ADDRESS);
|
||||
xmlHttp.setRequestHeader("Content-Type", "application/json");
|
||||
if (credentials)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
|
||||
xmlHttp.onreadystatechange = () => {
|
||||
callback();
|
||||
};
|
||||
xhr.send(JSON.stringify(request));
|
||||
xmlHttp.send(JSON.stringify(request));
|
||||
}
|
||||
|
||||
export function getConfig(callback: CallableFunction) {
|
||||
@@ -84,7 +106,7 @@ export function getMission(callback: CallableFunction) {
|
||||
}
|
||||
|
||||
export function getUnits(callback: CallableFunction, refresh: boolean = false) {
|
||||
GET(callback, `${UNITS_URI}?time=${refresh? 0: lastUpdateTime}`);
|
||||
GET(callback, `${UNITS_URI}`, `?time=${refresh? 0: lastUpdateTime}`);
|
||||
}
|
||||
|
||||
export function addDestination(ID: number, path: any) {
|
||||
@@ -208,4 +230,81 @@ export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolea
|
||||
|
||||
var data = { "setAdvancedOptions": command };
|
||||
POST(data, () => { });
|
||||
}
|
||||
|
||||
export function startUpdate() {
|
||||
/* On the first connection, force request of full data */
|
||||
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
|
||||
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
|
||||
getMission((data: any) => { getMissionData()?.update(data) });
|
||||
getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */);
|
||||
|
||||
requestUpdate();
|
||||
requestRefresh();
|
||||
}
|
||||
|
||||
export function requestUpdate() {
|
||||
/* Main update rate = 250ms is minimum time, equal to server update time. */
|
||||
getUnits((data: UnitsData) => {
|
||||
if (!getFreezed()) {
|
||||
getUnitsManager()?.update(data);
|
||||
checkSessionHash(data.sessionHash);
|
||||
}
|
||||
}, false);
|
||||
window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
|
||||
|
||||
getConnectionStatusPanel()?.update(getConnected());
|
||||
}
|
||||
|
||||
export function requestRefresh() {
|
||||
/* Main refresh rate = 5000ms. */
|
||||
getUnits((data: UnitsData) => {
|
||||
if (!getFreezed()) {
|
||||
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) {
|
||||
if (sessionHash != null) {
|
||||
if (newSessionHash != sessionHash)
|
||||
location.reload();
|
||||
}
|
||||
else
|
||||
sessionHash = newSessionHash;
|
||||
}
|
||||
|
||||
export function setConnected(newConnected: boolean) {
|
||||
if (connected != newConnected)
|
||||
newConnected ? getInfoPopup().setText("Connected to DCS Olympus server") : getInfoPopup().setText("Disconnected from DCS Olympus server");
|
||||
connected = newConnected;
|
||||
|
||||
if (connected) {
|
||||
document.querySelector("#splash-screen")?.classList.add("hide");
|
||||
document.querySelector("#gray-out")?.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
export function getConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
export function setFreezed(newFreezed: boolean) {
|
||||
freezed = newFreezed;
|
||||
freezed ? getInfoPopup().setText("Freezed") : getInfoPopup().setText("Unfreezed");
|
||||
}
|
||||
|
||||
export function getFreezed() {
|
||||
return freezed;
|
||||
}
|
||||
571
client/src/units/helicopterdatabase.ts
Normal file
@@ -0,0 +1,571 @@
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class HelicopterDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super();
|
||||
this.blueprints = {
|
||||
"AH-64D_BLK_II": {
|
||||
"name": "AH-64D_BLK_II",
|
||||
"label": "AH-64D Apache",
|
||||
"shortLabel": "AH64",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "AGM-114k Hellfire",
|
||||
"quantity": 8
|
||||
},
|
||||
{
|
||||
"name": "M151 Rocket Pod",
|
||||
"quantity": 2
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "2 * M261: M151 (6PD), 2 * Hellfire station: 4*AGM-114K",
|
||||
"name": "Gun / ATGM / Rocket"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "AGM-114K Hellfire",
|
||||
"quantity": 16
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "4 * Hellfire station: 4*AGM-114K",
|
||||
"name": "Gun / ATGM"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "ah-64.png"
|
||||
},
|
||||
"Ka-50_3": {
|
||||
"name": "Ka-50_3",
|
||||
"label": "Ka-50 Hokum A",
|
||||
"shortLabel": "K50",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "Igla",
|
||||
"quantity": 4
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAP"
|
||||
],
|
||||
"code": "4xIgla",
|
||||
"name": "Gun / Fox 2"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "Igla",
|
||||
"quantity": 4
|
||||
},
|
||||
{
|
||||
"name": "S-13",
|
||||
"quantity": 10
|
||||
},
|
||||
{
|
||||
"name": "Kh-25ML",
|
||||
"quantity": 2
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"Anti-Ship"
|
||||
],
|
||||
"code": "2xKh-25ML, 10xS-13, 4xIgla",
|
||||
"name": "Gun / ASM / Rockets / Fox 2"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "Igla",
|
||||
"quantity": 4
|
||||
},
|
||||
{
|
||||
"name": "S-80FP",
|
||||
"quantity": 40
|
||||
},
|
||||
{
|
||||
"name": "Vikhr-M",
|
||||
"quantity": 12
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "12x9A4172, 40xS-8OFP, 4xIgla",
|
||||
"name": "Gun / ATGM / Rockets / Fox 2"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "Igla",
|
||||
"quantity": 4
|
||||
},
|
||||
{
|
||||
"name": "S-80FP",
|
||||
"quantity": 40
|
||||
},
|
||||
{
|
||||
"name": "Vikhr-M",
|
||||
"quantity": 12
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "12x9A4172, 40xS-8OFP, 4xIgla",
|
||||
"name": "Gun / ATGM"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "Igla",
|
||||
"quantity": 4
|
||||
},
|
||||
{
|
||||
"name": "FAB-500",
|
||||
"quantity": 2
|
||||
},
|
||||
{
|
||||
"name": "S-13",
|
||||
"quantity": 10
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"Strike"
|
||||
],
|
||||
"code": "10xS-13, 2xFAB-500, 4xIgla",
|
||||
"name": "Gun / Bombs / Rockets / Fox 2"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "ka-50.png"
|
||||
},
|
||||
"Mi-24P": {
|
||||
"name": "Mi-24P",
|
||||
"label": "Mi-24P Hind",
|
||||
"shortLabel": "Mi24",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "S-8KOM",
|
||||
"quantity": 40
|
||||
},
|
||||
{
|
||||
"name": "9M114 ATGM",
|
||||
"quantity": 8
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "2xB8V20 (S-8KOM)+8xATGM 9M114",
|
||||
"name": "Gun / ATGM / Rockets"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "S-24B",
|
||||
"quantity": 4
|
||||
},
|
||||
{
|
||||
"name": "9M114 ATGM",
|
||||
"quantity": 4
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"Strike"
|
||||
],
|
||||
"code": "4xS-24B+4xATGM 9M114",
|
||||
"name": "Gun / ATGM / Rockets"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "GUV-1 Grenade Launcher",
|
||||
"quantity": 4
|
||||
},
|
||||
{
|
||||
"name": "9M114 ATGM",
|
||||
"quantity": 4
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "4xGUV-1 AP30+4xATGM 9M114",
|
||||
"name": "Gun / ATGM / Grenade Launcher"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "mi-24.png"
|
||||
},
|
||||
"SA342L": {
|
||||
"name": "SA342L",
|
||||
"label": "SA342L Gazelle",
|
||||
"shortLabel": "342",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "20mm Cannon",
|
||||
"quantity": 1
|
||||
},
|
||||
{
|
||||
"name": "SNEB68",
|
||||
"quantity": 8
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"Recon"
|
||||
],
|
||||
"code": "M621, 8xSNEB68 EAP",
|
||||
"name": "Gun / ATGM / Rockets"
|
||||
}
|
||||
],
|
||||
"filename": "sa-342.png"
|
||||
},
|
||||
"SA342M": {
|
||||
"name": "SA342M",
|
||||
"label": "SA342M Gazelle",
|
||||
"shortLabel": "342",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "HOT3",
|
||||
"quantity": 4
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "HOT3x4",
|
||||
"name": "ATGM"
|
||||
}
|
||||
],
|
||||
"filename": "sa-342.png"
|
||||
},
|
||||
"SA342Mistral": {
|
||||
"name": "SA342Mistral",
|
||||
"label": "SA342Mistral Gazelle",
|
||||
"shortLabel": "342",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "Mistral",
|
||||
"quantity": 4
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAP"
|
||||
],
|
||||
"code": "Mistral x 4",
|
||||
"name": "Fox 2"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "sa-342.png"
|
||||
},
|
||||
"AH-1W": {
|
||||
"name": "AH-1W",
|
||||
"label": "AH-1W Cobra",
|
||||
"shortLabel": "AH1",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "BGM-71 TOW",
|
||||
"quantity": 8
|
||||
},
|
||||
{
|
||||
"name": "Hydra-70 WP",
|
||||
"quantity": 38
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "8xBGM-71, 38xHYDRA-70 WP",
|
||||
"name": "TOW / Hydra"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "Hydra-70",
|
||||
"quantity": 76
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "76xHYDRA-70",
|
||||
"name": "Hydra"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "ah-1.png"
|
||||
},
|
||||
"Mi-26": {
|
||||
"name": "Mi-26",
|
||||
"label": "Mi-26 Halo",
|
||||
"shortLabel": "M26",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
"Transport"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "mi-26.png"
|
||||
},
|
||||
"Mi-28N": {
|
||||
"name": "Mi-28N",
|
||||
"label": "Mi-28N Havoc",
|
||||
"shortLabel": "M28",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "9M114 Shturm",
|
||||
"quantity": 16
|
||||
},
|
||||
{
|
||||
"name": "S-8",
|
||||
"quantity": 40
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "16x9M114, 40xS-8",
|
||||
"name": "ATGM / S-8"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "mi-28.png"
|
||||
},
|
||||
"Mi-8MT": {
|
||||
"name": "Mi-8MT",
|
||||
"label": "Mi-8MT Hip",
|
||||
"shortLabel": "Mi8",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "UPK",
|
||||
"quantity": 2
|
||||
},
|
||||
{
|
||||
"name": "B8",
|
||||
"quantity": 2
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "2 x UPK +2 x B8",
|
||||
"name": "Rockets / Gunpods"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
"Transport"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "mi-8.png"
|
||||
},
|
||||
"SH-60B": {
|
||||
"name": "SH-60B",
|
||||
"label": "SH-60B Blackhawk",
|
||||
"shortLabel": "S60",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "AGM-119 ASM",
|
||||
"quantity": 1
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "AGM-119",
|
||||
"name": "ASM"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
"Transport"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "uh-60.png"
|
||||
},
|
||||
"UH-60A": {
|
||||
"name": "UH-60A",
|
||||
"label": "UH-60A Blackhawk",
|
||||
"shortLabel": "U60",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
"Transport"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "uh-60.png"
|
||||
},
|
||||
"UH-1H": {
|
||||
"name": "UH-1H",
|
||||
"label": "UH-1H Huey",
|
||||
"shortLabel": "UH1",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
{
|
||||
"name": "M134 Minigun",
|
||||
"quantity": 2
|
||||
},
|
||||
{
|
||||
"name": "XM-158",
|
||||
"quantity": 2
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"CAS"
|
||||
],
|
||||
"code": "M134 Minigun*2, XM158*2",
|
||||
"name": "Miniguns / XM158"
|
||||
},
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
|
||||
],
|
||||
"roles": [
|
||||
"Transport"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Empty Loadout"
|
||||
}
|
||||
],
|
||||
"filename": "uh-1.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export var helicopterDatabase = new HelicopterDatabase();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon } from 'leaflet';
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet';
|
||||
import { getMap, getUnitsManager } from '..';
|
||||
import { rad2deg } from '../other/utils';
|
||||
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit } from '../server/server';
|
||||
import { aircraftDatabase } from './aircraftdatabase';
|
||||
import { groundUnitsDatabase } from './groundunitsdatabase';
|
||||
import { field } from 'geomag'
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: 'images/marker-icon.png',
|
||||
@@ -39,11 +40,7 @@ export class Unit extends Marker {
|
||||
coalition: "",
|
||||
},
|
||||
formationData: {
|
||||
formation: "",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: 0,
|
||||
wingmenIDs: [],
|
||||
leaderID: 0
|
||||
},
|
||||
taskData: {
|
||||
currentState: "IDLE",
|
||||
@@ -53,14 +50,12 @@ export class Unit extends Marker {
|
||||
targetAltitude: 0,
|
||||
isTanker: false,
|
||||
isAWACS: false,
|
||||
TACANOn: false,
|
||||
TACANChannel: 0,
|
||||
TACANXY: "X",
|
||||
TACANCallsign: "",
|
||||
radioFrequency: 0,
|
||||
radioCallsign: 0,
|
||||
radioCallsignNumber: 0,
|
||||
radioAMFM: "AM"
|
||||
radioCallsignNumber: 0
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "",
|
||||
@@ -70,6 +65,7 @@ export class Unit extends Marker {
|
||||
|
||||
#selectable: boolean;
|
||||
#selected: boolean = false;
|
||||
#hovered: boolean = false;
|
||||
#hidden: boolean = false;
|
||||
|
||||
#preventClick: boolean = false;
|
||||
@@ -77,9 +73,9 @@ export class Unit extends Marker {
|
||||
#pathMarkers: Marker[] = [];
|
||||
#pathPolyline: Polyline;
|
||||
#targetsPolylines: Polyline[];
|
||||
#miniMapMarker: CircleMarker | null = null;
|
||||
|
||||
#timer: number = 0;
|
||||
#forceUpdate: boolean = false;
|
||||
|
||||
static getConstructor(type: string) {
|
||||
if (type === "GroundUnit") return GroundUnit;
|
||||
@@ -91,7 +87,7 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
constructor(ID: number, data: UpdateData) {
|
||||
super(new LatLng(0, 0), { riseOnHover: true });
|
||||
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
|
||||
|
||||
this.ID = ID;
|
||||
|
||||
@@ -100,6 +96,8 @@ export class Unit extends Marker {
|
||||
this.on('click', (e) => this.#onClick(e));
|
||||
this.on('dblclick', (e) => this.#onDoubleClick(e));
|
||||
this.on('contextmenu', (e) => this.#onContextMenu(e));
|
||||
this.on('mouseover', () => { this.#hovered = true;})
|
||||
this.on('mouseout', () => { this.#hovered = false;})
|
||||
|
||||
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
|
||||
this.#pathPolyline.addTo(getMap());
|
||||
@@ -107,11 +105,11 @@ export class Unit extends Marker {
|
||||
|
||||
/* Deselect units if they are hidden */
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300);
|
||||
window.setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300);
|
||||
});
|
||||
|
||||
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
|
||||
setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300);
|
||||
window.setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300);
|
||||
});
|
||||
|
||||
/* Set the unit data */
|
||||
@@ -121,10 +119,10 @@ export class Unit extends Marker {
|
||||
var icon = new DivIcon({
|
||||
html: this.getMarkerHTML(),
|
||||
className: 'leaflet-unit-marker',
|
||||
iconAnchor: [0, 0]
|
||||
iconAnchor: [25, 25],
|
||||
iconSize: [50, 50],
|
||||
});
|
||||
this.setIcon(icon);
|
||||
|
||||
}
|
||||
|
||||
getMarkerHTML() {
|
||||
@@ -141,14 +139,14 @@ export class Unit extends Marker {
|
||||
return "";
|
||||
}
|
||||
|
||||
/********************** Unit data *************************/
|
||||
|
||||
setData(data: UpdateData) {
|
||||
var updateMarker = false;
|
||||
|
||||
if ((data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude))
|
||||
|| (data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading)
|
||||
|| (data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive)
|
||||
|| this.#forceUpdate || !getMap().hasLayer(this))
|
||||
updateMarker = true;
|
||||
/* Check if data has changed comparing new values to old values */
|
||||
const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude));
|
||||
const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading);
|
||||
const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive);
|
||||
var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this))
|
||||
|
||||
if (data.baseData != undefined)
|
||||
{
|
||||
@@ -267,22 +265,7 @@ export class Unit extends Marker {
|
||||
return this.#selectable;
|
||||
}
|
||||
|
||||
addDestination(latlng: L.LatLng) {
|
||||
var path: any = {};
|
||||
if (this.getTaskData().activePath != undefined) {
|
||||
path = this.getTaskData().activePath;
|
||||
path[(Object.keys(path).length + 1).toString()] = latlng;
|
||||
}
|
||||
else {
|
||||
path = { "1": latlng };
|
||||
}
|
||||
addDestination(this.ID, path);
|
||||
}
|
||||
|
||||
clearDestinations() {
|
||||
this.getTaskData().activePath = undefined;
|
||||
}
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility()
|
||||
{
|
||||
this.setHidden( document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null ||
|
||||
@@ -313,94 +296,114 @@ export class Unit extends Marker {
|
||||
return getUnitsManager().getUnitByID(this.getFormationData().leaderID);
|
||||
}
|
||||
|
||||
getFormation() {
|
||||
return [<Unit>this].concat(this.getWingmen())
|
||||
/********************** Unit commands *************************/
|
||||
addDestination(latlng: L.LatLng) {
|
||||
if (!this.getMissionData().flags.Human) {
|
||||
var path: any = {};
|
||||
if (this.getTaskData().activePath != undefined) {
|
||||
path = this.getTaskData().activePath;
|
||||
path[(Object.keys(path).length + 1).toString()] = latlng;
|
||||
}
|
||||
else {
|
||||
path = { "1": latlng };
|
||||
}
|
||||
addDestination(this.ID, path);
|
||||
}
|
||||
}
|
||||
|
||||
getWingmen() {
|
||||
var wingmen: Unit[] = [];
|
||||
if (this.getFormationData().wingmenIDs != undefined) {
|
||||
for (let ID of this.getFormationData().wingmenIDs) {
|
||||
var unit = getUnitsManager().getUnitByID(ID)
|
||||
if (unit)
|
||||
wingmen.push(unit);
|
||||
}
|
||||
}
|
||||
return wingmen;
|
||||
clearDestinations() {
|
||||
if (!this.getMissionData().flags.Human)
|
||||
this.getTaskData().activePath = undefined;
|
||||
}
|
||||
|
||||
attackUnit(targetID: number) {
|
||||
if (this.ID != targetID) {
|
||||
attackUnit(this.ID, targetID);
|
||||
}
|
||||
else {
|
||||
// TODO: show a message
|
||||
}
|
||||
/* Units can't attack themselves */
|
||||
if (!this.getMissionData().flags.Human)
|
||||
if (this.ID != targetID)
|
||||
attackUnit(this.ID, targetID);
|
||||
}
|
||||
|
||||
followUnit(targetID: number, offset: {"x": number, "y": number, "z": number}) {
|
||||
if (this.ID != targetID) {
|
||||
followUnit(this.ID, targetID, offset);
|
||||
}
|
||||
else {
|
||||
// TODO: show a message
|
||||
}
|
||||
/* Units can't follow themselves */
|
||||
if (!this.getMissionData().flags.Human)
|
||||
if (this.ID != targetID)
|
||||
followUnit(this.ID, targetID, offset);
|
||||
}
|
||||
|
||||
landAt(latlng: LatLng) {
|
||||
landAt(this.ID, latlng);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
landAt(this.ID, latlng);
|
||||
}
|
||||
|
||||
changeSpeed(speedChange: string) {
|
||||
changeSpeed(this.ID, speedChange);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
changeSpeed(this.ID, speedChange);
|
||||
}
|
||||
|
||||
changeAltitude(altitudeChange: string) {
|
||||
changeAltitude(this.ID, altitudeChange);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
changeAltitude(this.ID, altitudeChange);
|
||||
}
|
||||
|
||||
setSpeed(speed: number) {
|
||||
setSpeed(this.ID, speed);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
setSpeed(this.ID, speed);
|
||||
}
|
||||
|
||||
setAltitude(altitude: number) {
|
||||
setAltitude(this.ID, altitude);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
setAltitude(this.ID, altitude);
|
||||
}
|
||||
|
||||
setROE(ROE: string) {
|
||||
setROE(this.ID, ROE);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
setROE(this.ID, ROE);
|
||||
}
|
||||
|
||||
setReactionToThreat(reactionToThreat: string) {
|
||||
setReactionToThreat(this.ID, reactionToThreat);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
setReactionToThreat(this.ID, reactionToThreat);
|
||||
}
|
||||
|
||||
setLeader(isLeader: boolean, wingmenIDs: number[] = []) {
|
||||
setLeader(this.ID, isLeader, wingmenIDs);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
setLeader(this.ID, isLeader, wingmenIDs);
|
||||
}
|
||||
|
||||
delete() {
|
||||
// TODO: add confirmation popup
|
||||
deleteUnit(this.ID);
|
||||
}
|
||||
|
||||
refuel() {
|
||||
refuel(this.ID);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
refuel(this.ID);
|
||||
}
|
||||
|
||||
setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACANChannel: number, TACANXY: string, TACANcallsign: string, radioFrequency: number, radioCallsign: number, radioCallsignNumber: number) {
|
||||
setAdvacedOptions(this.ID, isTanker, isAWACS, TACANChannel, TACANXY, TACANcallsign, radioFrequency, radioCallsign, radioCallsignNumber);
|
||||
if (!this.getMissionData().flags.Human)
|
||||
setAdvacedOptions(this.ID, isTanker, isAWACS, TACANChannel, TACANXY, TACANcallsign, radioFrequency, radioCallsign, radioCallsignNumber);
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
onAdd(map: Map): this {
|
||||
super.onAdd(map);
|
||||
getMap().removeTemporaryMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
|
||||
return this;
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onClick(e: any) {
|
||||
this.#timer = setTimeout(() => {
|
||||
if (!this.#preventClick) {
|
||||
if (getMap().getState() === 'IDLE' || getMap().getState() === 'MOVE_UNIT' || e.originalEvent.ctrlKey) {
|
||||
if (!e.originalEvent.ctrlKey) {
|
||||
getUnitsManager().deselectAllUnits();
|
||||
}
|
||||
this.setSelected(true);
|
||||
if (!this.#preventClick) {
|
||||
if (getMap().getState() === 'IDLE' || getMap().getState() === 'MOVE_UNIT' || e.originalEvent.ctrlKey) {
|
||||
if (!e.originalEvent.ctrlKey) {
|
||||
getUnitsManager().deselectAllUnits();
|
||||
}
|
||||
this.setSelected(!this.getSelected());
|
||||
}
|
||||
}
|
||||
|
||||
this.#timer = window.setTimeout(() => {
|
||||
this.#preventClick = false;
|
||||
}, 200);
|
||||
}
|
||||
@@ -415,12 +418,11 @@ export class Unit extends Marker {
|
||||
|
||||
options["Center"] = `<div id="center-map">Center map</div>`;
|
||||
|
||||
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().includes(this)))
|
||||
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this))))
|
||||
{
|
||||
options = {
|
||||
'Attack': `<div id="attack">Attack</div>`,
|
||||
'Follow': `<div id="follow">Follow</div>`,
|
||||
}
|
||||
options['Attack'] = `<div id="attack">Attack</div>`;
|
||||
if (getUnitsManager().getSelectedUnitsType() === "Aircraft")
|
||||
options['Follow'] = `<div id="follow">Follow</div>`;
|
||||
}
|
||||
else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0)
|
||||
{
|
||||
@@ -461,6 +463,7 @@ export class Unit extends Marker {
|
||||
'Line abreast (LH)': `<div id="line-abreast">Line abreast (left)</div>`,
|
||||
'Line abreast (RH)': `<div id="line-abreast">Line abreast (right)</div>`,
|
||||
'Front': `<div id="front">In front</div>`,
|
||||
'Diamond': `<div id="diamond">Diamond</div>`,
|
||||
'Custom': `<div id="custom">Custom</div>`
|
||||
}
|
||||
|
||||
@@ -481,83 +484,115 @@ export class Unit extends Marker {
|
||||
})
|
||||
}
|
||||
else {
|
||||
// X: front-rear, positive front
|
||||
// Y: top-bottom, positive top
|
||||
// Z: left-right, positive right
|
||||
|
||||
var offset = {"x": 0, "y": 0, "z": 0};
|
||||
if (action == "Trail")
|
||||
{
|
||||
offset.x = -50; offset.y = -30; offset.z = 0;
|
||||
}
|
||||
else if (action == "Echelon (LH)")
|
||||
{
|
||||
offset.x = -50; offset.y = -10; offset.z = -50;
|
||||
}
|
||||
else if (action == "Echelon (RH)")
|
||||
{
|
||||
offset.x = -50; offset.y = -10; offset.z = 50;
|
||||
}
|
||||
else if (action == "Line abreast (RH)")
|
||||
{
|
||||
offset.x = 0; offset.y = 0; offset.z = 50;
|
||||
}
|
||||
else if (action == "Line abreast (LH)")
|
||||
{
|
||||
offset.x = 0; offset.y = 0; offset.z = -50;
|
||||
}
|
||||
else if (action == "Front")
|
||||
{
|
||||
offset.x = 100; offset.y = 0; offset.z = 0;
|
||||
}
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, offset);
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action);
|
||||
}
|
||||
}
|
||||
|
||||
#updateMarker() {
|
||||
this.updateVisibility();
|
||||
|
||||
if (!this.getHidden()) {
|
||||
/* Draw the minimap marker */
|
||||
if (this.getBaseData().alive )
|
||||
{
|
||||
if (this.#miniMapMarker == null)
|
||||
{
|
||||
this.#miniMapMarker = new CircleMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), {radius: 0.5});
|
||||
if (this.getMissionData().coalition == "neutral")
|
||||
this.#miniMapMarker.setStyle({color: "#CFD9E8"});
|
||||
else if (this.getMissionData().coalition == "red")
|
||||
this.#miniMapMarker.setStyle({color: "#ff5858"});
|
||||
else
|
||||
this.#miniMapMarker.setStyle({color: "#247be2"});
|
||||
this.#miniMapMarker.addTo(getMap().getMiniMapLayerGroup());
|
||||
this.#miniMapMarker.bringToBack();
|
||||
}
|
||||
else {
|
||||
this.#miniMapMarker.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
|
||||
this.#miniMapMarker.bringToBack();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.#miniMapMarker != null && getMap().getMiniMapLayerGroup().hasLayer(this.#miniMapMarker)) {
|
||||
getMap().getMiniMapLayerGroup().removeLayer(this.#miniMapMarker);
|
||||
this.#miniMapMarker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw the marker */
|
||||
if (!this.getHidden()) {
|
||||
this.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
|
||||
|
||||
var element = this.getElement();
|
||||
if (element != null) {
|
||||
/* Draw the velocity vector */
|
||||
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`);
|
||||
element.querySelector(".unit")?.setAttribute("data-pilot", this.getMissionData().flags.human? "human": "ai");
|
||||
|
||||
/* Set fuel data */
|
||||
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getMissionData().fuel}%`);
|
||||
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getMissionData().fuel < 20);
|
||||
|
||||
/* Set dead/alive flag */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive);
|
||||
|
||||
element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase());
|
||||
/* Set current unit state */
|
||||
if (this.getMissionData().flags.Human) // Unit is human
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "human");
|
||||
else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus)
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
|
||||
else // Unit is under Olympus control
|
||||
element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase());
|
||||
|
||||
var unitHeadingDiv = element.querySelector(".unit-heading");
|
||||
if (unitHeadingDiv != null)
|
||||
unitHeadingDiv.innerHTML = String(Math.floor(rad2deg(this.getFlightData().heading)));
|
||||
|
||||
var unitAltitudeDiv = element.querySelector(".unit-altitude");
|
||||
if (unitAltitudeDiv != null) {
|
||||
unitAltitudeDiv.innerHTML = String(Math.floor(this.getFlightData().altitude / 0.3048 / 1000));
|
||||
|
||||
}
|
||||
/* Set altitude and speed */
|
||||
if (element.querySelector(".unit-altitude"))
|
||||
(<HTMLElement> element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 1000));
|
||||
if (element.querySelector(".unit-speed"))
|
||||
(<HTMLElement> element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384 ) );
|
||||
|
||||
/* Rotate elements according to heading */
|
||||
element.querySelectorAll( "[data-rotate-to-heading]" ).forEach( el => {
|
||||
const headingDeg = rad2deg( this.getFlightData().heading );
|
||||
let currentStyle = el.getAttribute( "style" ) || "";
|
||||
el.setAttribute( "style", currentStyle + `transform:rotate(${headingDeg}deg);` );
|
||||
});
|
||||
|
||||
var unitSpeedDiv = element.querySelector(".unit-speed");
|
||||
if (unitSpeedDiv != null)
|
||||
unitSpeedDiv.innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384 ) );
|
||||
/* Turn on ordnance indicators */
|
||||
|
||||
var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1");
|
||||
var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2");
|
||||
var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3");
|
||||
var hasOtherAmmo = element.querySelector(".unit")?.hasAttribute("data-has-other-ammo");
|
||||
|
||||
var newHasFox1 = false;
|
||||
var newHasFox2 = false;
|
||||
var newHasFox3 = false;
|
||||
var newHasOtherAmmo = false;
|
||||
Object.values(this.getMissionData().ammo).forEach((ammo: any) => {
|
||||
if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) {
|
||||
if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) {
|
||||
newHasFox1 = true;
|
||||
}
|
||||
else if (ammo.desc.guidance == 2) {
|
||||
newHasFox2 = true;
|
||||
}
|
||||
else if (ammo.desc.guidance == 3) {
|
||||
newHasFox3 = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
newHasOtherAmmo = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasFox1 != newHasFox1) element.querySelector(".unit")?.toggleAttribute("data-has-fox-1", newHasFox1);
|
||||
if (hasFox2 != newHasFox2) element.querySelector(".unit")?.toggleAttribute("data-has-fox-2", newHasFox2);
|
||||
if (hasFox3 != newHasFox3) element.querySelector(".unit")?.toggleAttribute("data-has-fox-3", newHasFox3);
|
||||
if (hasOtherAmmo != newHasOtherAmmo) element.querySelector(".unit")?.toggleAttribute("data-has-other-ammo", newHasOtherAmmo);
|
||||
}
|
||||
|
||||
/* Set vertical offset for altitude stacking */
|
||||
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y);
|
||||
this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#hovered || this.#selected? 5000: 0));
|
||||
}
|
||||
|
||||
this.#forceUpdate = false;
|
||||
}
|
||||
|
||||
#drawPath() {
|
||||
@@ -616,7 +651,7 @@ export class Unit extends Marker {
|
||||
color = "#00FF00";
|
||||
else
|
||||
color = "#FFFFFF";
|
||||
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 1, smoothFactor: 1 });
|
||||
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 });
|
||||
targetPolyline.addTo(getMap());
|
||||
this.#targetsPolylines.push(targetPolyline)
|
||||
}
|
||||
|
||||
@@ -20,25 +20,18 @@ export class UnitsManager {
|
||||
document.addEventListener('unitSelection', (e: CustomEvent) => this.#onUnitSelection(e.detail));
|
||||
document.addEventListener('unitDeselection', (e: CustomEvent) => this.#onUnitDeselection(e.detail));
|
||||
document.addEventListener('keydown', (event) => this.#onKeyDown(event));
|
||||
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete() )
|
||||
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete())
|
||||
}
|
||||
|
||||
getSelectableAircraft() {
|
||||
|
||||
const units = this.getUnits();
|
||||
|
||||
return Object.keys( units ).reduce( ( acc:{[key:number]: Unit}, unitId:any ) => {
|
||||
|
||||
const baseData = units[ unitId ].getBaseData();
|
||||
|
||||
if ( baseData.category === "Aircraft" && baseData.alive === true ) {
|
||||
acc[ unitId ] = units[ unitId ];
|
||||
return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => {
|
||||
const baseData = units[unitId].getBaseData();
|
||||
if (baseData.category === "Aircraft" && baseData.alive === true) {
|
||||
acc[unitId] = units[unitId];
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
||||
}, {});
|
||||
|
||||
}
|
||||
|
||||
getUnits() {
|
||||
@@ -59,58 +52,57 @@ export class UnitsManager {
|
||||
this.#units[ID] = new constructor(ID, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
removeUnit(ID: number) {
|
||||
|
||||
}
|
||||
|
||||
update(data: UnitsData) {
|
||||
Object.keys(data.units)
|
||||
.filter((ID: string) => !(ID in this.#units))
|
||||
.reduce((timeout: number, ID: string) => {
|
||||
setTimeout(() => {
|
||||
if (!(ID in this.#units))
|
||||
this.addUnit(parseInt(ID), data.units[ID]);
|
||||
this.#units[parseInt(ID)]?.setData(data.units[ID]);
|
||||
}, timeout);
|
||||
return timeout + 10;
|
||||
}, 10);
|
||||
|
||||
.filter((ID: string) => !(ID in this.#units))
|
||||
.reduce((timeout: number, ID: string) => {
|
||||
window.setTimeout(() => {
|
||||
if (!(ID in this.#units))
|
||||
this.addUnit(parseInt(ID), data.units[ID]);
|
||||
this.#units[parseInt(ID)]?.setData(data.units[ID]);
|
||||
}, timeout);
|
||||
return timeout + 10;
|
||||
}, 10);
|
||||
|
||||
Object.keys(data.units)
|
||||
.filter((ID: string) => ID in this.#units)
|
||||
.forEach((ID: string) => this.#units[parseInt(ID)]?.setData(data.units[ID]));
|
||||
.filter((ID: string) => ID in this.#units)
|
||||
.forEach((ID: string) => this.#units[parseInt(ID)]?.setData(data.units[ID]));
|
||||
}
|
||||
|
||||
selectUnit(ID: number, deselectAllUnits: boolean = true)
|
||||
{
|
||||
if (deselectAllUnits)
|
||||
this.getSelectedUnits().filter((unit: Unit) => unit.ID !== ID ).forEach((unit: Unit) => unit.setSelected(false));
|
||||
selectUnit(ID: number, deselectAllUnits: boolean = true) {
|
||||
if (deselectAllUnits)
|
||||
this.getSelectedUnits().filter((unit: Unit) => unit.ID !== ID).forEach((unit: Unit) => unit.setSelected(false));
|
||||
this.#units[ID]?.setSelected(true);
|
||||
}
|
||||
|
||||
selectFromBounds(bounds: LatLngBounds)
|
||||
{
|
||||
selectFromBounds(bounds: LatLngBounds) {
|
||||
this.deselectAllUnits();
|
||||
for (let ID in this.#units)
|
||||
{
|
||||
if (this.#units[ID].getHidden() == false)
|
||||
{
|
||||
for (let ID in this.#units) {
|
||||
if (this.#units[ID].getHidden() == false) {
|
||||
var latlng = new LatLng(this.#units[ID].getFlightData().latitude, this.#units[ID].getFlightData().longitude);
|
||||
if (bounds.contains(latlng))
|
||||
{
|
||||
if (bounds.contains(latlng)) {
|
||||
this.#units[ID].setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedUnits() {
|
||||
getSelectedUnits(options?: {excludeHumans?: boolean}) {
|
||||
var selectedUnits = [];
|
||||
for (let ID in this.#units) {
|
||||
if (this.#units[ID].getSelected()) {
|
||||
selectedUnits.push(this.#units[ID]);
|
||||
}
|
||||
}
|
||||
if (options) {
|
||||
if (options.excludeHumans)
|
||||
selectedUnits = selectedUnits.filter((unit: Unit) => {return !unit.getMissionData().flags.Human});
|
||||
}
|
||||
return selectedUnits;
|
||||
}
|
||||
|
||||
@@ -120,224 +112,225 @@ export class UnitsManager {
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedLeaders() {
|
||||
var leaders: Unit[] = [];
|
||||
for (let idx in this.getSelectedUnits())
|
||||
{
|
||||
var unit = this.getSelectedUnits()[idx];
|
||||
if (unit.getFormationData().isLeader)
|
||||
leaders.push(unit);
|
||||
else if (unit.getFormationData().isWingman)
|
||||
{
|
||||
var leader = unit.getLeader();
|
||||
if (leader && !leaders.includes(leader))
|
||||
leaders.push(leader);
|
||||
}
|
||||
}
|
||||
return leaders;
|
||||
}
|
||||
|
||||
getSelectedSingletons() {
|
||||
var singletons: Unit[] = [];
|
||||
for (let idx in this.getSelectedUnits())
|
||||
{
|
||||
var unit = this.getSelectedUnits()[idx];
|
||||
if (!unit.getFormationData().isLeader && !unit.getFormationData().isWingman)
|
||||
singletons.push(unit);
|
||||
}
|
||||
return singletons;
|
||||
}
|
||||
|
||||
getSelectedUnitsType () {
|
||||
getSelectedUnitsType() {
|
||||
if (this.getSelectedUnits().length == 0)
|
||||
return undefined;
|
||||
return this.getSelectedUnits().map((unit: Unit) => {
|
||||
return unit.constructor.name
|
||||
})?.reduce((a: any, b: any) => {
|
||||
return a == b? a: undefined
|
||||
return a == b ? a : undefined
|
||||
});
|
||||
};
|
||||
|
||||
getSelectedUnitsTargetSpeed () {
|
||||
getSelectedUnitsTargetSpeed() {
|
||||
if (this.getSelectedUnits().length == 0)
|
||||
return undefined;
|
||||
return this.getSelectedUnits().map((unit: Unit) => {
|
||||
return unit.getTaskData().targetSpeed
|
||||
})?.reduce((a: any, b: any) => {
|
||||
return a == b? a: undefined
|
||||
return a == b ? a : undefined
|
||||
});
|
||||
};
|
||||
|
||||
getSelectedUnitsTargetAltitude () {
|
||||
getSelectedUnitsTargetAltitude() {
|
||||
if (this.getSelectedUnits().length == 0)
|
||||
return undefined;
|
||||
return this.getSelectedUnits().map((unit: Unit) => {
|
||||
return unit.getTaskData().targetAltitude
|
||||
})?.reduce((a: any, b: any) => {
|
||||
return a == b? a: undefined
|
||||
return a == b ? a : undefined
|
||||
});
|
||||
};
|
||||
|
||||
getSelectedUnitsCoalition () {
|
||||
getSelectedUnitsCoalition() {
|
||||
if (this.getSelectedUnits().length == 0)
|
||||
return undefined;
|
||||
return this.getSelectedUnits().map((unit: Unit) => {
|
||||
return unit.getMissionData().coalition
|
||||
})?.reduce((a: any, b: any) => {
|
||||
return a == b? a: undefined
|
||||
return a == b ? a : undefined
|
||||
});
|
||||
};
|
||||
|
||||
/*********************** Actions on selected units ************************/
|
||||
selectedUnitsAddDestination(latlng: L.LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
var commandedUnit = selectedUnits[idx];
|
||||
commandedUnit.addDestination(latlng);
|
||||
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 (unit.getTaskData().currentState === "Follow") {
|
||||
const leader = this.getUnitByID(unit.getFormationData().leaderID)
|
||||
if (leader && leader.getSelected())
|
||||
leader.addDestination(latlng);
|
||||
else
|
||||
unit.addDestination(latlng);
|
||||
}
|
||||
else
|
||||
unit.addDestination(latlng);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, " new destination added");
|
||||
}
|
||||
|
||||
selectedUnitsClearDestinations() {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
var commandedUnit = selectedUnits[idx];
|
||||
commandedUnit.clearDestinations();
|
||||
const unit = selectedUnits[idx];
|
||||
if (unit.getTaskData().currentState === "Follow") {
|
||||
const leader = this.getUnitByID(unit.getFormationData().leaderID)
|
||||
if (leader && leader.getSelected())
|
||||
leader.clearDestinations();
|
||||
else
|
||||
unit.clearDestinations();
|
||||
}
|
||||
else
|
||||
unit.clearDestinations();
|
||||
}
|
||||
}
|
||||
|
||||
selectedUnitsLandAt(latlng: LatLng)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsLandAt(latlng: LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].landAt(latlng);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, " landing");
|
||||
}
|
||||
|
||||
selectedUnitsChangeSpeed(speedChange: string)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsChangeSpeed(speedChange: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].changeSpeed(speedChange);
|
||||
}
|
||||
}
|
||||
|
||||
selectedUnitsChangeAltitude(altitudeChange: string)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsChangeAltitude(altitudeChange: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].changeAltitude(altitudeChange);
|
||||
}
|
||||
}
|
||||
|
||||
selectedUnitsSetSpeed(speed: number)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsSetSpeed(speed: number) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setSpeed(speed);
|
||||
}
|
||||
|
||||
|
||||
this.#showActionMessage(selectedUnits, `setting speed to ${speed * 1.94384} kts`);
|
||||
}
|
||||
|
||||
selectedUnitsSetAltitude(altitude: number)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsSetAltitude(altitude: number) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setAltitude(altitude);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `setting altitude to ${altitude / 0.3048} ft`);
|
||||
}
|
||||
|
||||
selectedUnitsSetROE(ROE: string)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsSetROE(ROE: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setROE(ROE);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `ROE set to ${ROE}`);
|
||||
}
|
||||
|
||||
selectedUnitsSetReactionToThreat(reactionToThreat: string)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsSetReactionToThreat(reactionToThreat: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setReactionToThreat(reactionToThreat);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `reaction to threat set to ${reactionToThreat}`);
|
||||
}
|
||||
|
||||
selectedUnitsAttackUnit(ID: number) {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].attackUnit(ID);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
|
||||
}
|
||||
|
||||
selectedUnitsDelete()
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsDelete() {
|
||||
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].delete();
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `deleted`);
|
||||
}
|
||||
|
||||
selectedUnitsRefuel()
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnitsRefuel() {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].refuel();
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `sent to nearest tanker`);
|
||||
}
|
||||
|
||||
selectedUnitsFollowUnit(ID: number, offset: {"x": number, "y": number, "z": number}) {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
selectedUnitsFollowUnit(ID: number, offset?: { "x": number, "y": number, "z": number }, formation?: string) {
|
||||
if (offset == undefined) {
|
||||
/* Simple formations with fixed offsets */
|
||||
// X: front-rear, positive front
|
||||
// Y: top-bottom, positive top
|
||||
// Z: left-right, positive right
|
||||
offset = { "x": 0, "y": 0, "z": 0 };
|
||||
if (formation === "Trail") { offset.x = -50; offset.y = -30; offset.z = 0; }
|
||||
else if (formation === "Echelon (LH)") { offset.x = -50; offset.y = -10; offset.z = -50; }
|
||||
else if (formation === "Echelon (RH)") { offset.x = -50; offset.y = -10; offset.z = 50; }
|
||||
else if (formation === "Line abreast (RH)") { offset.x = 0; offset.y = 0; offset.z = 50; }
|
||||
else if (formation === "Line abreast (LH)") { offset.x = 0; offset.y = 0; offset.z = -50; }
|
||||
else if (formation === "Front") { offset.x = 100; offset.y = 0; offset.z = 0; }
|
||||
else offset = undefined;
|
||||
}
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var count = 1;
|
||||
var xr = 0; var yr = 1; var zr = -1;
|
||||
var layer = 1;
|
||||
for (let idx in selectedUnits) {
|
||||
var commandedUnit = selectedUnits[idx];
|
||||
commandedUnit.followUnit(ID, {"x": offset.x * count, "y": offset.y * count, "z": offset.z * count} );
|
||||
var unit = selectedUnits[idx];
|
||||
if (offset != undefined)
|
||||
/* Offset is set, apply it */
|
||||
unit.followUnit(ID, { "x": offset.x * count, "y": offset.y * count, "z": offset.z * count });
|
||||
else {
|
||||
/* More complex formations with variable offsets */
|
||||
if (formation === "Diamond") {
|
||||
var xl = xr * Math.cos(Math.PI / 4) - yr * Math.sin(Math.PI / 4);
|
||||
var yl = xr * Math.sin(Math.PI / 4) + yr * Math.cos(Math.PI / 4);
|
||||
unit.followUnit(ID, { "x": -yl * 50, "y": zr * 10, "z": xl * 50 });
|
||||
|
||||
if (yr == 0) { layer++; xr = 0; yr = layer; zr = -layer; }
|
||||
else {
|
||||
if (xr < layer) { xr++; zr--; }
|
||||
else { yr--; zr++; }
|
||||
}
|
||||
}
|
||||
}
|
||||
count++;
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
|
||||
}
|
||||
|
||||
copyUnits()
|
||||
{
|
||||
this.#copiedUnits = this.getSelectedUnits();
|
||||
/***********************************************/
|
||||
copyUnits() {
|
||||
this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
|
||||
this.#showActionMessage(this.#copiedUnits, `copied`);
|
||||
}
|
||||
|
||||
pasteUnits()
|
||||
{
|
||||
if (!this.#pasteDisabled)
|
||||
{
|
||||
for (let idx in this.#copiedUnits)
|
||||
{
|
||||
pasteUnits() {
|
||||
if (!this.#pasteDisabled) {
|
||||
for (let idx in this.#copiedUnits) {
|
||||
var unit = this.#copiedUnits[idx];
|
||||
getMap().addTemporaryMarker(getMap().getMouseCoordinates());
|
||||
cloneUnit(unit.ID, getMap().getMouseCoordinates());
|
||||
this.#showActionMessage(this.#copiedUnits, `pasted`);
|
||||
this.#showActionMessage(this.#copiedUnits, `pasted`);
|
||||
}
|
||||
this.#pasteDisabled = true;
|
||||
setTimeout(() => this.#pasteDisabled = false, 250);
|
||||
window.setTimeout(() => this.#pasteDisabled = false, 250);
|
||||
}
|
||||
}
|
||||
|
||||
#onKeyDown(event: KeyboardEvent)
|
||||
{
|
||||
if ( !keyEventWasInInput( event ) && event.key === "Delete")
|
||||
{
|
||||
/***********************************************/
|
||||
#onKeyDown(event: KeyboardEvent) {
|
||||
if (!keyEventWasInInput(event) && event.key === "Delete") {
|
||||
this.selectedUnitsDelete();
|
||||
}
|
||||
}
|
||||
@@ -346,10 +339,9 @@ export class UnitsManager {
|
||||
if (this.getSelectedUnits().length > 0) {
|
||||
getMap().setState(MOVE_UNIT);
|
||||
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
|
||||
if (!this.#selectionEventDisabled)
|
||||
{
|
||||
setTimeout(() => {
|
||||
document.dispatchEvent(new CustomEvent("unitsSelection", {detail: this.getSelectedUnits()}));
|
||||
if (!this.#selectionEventDisabled) {
|
||||
window.setTimeout(() => {
|
||||
document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() }));
|
||||
this.#selectionEventDisabled = false;
|
||||
}, 100);
|
||||
this.#selectionEventDisabled = true;
|
||||
@@ -366,14 +358,14 @@ export class UnitsManager {
|
||||
getMap().setState(IDLE);
|
||||
document.dispatchEvent(new CustomEvent("clearSelection"));
|
||||
}
|
||||
else
|
||||
document.dispatchEvent(new CustomEvent("unitsDeselection", {detail: this.getSelectedUnits()}));
|
||||
else
|
||||
document.dispatchEvent(new CustomEvent("unitsDeselection", { detail: this.getSelectedUnits() }));
|
||||
}
|
||||
|
||||
#showActionMessage(units: Unit[], message: string) {
|
||||
if (units.length == 1)
|
||||
getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`);
|
||||
else
|
||||
else if (units.length > 1)
|
||||
getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "es2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
@@ -33,14 +33,15 @@
|
||||
], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": [
|
||||
"leaflet",
|
||||
"geojson"
|
||||
"geojson",
|
||||
"node"
|
||||
], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="connection-status-panel" class="ol-panel">
|
||||
<div id="connection-status-panel" class="ol-panel" oncontextmenu="return false;">
|
||||
<dl class="ol-data-grid">
|
||||
<dt></dt>
|
||||
<dd></dd>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<div id="map-contextmenu">
|
||||
<div id="map-contextmenu" oncontextmenu="return false;">
|
||||
<div id="active-coalition-label" data-active-coalition="blue"></div>
|
||||
<div id="upper-bar" class="ol-panel">
|
||||
<label id="context-menu-switch" class="toggle" for="context-menu-toggle">
|
||||
<input class="toggle-input" name="" type="checkbox" id="context-menu-toggle">
|
||||
<div data-active-coalition="blue" class="toggle-fill"></div>
|
||||
</label>
|
||||
<button data-active-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="contextMenuShow"
|
||||
@@ -75,11 +74,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="unit-contextmenu" class="ol-panel">
|
||||
<div id="unit-contextmenu" class="ol-panel" oncontextmenu="return false;">
|
||||
<!-- Here the available unit options will be shown -->
|
||||
</div>
|
||||
|
||||
<div id="airbase-contextmenu" class="ol-panel">
|
||||
<div id="airbase-contextmenu" class="ol-panel" oncontextmenu="return false;">
|
||||
|
||||
<h3 id="airbase-name"></h3>
|
||||
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
<div id="splash-screen" class="ol-dialog" data-on-click="closeDialog">
|
||||
|
||||
<div id="splash-screen" class="ol-dialog" oncontextmenu="return false;">
|
||||
<div id="splash-content" class="ol-dialog-content">
|
||||
|
||||
<div id="app-summary">
|
||||
<h2>DCS Olympus</h2>
|
||||
<h4>Dynamic Unit Command</h4>
|
||||
<div class="app-version">Version <span class="app-version-number">v0.2.0</span></div>
|
||||
<div class="app-version">Version <span class="app-version-number">v0.2.1</span></div>
|
||||
</div>
|
||||
|
||||
<div id="authentication-form">
|
||||
<div><h5>Username</h5> <input type="text" id="username" name="username" required autocomplete="username" placeholder="Enter username..."></div>
|
||||
<div><h5>Password</h5> <input type="password" id="password" name="password" minlength="8" required autocomplete="current-password" placeholder="Enter password..."></div>
|
||||
<button id="connection-button" class="ol-button-apply" data-on-click="tryConnection">Connect</button>
|
||||
</div>
|
||||
|
||||
<h5 id="connection-status"><br></h5>
|
||||
|
||||
<div id="legal-stuff">
|
||||
<h4>Disclaimer</h4>
|
||||
<p>We ain't no friends with no Eagle Dynamics.</p>
|
||||
<h5>DISCLAIMER</h5>
|
||||
<p> DCS Olympus (the "MATERIAL" or "Software") is provided completely free to users subject to the terms of the <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0 Licence</a> except where such terms conflict with this disclaimer, in which case, the terms of this disclaimer shall prevail.
|
||||
The authors and/or copyright holders of the Software have not received any financial benefit in connection with the Software. In any event, the Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and non-infringement. In no event shall the authors and/or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. Any party making use of the Software in any manner agrees to be bound by the terms set out in this disclaimer. THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="advanced-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide">
|
||||
<div id="advanced-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">
|
||||
@@ -141,7 +146,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div id="custom-formation-dialog" class="ol-panel ol-dialog olympus-dialog-close hide">
|
||||
<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-header">
|
||||
|
||||
@@ -34,10 +34,13 @@
|
||||
<%- include('dialogs.ejs') %>
|
||||
<%- include('unitdatatable.ejs') %>
|
||||
<%- include('popups.ejs') %>
|
||||
|
||||
<div id="gray-out"></div>
|
||||
|
||||
<% /* %>
|
||||
<%- include('log.ejs') %>
|
||||
<% */ %>
|
||||
|
||||
<script src="javascripts/bundle.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<div id="mouse-info-panel" class="ol-panel">
|
||||
<div id="mouse-info-panel" class="ol-panel" oncontextmenu="return false;">
|
||||
|
||||
<div>
|
||||
<dl class="ol-data-grid">
|
||||
<dt id="ref-measure-position" data-tooltip="CTRL-click on the map to activate the measuring tool."></dt>
|
||||
<dd id="measure-position" class="br-info" data-bearing="---" data-distance="---" data-distance-units="nm"></dd>
|
||||
<dd id="measure-position" class="br-info" data-bearing="---" data-distance="---" data-distance-units="NM"></dd>
|
||||
<dt id="ref-unit-position" data-tooltip="Bearing/range from selected unit."></dt>
|
||||
<dd id="unit-position" class="br-info" data-bearing="---" data-distance="---" data-distance-units="nm"></dd>
|
||||
<dd id="unit-position" class="br-info" data-bearing="---" data-distance="---" data-distance-units="NM"></dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dl class="ol-data-grid">
|
||||
<dt id="ref-bullseye-2" data-tooltip="Bearing/range from this bullseye." data-label="BE" data-coalition="blue"></dt>
|
||||
<dd id="bullseye-2" class="br-info" data-bearing="---" data-distance="---" data-distance-units="nm"></dd>
|
||||
<dd id="bullseye-2" class="br-info" data-bearing="---" data-distance="---" data-distance-units="NM"></dd>
|
||||
<dt id="ref-bullseye-1" data-tooltip="Bearing/range from this bullseye." data-label="BE" data-coalition="red"></dt>
|
||||
<dd id="bullseye-1" class="br-info" data-bearing="---" data-distance="---" data-distance-units="nm"></dd>
|
||||
<dd id="bullseye-1" class="br-info" data-bearing="---" data-distance="---" data-distance-units="NM"></dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<nav id="primary-toolbar" class="ol-panel">
|
||||
<nav id="primary-toolbar" class="ol-panel" oncontextmenu="return false;">
|
||||
|
||||
<div id="app-icon" class="ol-select ol-select-image">
|
||||
<div class="ol-select-value icon">
|
||||
@@ -6,8 +6,8 @@
|
||||
</div>
|
||||
<div class="ol-select-options">
|
||||
<div id="olympus-toolbar-summary">
|
||||
<h3>Olympus</h3>
|
||||
<div class="accent-green app-version-number">v0.2.0</div>
|
||||
<h3>DCS Olympus</h3>
|
||||
<div class="accent-green app-version-number">version v0.2.1</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://www.discord.com" target="_blank">Discord</a>
|
||||
@@ -15,6 +15,9 @@
|
||||
<div>
|
||||
<a href="https://github.com/Pax1601/DCSOlympus" target="_blank">Github</a>
|
||||
</div>
|
||||
<div data-on-click="reloadPage">
|
||||
<a href="" target="_blank" data-on-click="reloadPage">Restart Olyumpus</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="info-popup" class="ol-panel ol-popup hide">
|
||||
<div id="info-popup" class="ol-panel ol-popup hide" oncontextmenu="return false;">
|
||||
<div>
|
||||
<!-- Here the content of the popup will be shown -->
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="unit-control-panel" class="ol-panel ol-panel-padding-lg">
|
||||
<div id="unit-control-panel" class="ol-panel ol-panel-padding-lg" oncontextmenu="return false;">
|
||||
|
||||
<h3>Selected Units</h3>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="slider-container flight-control-slider" id="airspeed-slider">
|
||||
<dl class="ol-data-grid">
|
||||
<dt>Speed</dt>
|
||||
<dd class="flight-control-value" id="value">451kts</dd>
|
||||
<dd class="flight-control-value" id="value"></dd>
|
||||
</dl>
|
||||
<input type="range" min="0" max="100" value="0" class="slider">
|
||||
</div>
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="slider-container flight-control-slider" id="altitude-slider">
|
||||
<dl class="ol-data-grid">
|
||||
<dt>Altitude</dt>
|
||||
<dd class="flight-control-value" id="value">21,594ft</dd>
|
||||
<dd class="flight-control-value" id="value"></dd>
|
||||
</dl>
|
||||
<input type="range" min="0" max="100" value="0" class="slider">
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="unit-data-table" class="ol-panel ol-dialog scrollable">
|
||||
<div id="unit-data-table" class="ol-panel ol-dialog scrollable" oncontextmenu="return false;">
|
||||
|
||||
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="unit-info-panel" class="ol-panel" >
|
||||
<div id="unit-info-panel" class="ol-panel" oncontextmenu="return false;">
|
||||
|
||||
<div class="ol-panel-board">
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#define nwjsFolder "C:\Users\dpass\Documents\nwjs\"
|
||||
#define version "v0.2.0-alpha"
|
||||
#define version "v0.2.1-alpha"
|
||||
|
||||
[Setup]
|
||||
AppName=DCS Olympus
|
||||
|
||||
@@ -2,5 +2,8 @@
|
||||
"server": {
|
||||
"address": "localhost",
|
||||
"port": 30000
|
||||
},
|
||||
"authentication": {
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
local version = "v0.2.0-alpha"
|
||||
local version = "v0.2.1-alpha"
|
||||
|
||||
local debug = false
|
||||
|
||||
@@ -48,9 +48,11 @@ end
|
||||
function Olympus.getCountryIDByCoalition(coalition)
|
||||
local countryID = 0
|
||||
if coalition == 'red' then
|
||||
countryID = country.id.RUSSIA
|
||||
countryID = country.id.CJTF_RED
|
||||
elseif coalition == 'blue' then
|
||||
countryID = country.id.CJTF_BLUE
|
||||
else
|
||||
countryID = country.id.USA
|
||||
countryID = country.id.UN_PEACEKEEPERS
|
||||
end
|
||||
return countryID
|
||||
end
|
||||
@@ -321,13 +323,13 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["action"] = "From Runway",
|
||||
["action"] = "From Parking Area Hot",
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] = {["tasks"] = {},},
|
||||
},
|
||||
["type"] = "TakeOff",
|
||||
["type"] = "TakeOffParkingHot",
|
||||
["ETA"] = 0,
|
||||
["ETA_locked"] = true,
|
||||
["x"] = spawnLocation.x,
|
||||
@@ -361,17 +363,20 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
|
||||
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.
|
||||
function Olympus.clone(ID, lat, lng)
|
||||
Olympus.debug("Olympus.clone " .. ID, 2)
|
||||
function Olympus.clone(ID, lat, lng, category)
|
||||
Olympus.debug("Olympus.clone " .. ID .. ", " .. category, 2)
|
||||
local unit = Olympus.getUnitByID(ID)
|
||||
if unit then
|
||||
local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition())
|
||||
|
||||
-- TODO: only works on Aircraft
|
||||
local spawnOptions = {
|
||||
payload = Olympus.payloadRegistry[unit:getName()]
|
||||
}
|
||||
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, spawnOptions)
|
||||
if category == "Aircraft" then
|
||||
local spawnOptions = {
|
||||
payload = Olympus.payloadRegistry[unit:getName()]
|
||||
}
|
||||
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, spawnOptions)
|
||||
elseif category == "GroundUnit" then
|
||||
Olympus.spawnGroundUnit(coalition, unit:getTypeName(), lat, lng)
|
||||
end
|
||||
end
|
||||
Olympus.debug("Olympus.clone completed successfully", 2)
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
local version = 'v0.2.0-alpha'
|
||||
local version = 'v0.2.1-alpha'
|
||||
|
||||
Olympus = {}
|
||||
Olympus.OlympusDLL = nil
|
||||
|
||||
25225
scripts/unitPayloads.lua
@@ -185,7 +185,7 @@
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>
|
||||
</PrecompiledHeaderFile>
|
||||
<AdditionalIncludeDirectories>include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>include;..\..\third-party\base64\include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
|
||||
@@ -23,7 +23,6 @@ protected:
|
||||
virtual void setState(int newState);
|
||||
bool isDestinationReached();
|
||||
bool setActiveDestination();
|
||||
void createHoldingPattern();
|
||||
bool updateActivePath(bool looping);
|
||||
void goToDestination(wstring enrouteTask = L"nil");
|
||||
};
|
||||
@@ -7,10 +7,6 @@ namespace CommandPriority {
|
||||
enum CommandPriorities { LOW, MEDIUM, HIGH };
|
||||
};
|
||||
|
||||
namespace CommandType {
|
||||
enum CommandTypes { NO_TYPE, MOVE, SMOKE, SPAWN_AIR, SPAWN_GROUND, CLONE, FOLLOW, RESET_TASK, SET_OPTION, SET_COMMAND, SET_TASK };
|
||||
};
|
||||
|
||||
namespace SetCommandType {
|
||||
enum SetCommandTypes {
|
||||
ROE = 0,
|
||||
@@ -61,29 +57,25 @@ class Command
|
||||
{
|
||||
public:
|
||||
int getPriority() { return priority; }
|
||||
int getType() { return type; }
|
||||
virtual wstring getString(lua_State* L) = 0;
|
||||
virtual int getLoad() = 0;
|
||||
|
||||
protected:
|
||||
int priority = CommandPriority::LOW;
|
||||
int type = CommandType::NO_TYPE;
|
||||
};
|
||||
|
||||
/* Simple low priority move command (from user click) */
|
||||
class Move : public Command
|
||||
{
|
||||
public:
|
||||
Move(int ID, Coords destination, double speed, double altitude, wstring unitCategory, wstring taskOptions):
|
||||
Move(int ID, Coords destination, double speed, double altitude, wstring taskOptions):
|
||||
ID(ID),
|
||||
destination(destination),
|
||||
speed(speed),
|
||||
altitude(altitude),
|
||||
unitCategory(unitCategory),
|
||||
taskOptions(taskOptions)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
type = CommandType::MOVE;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 5; }
|
||||
@@ -91,7 +83,6 @@ public:
|
||||
private:
|
||||
const int ID;
|
||||
const Coords destination;
|
||||
const wstring unitCategory;
|
||||
const double speed;
|
||||
const double altitude;
|
||||
const wstring taskOptions;
|
||||
@@ -106,7 +97,6 @@ public:
|
||||
location(location)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
type = CommandType::SMOKE;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 5; }
|
||||
@@ -126,7 +116,6 @@ public:
|
||||
location(location)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
type = CommandType::SPAWN_GROUND;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 100; }
|
||||
@@ -149,7 +138,6 @@ public:
|
||||
airbaseName(airbaseName)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
type = CommandType::SPAWN_AIR;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 100; }
|
||||
@@ -171,7 +159,6 @@ public:
|
||||
location(location)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
type = CommandType::CLONE;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 100; }
|
||||
@@ -189,7 +176,6 @@ public:
|
||||
ID(ID)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
type = CommandType::CLONE;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 20; }
|
||||
@@ -207,7 +193,6 @@ public:
|
||||
task(task)
|
||||
{
|
||||
priority = CommandPriority::MEDIUM;
|
||||
type = CommandType::FOLLOW;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
@@ -225,7 +210,6 @@ public:
|
||||
ID(ID)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
type = CommandType::RESET_TASK;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
@@ -243,7 +227,6 @@ public:
|
||||
command(command)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
type = CommandType::RESET_TASK;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
@@ -263,7 +246,6 @@ public:
|
||||
optionValue(optionValue)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
type = CommandType::RESET_TASK;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
|
||||
@@ -27,5 +27,7 @@ private:
|
||||
void task();
|
||||
|
||||
atomic<bool> runListener;
|
||||
|
||||
wstring password = L"";
|
||||
};
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ public:
|
||||
void updateExportData(json::value json);
|
||||
void updateMissionData(json::value json);
|
||||
json::value getData(long long time);
|
||||
virtual wstring getCategory() { return L"No category"; };
|
||||
|
||||
/********** Base data **********/
|
||||
void setAI(bool newAI) { AI = newAI; addMeasure(L"AI", json::value(newAI)); }
|
||||
@@ -79,17 +80,9 @@ public:
|
||||
json::value getFlags() { return flags; }
|
||||
|
||||
/********** Formation data **********/
|
||||
void setIsLeader(bool newIsLeader);
|
||||
void setIsWingman(bool newIsWingman);
|
||||
void setLeader(Unit* newLeader);
|
||||
void setWingmen(vector<Unit*> newWingmen);
|
||||
void setFormation(wstring newFormation) { formation = newFormation; addMeasure(L"formation", json::value(formation));}
|
||||
void setLeaderID(int newLeaderID) { leaderID = newLeaderID; addMeasure(L"leaderID", json::value(newLeaderID)); }
|
||||
void setFormationOffset(Offset formationOffset);
|
||||
bool getIsLeader() { return isLeader; }
|
||||
bool getIsWingman() { return isWingman; }
|
||||
Unit* getLeader() { return leader; }
|
||||
vector<Unit*> getWingmen() { return wingmen; }
|
||||
wstring getFormation() { return formation; }
|
||||
int getLeaderID() { return leaderID; }
|
||||
Offset getFormationoffset() { return formationOffset; }
|
||||
|
||||
/********** Task data **********/
|
||||
@@ -103,14 +96,13 @@ public:
|
||||
void pushActivePathBack(Coords newActivePathBack);
|
||||
void popActivePathFront();
|
||||
void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));}
|
||||
void setIsTanker(bool newIsTanker) { isTanker = newIsTanker; addMeasure(L"isTanker", json::value(newIsTanker));}
|
||||
void setIsAWACS(bool newIsAWACS) { isAWACS = newIsAWACS; addMeasure(L"isAWACS", json::value(newIsAWACS));}
|
||||
void setTACANOn(bool newTACANOn);
|
||||
void setIsTanker(bool newIsTanker);
|
||||
void setIsAWACS(bool newIsAWACS);
|
||||
void setTACANChannel(int newTACANChannel);
|
||||
void setTACANXY(wstring newTACANXY);
|
||||
void setTACANCallsign(wstring newTACANCallsign);
|
||||
void setTACAN();
|
||||
void setRadioOn(bool newRadioOn);
|
||||
void setEPLRS(bool state);
|
||||
void setRadioFrequency(int newRadioFrequency);
|
||||
void setRadioCallsign(int newRadioCallsign);
|
||||
void setRadioCallsignNumber(int newRadioCallsignNumber);
|
||||
@@ -123,11 +115,9 @@ public:
|
||||
int getTargetID() { return targetID; }
|
||||
bool getIsTanker() { return isTanker; }
|
||||
bool getIsAWACS() { return isAWACS; }
|
||||
bool getTACANOn() { return TACANOn; }
|
||||
int getTACANChannel() { return TACANChannel; }
|
||||
wstring getTACANXY() { return TACANXY; }
|
||||
wstring getTACANCallsign() { return TACANCallsign; }
|
||||
bool getRadioOn() { return radioOn; }
|
||||
int getRadioFrequency() { return radioFrequency; }
|
||||
int getRadioCallsign() { return radioCallsign; }
|
||||
int getRadioCallsignNumber() { return radioCallsignNumber; }
|
||||
@@ -177,11 +167,7 @@ protected:
|
||||
json::value flags = json::value::null();
|
||||
|
||||
/********** Formation data **********/
|
||||
bool isLeader = false;
|
||||
bool isWingman = false;
|
||||
wstring formation = L"";
|
||||
Unit *leader = nullptr;
|
||||
vector<Unit *> wingmen;
|
||||
int leaderID = NULL;
|
||||
Offset formationOffset = Offset(NULL);
|
||||
|
||||
/********** Task data **********/
|
||||
@@ -193,11 +179,9 @@ protected:
|
||||
int targetID = NULL;
|
||||
bool isTanker = false;
|
||||
bool isAWACS = false;
|
||||
bool TACANOn = false;
|
||||
int TACANChannel = 40;
|
||||
wstring TACANXY = L"X";
|
||||
wstring TACANCallsign = L"TKR";
|
||||
bool radioOn = false;
|
||||
int radioFrequency = 260000000; // MHz
|
||||
int radioCallsign = 1;
|
||||
int radioCallsignNumber = 1;
|
||||
@@ -213,9 +197,10 @@ protected:
|
||||
Coords oldPosition = Coords(0); // Used to approximate speed
|
||||
|
||||
/********** Functions **********/
|
||||
virtual wstring getCategory() { return L"No category"; };
|
||||
wstring getTargetName();
|
||||
wstring getLeaderName();
|
||||
bool isTargetAlive();
|
||||
bool isLeaderAlive();
|
||||
virtual void AIloop() = 0;
|
||||
void addMeasure(wstring key, json::value value);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ void AirUnit::setState(int newState)
|
||||
{
|
||||
if (state != newState)
|
||||
{
|
||||
/* Perform any action required when LEAVING a certain state */
|
||||
/************ Perform any action required when LEAVING a certain state ************/
|
||||
switch (state) {
|
||||
case State::IDLE: {
|
||||
break;
|
||||
@@ -35,6 +35,7 @@ void AirUnit::setState(int newState)
|
||||
break;
|
||||
}
|
||||
case State::FOLLOW: {
|
||||
setLeaderID(NULL);
|
||||
break;
|
||||
}
|
||||
case State::LAND: {
|
||||
@@ -47,7 +48,7 @@ void AirUnit::setState(int newState)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Perform any action required when ENTERING a certain state */
|
||||
/************ Perform any action required when ENTERING a certain state ************/
|
||||
switch (newState) {
|
||||
case State::IDLE: {
|
||||
clearActivePath();
|
||||
@@ -135,23 +136,6 @@ bool AirUnit::setActiveDestination()
|
||||
}
|
||||
}
|
||||
|
||||
void AirUnit::createHoldingPattern()
|
||||
{
|
||||
/* Air units must ALWAYS have a destination or they will RTB and become uncontrollable */
|
||||
clearActivePath();
|
||||
Coords point1;
|
||||
Coords point2;
|
||||
Coords point3;
|
||||
Geodesic::WGS84().Direct(latitude, longitude, 45, 10000, point1.lat, point1.lng);
|
||||
Geodesic::WGS84().Direct(point1.lat, point1.lng, 135, 10000, point2.lat, point2.lng);
|
||||
Geodesic::WGS84().Direct(point2.lat, point2.lng, 225, 10000, point3.lat, point3.lng);
|
||||
pushActivePathBack(point1);
|
||||
pushActivePathBack(point2);
|
||||
pushActivePathBack(point3);
|
||||
pushActivePathBack(Coords(latitude, longitude));
|
||||
log(unitName + L" holding pattern created");
|
||||
}
|
||||
|
||||
bool AirUnit::updateActivePath(bool looping)
|
||||
{
|
||||
if (activePath.size() > 0)
|
||||
@@ -172,7 +156,7 @@ void AirUnit::goToDestination(wstring enrouteTask)
|
||||
{
|
||||
if (activeDestination != NULL)
|
||||
{
|
||||
Command* command = dynamic_cast<Command*>(new Move(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), getCategory(), enrouteTask));
|
||||
Command* command = dynamic_cast<Command*>(new Move(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), enrouteTask));
|
||||
scheduler->appendCommand(command);
|
||||
hasTask = true;
|
||||
}
|
||||
@@ -282,22 +266,22 @@ void AirUnit::AIloop()
|
||||
clearActivePath();
|
||||
activeDestination = Coords(NULL);
|
||||
|
||||
/* If the target is not alive (either not set or was destroyed) go back to IDLE */
|
||||
if (!isTargetAlive()) {
|
||||
/* If the leader is not alive (either not set or was destroyed) go back to IDLE */
|
||||
if (!isLeaderAlive()) {
|
||||
setState(State::IDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
currentTask = L"Following " + getTargetName();
|
||||
|
||||
Unit* target = unitsManager->getUnit(targetID);
|
||||
Unit* leader = unitsManager->getUnit(leaderID);
|
||||
if (!hasTask) {
|
||||
if (target != nullptr && target->getAlive() && formationOffset != NULL)
|
||||
if (leader != nullptr && leader->getAlive() && formationOffset != NULL)
|
||||
{
|
||||
std::wostringstream taskSS;
|
||||
taskSS << "{"
|
||||
<< "id = 'FollowUnit'" << ", "
|
||||
<< "leaderID = " << target->getID() << ","
|
||||
<< "leaderID = " << leader->getID() << ","
|
||||
<< "offset = {"
|
||||
<< "x = " << formationOffset.x << ","
|
||||
<< "y = " << formationOffset.y << ","
|
||||
@@ -332,5 +316,6 @@ void AirUnit::AIloop()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
addMeasure(L"currentTask", json::value(currentTask));
|
||||
}
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
#include "commands.h"
|
||||
#include "logger.h"
|
||||
#include "dcstools.h"
|
||||
#include "unit.h"
|
||||
#include "unitsmanager.h"
|
||||
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
/* Move command */
|
||||
wstring Move::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.move, "
|
||||
<< ID << ", "
|
||||
<< destination.lat << ", "
|
||||
<< destination.lng << ", "
|
||||
<< altitude << ", "
|
||||
<< speed << ", "
|
||||
<< "\"" << unitCategory << "\"" << ", "
|
||||
<< taskOptions;
|
||||
return commandSS.str();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.move, "
|
||||
<< ID << ", "
|
||||
<< destination.lat << ", "
|
||||
<< destination.lng << ", "
|
||||
<< altitude << ", "
|
||||
<< speed << ", "
|
||||
<< "\"" << unit->getCategory() << "\"" << ", "
|
||||
<< taskOptions;
|
||||
return commandSS.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
}
|
||||
|
||||
/* Smoke command */
|
||||
@@ -67,13 +79,22 @@ wstring SpawnAircraft::getString(lua_State* L)
|
||||
/* Clone unit command */
|
||||
wstring Clone::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.clone, "
|
||||
<< ID << ", "
|
||||
<< location.lat << ", "
|
||||
<< location.lng;
|
||||
return commandSS.str();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.clone, "
|
||||
<< ID << ", "
|
||||
<< location.lat << ", "
|
||||
<< location.lng << ", "
|
||||
<< "\"" << unit->getCategory() << "\"";
|
||||
return commandSS.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete unit command */
|
||||
|
||||
@@ -29,7 +29,7 @@ void GroundUnit::AIloop()
|
||||
if (activeDestination != activePath.front())
|
||||
{
|
||||
activeDestination = activePath.front();
|
||||
Command* command = dynamic_cast<Command*>(new Move(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), getCategory(), L"nil"));
|
||||
Command* command = dynamic_cast<Command*>(new Move(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), L"nil"));
|
||||
scheduler->appendCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,30 +145,30 @@ void Scheduler::handleRequest(wstring key, json::value value)
|
||||
else if (key.compare(L"followUnit") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
int targetID = value[L"targetID"].as_integer();
|
||||
int leaderID = value[L"targetID"].as_integer();
|
||||
int offsetX = value[L"offsetX"].as_integer();
|
||||
int offsetY = value[L"offsetY"].as_integer();
|
||||
int offsetZ = value[L"offsetZ"].as_integer();
|
||||
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
Unit* target = unitsManager->getUnit(targetID);
|
||||
Unit* leader = unitsManager->getUnit(leaderID);
|
||||
|
||||
wstring unitName;
|
||||
wstring targetName;
|
||||
wstring leaderName;
|
||||
|
||||
if (unit != nullptr)
|
||||
unitName = unit->getUnitName();
|
||||
else
|
||||
return;
|
||||
|
||||
if (target != nullptr)
|
||||
targetName = target->getUnitName();
|
||||
if (leader != nullptr)
|
||||
leaderName = leader->getUnitName();
|
||||
else
|
||||
return;
|
||||
|
||||
log(L"Unit " + unitName + L" following unit " + targetName);
|
||||
log(L"Unit " + unitName + L" following unit " + leaderName);
|
||||
unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ));
|
||||
unit->setTargetID(targetID);
|
||||
unit->setLeaderID(leaderID);
|
||||
unit->setState(State::FOLLOW);
|
||||
}
|
||||
else if (key.compare(L"changeSpeed") == 0)
|
||||
@@ -208,40 +208,6 @@ void Scheduler::handleRequest(wstring key, json::value value)
|
||||
command = dynamic_cast<Command*>(new Clone(ID, loc));
|
||||
log(L"Cloning unit " + to_wstring(ID));
|
||||
}
|
||||
else if (key.compare(L"setLeader") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
bool isLeader = value[L"isLeader"].as_bool();
|
||||
if (isLeader)
|
||||
{
|
||||
json::value wingmenIDs = value[L"wingmenIDs"];
|
||||
vector<Unit*> wingmen;
|
||||
if (unit != nullptr)
|
||||
{
|
||||
for (auto itr = wingmenIDs.as_array().begin(); itr != wingmenIDs.as_array().end(); itr++)
|
||||
{
|
||||
Unit* wingman = unitsManager->getUnit(itr->as_integer());
|
||||
if (wingman != nullptr)
|
||||
wingmen.push_back(wingman);
|
||||
}
|
||||
unit->setFormation(L"Line abreast");
|
||||
unit->setIsLeader(true);
|
||||
unit->setWingmen(wingmen);
|
||||
log(L"Setting " + unit->getName() + L" as formation leader");
|
||||
}
|
||||
}
|
||||
else {
|
||||
unit->setIsLeader(false);
|
||||
}
|
||||
}
|
||||
else if (key.compare(L"setFormation") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
wstring formation = value[L"formation"].as_string();
|
||||
unit->setFormation(formation);
|
||||
}
|
||||
else if (key.compare(L"setROE") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
@@ -285,13 +251,11 @@ void Scheduler::handleRequest(wstring key, json::value value)
|
||||
unit->setIsTanker(value[L"isTanker"].as_bool());
|
||||
unit->setIsAWACS(value[L"isAWACS"].as_bool());
|
||||
|
||||
unit->setTACANOn(true); // TODO Remove
|
||||
unit->setTACANChannel(value[L"TACANChannel"].as_number().to_int32());
|
||||
unit->setTACANXY(value[L"TACANXY"].as_string());
|
||||
unit->setTACANCallsign(value[L"TACANCallsign"].as_string());
|
||||
unit->setTACAN();
|
||||
|
||||
unit->setRadioOn(true); // TODO Remove
|
||||
unit->setRadioFrequency(value[L"radioFrequency"].as_number().to_int32());
|
||||
unit->setRadioCallsign(value[L"radioCallsign"].as_number().to_int32());
|
||||
unit->setRadioCallsignNumber(value[L"radioCallsignNumber"].as_number().to_int32());
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include "luatools.h"
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include "base64.hpp"
|
||||
|
||||
#include <chrono>
|
||||
using namespace std::chrono;
|
||||
using namespace base64;
|
||||
|
||||
extern UnitsManager* unitsManager;
|
||||
extern Scheduler* scheduler;
|
||||
@@ -54,10 +56,10 @@ void Server::stop(lua_State* L)
|
||||
void Server::handle_options(http_request request)
|
||||
{
|
||||
http_response response(status_codes::OK);
|
||||
response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS"));
|
||||
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
|
||||
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
|
||||
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
|
||||
|
||||
request.reply(response);
|
||||
}
|
||||
@@ -68,87 +70,102 @@ void Server::handle_get(http_request request)
|
||||
lock_guard<mutex> guard(mutexLock);
|
||||
|
||||
http_response response(status_codes::OK);
|
||||
response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
|
||||
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
|
||||
string authorization = to_base64("admin:" + to_string(password));
|
||||
if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization)))
|
||||
{
|
||||
std::exception_ptr eptr;
|
||||
try {
|
||||
auto answer = json::value::object();
|
||||
auto path = uri::split_path(uri::decode(request.relative_uri().path()));
|
||||
|
||||
std::exception_ptr eptr;
|
||||
try {
|
||||
auto answer = json::value::object();
|
||||
auto path = uri::split_path(uri::decode(request.relative_uri().path()));
|
||||
|
||||
if (path.size() > 0)
|
||||
{
|
||||
if (path[0] == UNITS_URI)
|
||||
if (path.size() > 0)
|
||||
{
|
||||
map<utility::string_t, utility::string_t> query = request.relative_uri().split_query(request.relative_uri().query());
|
||||
long long time = 0;
|
||||
if (query.find(L"time") != query.end())
|
||||
if (path[0] == UNITS_URI)
|
||||
{
|
||||
try {
|
||||
time = stoll((*(query.find(L"time"))).second);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
time = 0;
|
||||
map<utility::string_t, utility::string_t> query = request.relative_uri().split_query(request.relative_uri().query());
|
||||
long long time = 0;
|
||||
if (query.find(L"time") != query.end())
|
||||
{
|
||||
try {
|
||||
time = stoll((*(query.find(L"time"))).second);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
time = 0;
|
||||
}
|
||||
}
|
||||
unitsManager->getData(answer, time);
|
||||
}
|
||||
unitsManager->getData(answer, time);
|
||||
}
|
||||
else if (path[0] == LOGS_URI)
|
||||
{
|
||||
auto logs = json::value::object();
|
||||
getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries
|
||||
answer[L"logs"] = logs;
|
||||
}
|
||||
else if (path[0] == AIRBASES_URI)
|
||||
answer[L"airbases"] = airbases;
|
||||
else if (path[0] == BULLSEYE_URI)
|
||||
answer[L"bullseyes"] = bullseyes;
|
||||
else if (path[0] == MISSION_URI)
|
||||
answer[L"mission"] = mission;
|
||||
else if (path[0] == LOGS_URI)
|
||||
{
|
||||
auto logs = json::value::object();
|
||||
getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries
|
||||
answer[L"logs"] = logs;
|
||||
}
|
||||
else if (path[0] == AIRBASES_URI)
|
||||
answer[L"airbases"] = airbases;
|
||||
else if (path[0] == BULLSEYE_URI)
|
||||
answer[L"bullseyes"] = bullseyes;
|
||||
else if (path[0] == MISSION_URI)
|
||||
answer[L"mission"] = mission;
|
||||
|
||||
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
||||
answer[L"time"] = json::value::string(to_wstring(ms.count()));
|
||||
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
|
||||
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
||||
answer[L"time"] = json::value::string(to_wstring(ms.count()));
|
||||
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
|
||||
}
|
||||
|
||||
response.set_body(answer);
|
||||
}
|
||||
catch (...) {
|
||||
eptr = std::current_exception(); // capture
|
||||
}
|
||||
handle_eptr(eptr);
|
||||
}
|
||||
else {
|
||||
response = status_codes::Unauthorized;
|
||||
}
|
||||
|
||||
response.set_body(answer);
|
||||
}
|
||||
catch (...) {
|
||||
eptr = std::current_exception(); // capture
|
||||
}
|
||||
handle_eptr(eptr);
|
||||
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
|
||||
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
|
||||
|
||||
request.reply(response);
|
||||
}
|
||||
|
||||
void Server::handle_request(http_request request, function<void(json::value const&, json::value&)> action)
|
||||
{
|
||||
auto answer = json::value::object();
|
||||
request.extract_json().then([&answer, &action](pplx::task<json::value> task)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto const& jvalue = task.get();
|
||||
|
||||
if (!jvalue.is_null())
|
||||
{
|
||||
action(jvalue, answer);
|
||||
}
|
||||
}
|
||||
catch (http_exception const& e)
|
||||
{
|
||||
log(e.what());
|
||||
}
|
||||
}).wait();
|
||||
|
||||
http_response response(status_codes::OK);
|
||||
response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS"));
|
||||
string authorization = to_base64("admin:" + to_string(password));
|
||||
if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization)))
|
||||
{
|
||||
auto answer = json::value::object();
|
||||
request.extract_json().then([&answer, &action](pplx::task<json::value> task)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto const& jvalue = task.get();
|
||||
|
||||
if (!jvalue.is_null())
|
||||
{
|
||||
action(jvalue, answer);
|
||||
}
|
||||
}
|
||||
catch (http_exception const& e)
|
||||
{
|
||||
log(e.what());
|
||||
}
|
||||
}).wait();
|
||||
response.set_body(answer);
|
||||
}
|
||||
else {
|
||||
response = status_codes::Unauthorized;
|
||||
}
|
||||
|
||||
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
|
||||
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
|
||||
response.set_body(answer);
|
||||
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
|
||||
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
|
||||
|
||||
request.reply(response);
|
||||
}
|
||||
|
||||
@@ -197,9 +214,15 @@ void Server::task()
|
||||
log(L"Starting server on " + address);
|
||||
}
|
||||
else
|
||||
{
|
||||
log(L"Error reading configuration file. Starting server on " + address);
|
||||
|
||||
if (config.is_object() && config.has_object_field(L"authentication") &&
|
||||
config[L"authentication"].has_string_field(L"password"))
|
||||
{
|
||||
password = config[L"authentication"][L"password"].as_string();
|
||||
}
|
||||
else
|
||||
log(L"Error reading configuration file. No password set.");
|
||||
free(buf);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "commands.h"
|
||||
#include "scheduler.h"
|
||||
#include "defines.h"
|
||||
#include "unitsManager.h"
|
||||
#include "unitsmanager.h"
|
||||
|
||||
#include <chrono>
|
||||
using namespace std::chrono;
|
||||
@@ -20,6 +20,17 @@ Unit::Unit(json::value json, int ID) :
|
||||
{
|
||||
log("Creating unit with ID: " + to_string(ID));
|
||||
addMeasure(L"currentState", json::value(L"Idle"));
|
||||
|
||||
addMeasure(L"TACANChannel", json::value(TACANChannel));
|
||||
addMeasure(L"TACANXY", json::value(TACANXY));
|
||||
addMeasure(L"TACANCallsign", json::value(TACANCallsign));
|
||||
|
||||
addMeasure(L"radioFrequency", json::value(radioFrequency));
|
||||
addMeasure(L"radioCallsign", json::value(radioCallsign));
|
||||
addMeasure(L"radioCallsignNumber", json::value(radioCallsignNumber));
|
||||
|
||||
addMeasure(L"ROE", json::value(L"Designated"));
|
||||
addMeasure(L"reactionToThreat", json::value(L"Evade"));
|
||||
}
|
||||
|
||||
Unit::~Unit()
|
||||
@@ -108,45 +119,59 @@ json::value Unit::getData(long long time)
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"baseData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"baseData"].size() == 0)
|
||||
json.erase(L"baseData");
|
||||
|
||||
/********** Flight data **********/
|
||||
json[L"flightData"] = json::value::object();
|
||||
for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading"})
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"flightData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (alive) {
|
||||
/********** Flight data **********/
|
||||
json[L"flightData"] = json::value::object();
|
||||
for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"flightData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"flightData"].size() == 0)
|
||||
json.erase(L"flightData");
|
||||
|
||||
/********** Mission data **********/
|
||||
json[L"missionData"] = json::value::object();
|
||||
for (auto key : { L"fuel", L"ammo", L"targets", L"hasTask", L"coalition", L"flags"})
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"missionData"][key] = measures[key]->getValue();
|
||||
}
|
||||
/********** Mission data **********/
|
||||
json[L"missionData"] = json::value::object();
|
||||
for (auto key : { L"fuel", L"ammo", L"targets", L"hasTask", L"coalition", L"flags" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"missionData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"missionData"].size() == 0)
|
||||
json.erase(L"missionData");
|
||||
|
||||
/********** Formation data **********/
|
||||
json[L"formationData"] = json::value::object();
|
||||
for (auto key : { L"isLeader", L"isWingman", L"formation", L"wingmenIDs", L"leaderID" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"missionData"][key] = measures[key]->getValue();
|
||||
}
|
||||
/********** Formation data **********/
|
||||
json[L"formationData"] = json::value::object();
|
||||
for (auto key : { L"leaderID" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"formationData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"formationData"].size() == 0)
|
||||
json.erase(L"formationData");
|
||||
|
||||
/********** Task data **********/
|
||||
json[L"taskData"] = json::value::object();
|
||||
for (auto key : { L"currentState", L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath", L"isTanker", L"isAWACS", L"TACANChannel", L"TACANXY", L"TACANCallsign", L"radioFrequency", L"radioCallsign", L"radioCallsignNumber"})
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"taskData"][key] = measures[key]->getValue();
|
||||
}
|
||||
/********** Task data **********/
|
||||
json[L"taskData"] = json::value::object();
|
||||
for (auto key : { L"currentState", L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath", L"isTanker", L"isAWACS", L"TACANChannel", L"TACANXY", L"TACANCallsign", L"radioFrequency", L"radioCallsign", L"radioCallsignNumber" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"taskData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"taskData"].size() == 0)
|
||||
json.erase(L"taskData");
|
||||
|
||||
/********** Options data **********/
|
||||
json[L"optionsData"] = json::value::object();
|
||||
for (auto key : { L"ROE", L"reactionToThreat" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"optionsData"][key] = measures[key]->getValue();
|
||||
/********** Options data **********/
|
||||
json[L"optionsData"] = json::value::object();
|
||||
for (auto key : { L"ROE", L"reactionToThreat" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"optionsData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"optionsData"].size() == 0)
|
||||
json.erase(L"optionsData");
|
||||
}
|
||||
|
||||
return json;
|
||||
@@ -154,12 +179,9 @@ json::value Unit::getData(long long time)
|
||||
|
||||
void Unit::setActivePath(list<Coords> newPath)
|
||||
{
|
||||
if (state != State::WINGMAN && state != State::FOLLOW)
|
||||
{
|
||||
activePath = newPath;
|
||||
resetActiveDestination();
|
||||
}
|
||||
|
||||
activePath = newPath;
|
||||
resetActiveDestination();
|
||||
|
||||
auto path = json::value::object();
|
||||
if (activePath.size() > 0) {
|
||||
int count = 1;
|
||||
@@ -223,22 +245,6 @@ int Unit::getCoalitionID()
|
||||
return 2;
|
||||
}
|
||||
|
||||
void Unit::setLeader(Unit* newLeader)
|
||||
{
|
||||
leader = newLeader;
|
||||
if (leader != nullptr)
|
||||
addMeasure(L"leaderID", json::value(leader->getID()));
|
||||
}
|
||||
|
||||
void Unit::setWingmen(vector<Unit*> newWingmen) {
|
||||
wingmen = newWingmen;
|
||||
auto wingmenIDs = json::value::object();
|
||||
int i = 0;
|
||||
for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++)
|
||||
wingmenIDs[i++] = (*itr)->getID();
|
||||
addMeasure(L"wingmen", wingmenIDs);
|
||||
}
|
||||
|
||||
wstring Unit::getTargetName()
|
||||
{
|
||||
if (isTargetAlive())
|
||||
@@ -262,6 +268,29 @@ bool Unit::isTargetAlive()
|
||||
return false;
|
||||
}
|
||||
|
||||
wstring Unit::getLeaderName()
|
||||
{
|
||||
if (isLeaderAlive())
|
||||
{
|
||||
Unit* leader = unitsManager->getUnit(leaderID);
|
||||
if (leader != nullptr)
|
||||
return leader->getUnitName();
|
||||
}
|
||||
return L"";
|
||||
}
|
||||
|
||||
bool Unit::isLeaderAlive()
|
||||
{
|
||||
if (leaderID == NULL)
|
||||
return false;
|
||||
|
||||
Unit* leader = unitsManager->getUnit(leaderID);
|
||||
if (leader != nullptr)
|
||||
return leader->alive;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void Unit::resetActiveDestination()
|
||||
{
|
||||
activeDestination = Coords(NULL);
|
||||
@@ -273,30 +302,6 @@ void Unit::resetTask()
|
||||
scheduler->appendCommand(command);
|
||||
}
|
||||
|
||||
void Unit::setIsLeader(bool newIsLeader) {
|
||||
isLeader = newIsLeader;
|
||||
if (!isLeader) {
|
||||
for (auto wingman : wingmen)
|
||||
{
|
||||
wingman->setFormation(L"");
|
||||
wingman->setIsWingman(false);
|
||||
wingman->setLeader(nullptr);
|
||||
}
|
||||
}
|
||||
addMeasure(L"isLeader", json::value(newIsLeader));
|
||||
}
|
||||
|
||||
void Unit::setIsWingman(bool newIsWingman)
|
||||
{
|
||||
isWingman = newIsWingman;
|
||||
if (isWingman)
|
||||
setState(State::WINGMAN);
|
||||
else
|
||||
setState(State::IDLE);
|
||||
|
||||
addMeasure(L"isWingman", json::value(isWingman));
|
||||
}
|
||||
|
||||
void Unit::setFormationOffset(Offset newFormationOffset)
|
||||
{
|
||||
formationOffset = newFormationOffset;
|
||||
@@ -349,9 +354,17 @@ void Unit::landAt(Coords loc) {
|
||||
setState(State::LAND);
|
||||
}
|
||||
|
||||
void Unit::setTACANOn(bool newTACANOn) {
|
||||
TACANOn = newTACANOn;
|
||||
addMeasure(L"TACANOn", json::value(newTACANOn));
|
||||
void Unit::setIsTanker(bool newIsTanker) {
|
||||
isTanker = newIsTanker;
|
||||
resetTask();
|
||||
addMeasure(L"isTanker", json::value(newIsTanker));
|
||||
}
|
||||
|
||||
void Unit::setIsAWACS(bool newIsAWACS) {
|
||||
isAWACS = newIsAWACS;
|
||||
resetTask();
|
||||
addMeasure(L"isAWACS", json::value(newIsAWACS));
|
||||
setEPLRS(true);
|
||||
}
|
||||
|
||||
void Unit::setTACANChannel(int newTACANChannel) {
|
||||
@@ -368,11 +381,6 @@ void Unit::setTACANCallsign(wstring newTACANCallsign) {
|
||||
addMeasure(L"TACANCallsign", json::value(newTACANCallsign));
|
||||
}
|
||||
|
||||
void Unit::setRadioOn(bool newRadioOn) {
|
||||
radioOn = newRadioOn;
|
||||
addMeasure(L"radioOn", json::value(newRadioOn));
|
||||
}
|
||||
|
||||
void Unit::setRadioFrequency(int newRadioFrequency) {
|
||||
radioFrequency = newRadioFrequency;
|
||||
addMeasure(L"radioFrequency", json::value(newRadioFrequency));
|
||||
@@ -388,6 +396,19 @@ void Unit::setRadioCallsignNumber(int newRadioCallsignNumber) {
|
||||
addMeasure(L"radioCallsignNumber", json::value(newRadioCallsignNumber));
|
||||
}
|
||||
|
||||
void Unit::setEPLRS(bool state)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
commandSS << "{"
|
||||
<< "id = 'EPLRS',"
|
||||
<< "params = {"
|
||||
<< "value = " << (state? "true": "false") << ", "
|
||||
<< "}"
|
||||
<< "}";
|
||||
Command* command = dynamic_cast<Command*>(new SetCommand(ID, commandSS.str()));
|
||||
scheduler->appendCommand(command);
|
||||
}
|
||||
|
||||
void Unit::setTACAN()
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
@@ -395,9 +416,9 @@ void Unit::setTACAN()
|
||||
<< "id = 'ActivateBeacon',"
|
||||
<< "params = {"
|
||||
<< "type = " << ((TACANXY.compare(L"X") == 0)? 4: 5) << ","
|
||||
<< "system = 4,"
|
||||
<< "name = Olympus_TACAN,"
|
||||
<< "callsign = " << TACANCallsign << ", "
|
||||
<< "system = 3,"
|
||||
<< "name = \"Olympus_TACAN\","
|
||||
<< "callsign = \"" << TACANCallsign << "\", "
|
||||
<< "frequency = " << TACANChannelToFrequency(TACANChannel, TACANXY) << ","
|
||||
<< "}"
|
||||
<< "}";
|
||||
|
||||
@@ -97,7 +97,9 @@ void UnitsManager::getData(json::value& answer, long long time)
|
||||
auto unitsJson = json::value::object();
|
||||
for (auto const& p : units)
|
||||
{
|
||||
unitsJson[to_wstring(p.first)] = p.second->getData(time);
|
||||
auto unitJson = p.second->getData(time);
|
||||
if (unitJson.size() > 0)
|
||||
unitsJson[to_wstring(p.first)] = p.second->getData(time);
|
||||
}
|
||||
answer[L"units"] = unitsJson;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION "v0.2.0"
|
||||
#define VERSION "v0.2.1"
|
||||
#define LOG_NAME "Olympus_log.txt"
|
||||
#define REST_ADDRESS L"http://localhost:30000"
|
||||
#define REST_URI L"olympus"
|
||||
|
||||
81
third-party/base64/include/base64.hpp
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
#ifndef BASE_64_HPP
|
||||
#define BASE_64_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
namespace base64 {
|
||||
|
||||
inline std::string get_base64_chars() {
|
||||
static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
return base64_chars;
|
||||
}
|
||||
|
||||
inline std::string to_base64(std::string const &data) {
|
||||
int counter = 0;
|
||||
uint32_t bit_stream = 0;
|
||||
const std::string base64_chars = get_base64_chars();
|
||||
std::string encoded;
|
||||
int offset = 0;
|
||||
for (unsigned char c : data) {
|
||||
auto num_val = static_cast<unsigned int>(c);
|
||||
offset = 16 - counter % 3 * 8;
|
||||
bit_stream += num_val << offset;
|
||||
if (offset == 16) {
|
||||
encoded += base64_chars.at(bit_stream >> 18 & 0x3f);
|
||||
}
|
||||
if (offset == 8) {
|
||||
encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
|
||||
}
|
||||
if (offset == 0 && counter != 3) {
|
||||
encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
|
||||
encoded += base64_chars.at(bit_stream & 0x3f);
|
||||
bit_stream = 0;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
if (offset == 16) {
|
||||
encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
|
||||
encoded += "==";
|
||||
}
|
||||
if (offset == 8) {
|
||||
encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
|
||||
encoded += '=';
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
inline std::string from_base64(std::string const &data) {
|
||||
int counter = 0;
|
||||
uint32_t bit_stream = 0;
|
||||
std::string decoded;
|
||||
int offset = 0;
|
||||
const std::string base64_chars = get_base64_chars();
|
||||
for (unsigned char c : data) {
|
||||
auto num_val = base64_chars.find(c);
|
||||
if (num_val != std::string::npos) {
|
||||
offset = 18 - counter % 4 * 6;
|
||||
bit_stream += num_val << offset;
|
||||
if (offset == 12) {
|
||||
decoded += static_cast<char>(bit_stream >> 16 & 0xff);
|
||||
}
|
||||
if (offset == 6) {
|
||||
decoded += static_cast<char>(bit_stream >> 8 & 0xff);
|
||||
}
|
||||
if (offset == 0 && counter != 4) {
|
||||
decoded += static_cast<char>(bit_stream & 0xff);
|
||||
bit_stream = 0;
|
||||
}
|
||||
} else if (c != '=') {
|
||||
return std::string();
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // BASE_64_HPP
|
||||