mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge branch 'release-candidate' into manager-wizard
This commit is contained in:
commit
73b1714191
2
.github/workflows/build_package.yml
vendored
2
.github/workflows/build_package.yml
vendored
@ -33,6 +33,6 @@ jobs:
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
with:
|
||||
name: latest
|
||||
name: development_build_not_a_release
|
||||
path: ./package
|
||||
|
||||
|
||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
uses: actions/setup-node@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: npm install
|
||||
working-directory: ./client
|
||||
|
||||
- name: Create the docs directory locally in CI
|
||||
|
||||
@ -23,7 +23,7 @@ Even better it requires no client mods be installed if used on a server
|
||||
The full feature list is simply too long to enumerate in a short summary but needless to say Olympus offers up a lot of unique gameplay that has previously not existed, and enhances many other elements of DCS in exciting ways
|
||||
|
||||
### Installing DCS Olympus
|
||||
A prebuilt installer will soon be released and available here
|
||||
Check the [Wiki](https://github.com/Pax1601/DCSOlympus/wiki) for installation instructions
|
||||
|
||||
# Frequently Asked Questions
|
||||
### Can I join up and help out with the project? ###
|
||||
|
||||
18
client/Dockerfile
Normal file
18
client/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
FROM node:20-alpine AS appbuild
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY package.json ./
|
||||
COPY package-lock.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build-release-linux
|
||||
|
||||
FROM node:20-alpine
|
||||
WORKDIR /usr/src/app
|
||||
COPY package.json ./
|
||||
COPY package-lock.json ./
|
||||
RUN npm install --omit=dev
|
||||
COPY . .
|
||||
COPY --from=appbuild /usr/src/app/public ./public
|
||||
EXPOSE 3000
|
||||
CMD npm start
|
||||
194
client/configurator.js
Normal file
194
client/configurator.js
Normal file
@ -0,0 +1,194 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const yargs = require('yargs');
|
||||
const prompt = require('prompt-sync')({sigint: true});
|
||||
const sha256 = require('sha256');
|
||||
var jsonPath = path.join('..', 'olympus.json');
|
||||
var regedit = require('regedit')
|
||||
|
||||
const shellFoldersKey = 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
|
||||
const saveGamesKey = '{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}'
|
||||
|
||||
/* Set the acceptable values */
|
||||
yargs.alias('a', 'address').describe('a', 'Backend address').string('a');
|
||||
yargs.alias('b', 'backendPort').describe('b', 'Backend port').number('b');
|
||||
yargs.alias('c', 'clientPort').describe('c', 'Client port').number('c');
|
||||
yargs.alias('p', 'gameMasterPassword').describe('p', 'Game Master password').string('p');
|
||||
yargs.alias('bp', 'blueCommanderPassword').describe('bp', 'Blue Commander password').string('bp');
|
||||
yargs.alias('rp', 'redCommanderPassword').describe('rp', 'Red Commander password').string('rp');
|
||||
yargs.alias('d', 'directory').describe('d', 'Directory where the DCS Olympus configurator is located').string('rp');
|
||||
args = yargs.argv;
|
||||
|
||||
async function run() {
|
||||
/* Check that we can read the json */
|
||||
if (fs.existsSync(jsonPath)) {
|
||||
var json = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
|
||||
|
||||
var address = args.address ?? json["server"]["address"];
|
||||
var clientPort = args.clientPort ?? json["client"]["port"];
|
||||
var backendPort = args.backendPort ?? json["server"]["port"];
|
||||
var gameMasterPassword = args.gameMasterPassword? sha256(args.gameMasterPassword): json["authentication"]["gameMasterPassword"];
|
||||
var blueCommanderPassword = args.blueCommanderPassword? sha256(args.blueCommanderPassword): json["authentication"]["blueCommanderPassword"];
|
||||
var redCommanderPassword = args.redCommanderPassword? sha256(args.redCommanderPassword): json["authentication"]["redCommanderPassword"];
|
||||
|
||||
/* Run in interactive mode */
|
||||
if (args.address === undefined && args.clientPort === undefined && args.backendPort === undefined &&
|
||||
args.gameMasterPassword === undefined && args.blueCommanderPassword === undefined && args.redCommanderPassword === undefined) {
|
||||
|
||||
var newValue;
|
||||
var result;
|
||||
|
||||
/* Get the new address */
|
||||
newValue = prompt(`Insert an address or press Enter to keep current value ${address}. Use * for any address: `);
|
||||
address = newValue !== ""? newValue: address;
|
||||
|
||||
/* Get the new client port */
|
||||
while (true) {
|
||||
newValue = prompt(`Insert a client port or press Enter to keep current value ${clientPort}. Integers between 1025 and 65535: `);
|
||||
if (newValue === "")
|
||||
break;
|
||||
result = Number(newValue);
|
||||
|
||||
if (!isNaN(result) && Number.isInteger(result) && result > 1024 && result <= 65535)
|
||||
break;
|
||||
}
|
||||
clientPort = newValue? result: clientPort;
|
||||
|
||||
/* Get the new backend port */
|
||||
while (true) {
|
||||
newValue = prompt(`Insert a backend port or press Enter to keep current value ${backendPort}. Integers between 1025 and 65535: `);
|
||||
if (newValue === "")
|
||||
break;
|
||||
result = Number(newValue);
|
||||
|
||||
if (!isNaN(result) && Number.isInteger(result) && result > 1024 && result <= 65535 && result != clientPort)
|
||||
break;
|
||||
|
||||
if (result === clientPort)
|
||||
console.log("Client port and backend port must be different.");
|
||||
}
|
||||
backendPort = newValue? result: backendPort;
|
||||
|
||||
/* Get the new Game Master password */
|
||||
while (true) {
|
||||
newValue = prompt(`Insert a new Game Master password or press Enter to keep current value: `, {echo: "*"});
|
||||
gameMasterPassword = newValue !== ""? sha256(newValue): gameMasterPassword;
|
||||
|
||||
// Check if Game Master password is unique
|
||||
if (gameMasterPassword === blueCommanderPassword || gameMasterPassword === redCommanderPassword) {
|
||||
console.log("Game Master password must be different from other passwords. Please try again.");
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Get the new Blue Commander password */
|
||||
while (true) {
|
||||
newValue = prompt(`Insert a new Blue Commander password or press Enter to keep current value: `, {echo: "*"});
|
||||
blueCommanderPassword = newValue !== ""? sha256(newValue): blueCommanderPassword;
|
||||
|
||||
// Check if Blue Commander password is unique
|
||||
if (blueCommanderPassword === gameMasterPassword || blueCommanderPassword === redCommanderPassword) {
|
||||
console.log("Blue Commander password must be different from other passwords. Please try again.");
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Get the new Red Commander password */
|
||||
while (true) {
|
||||
newValue = prompt(`Insert a new Red Commander password or press Enter to keep current value: `, {echo: "*"});
|
||||
redCommanderPassword = newValue !== ""? sha256(newValue): redCommanderPassword;
|
||||
|
||||
// Check if Red Commander password is unique
|
||||
if (redCommanderPassword === gameMasterPassword || redCommanderPassword === blueCommanderPassword) {
|
||||
console.log("Red Commander password must be different from other passwords. Please try again.");
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply the inputs */
|
||||
json["server"]["address"] = address;
|
||||
json["client"]["port"] = clientPort;
|
||||
json["server"]["port"] = backendPort;
|
||||
json["authentication"]["gameMasterPassword"] = gameMasterPassword;
|
||||
json["authentication"]["blueCommanderPassword"] = blueCommanderPassword;
|
||||
json["authentication"]["redCommanderPassword"] = redCommanderPassword;
|
||||
|
||||
/* Write the result to disk */
|
||||
const serialized = JSON.stringify(json, null, 4);
|
||||
fs.writeFileSync(jsonPath, serialized, 'utf8');
|
||||
console.log("Olympus.json updated correctly, goodbye!");
|
||||
}
|
||||
else {
|
||||
console.error("Error, could not read olympus.json file!")
|
||||
}
|
||||
|
||||
/* Wait a bit before closing the window */
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
}
|
||||
|
||||
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* _____ _____ _____ ____ _ *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* | __ \\ / ____|/ ____| / __ \\| | *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* | | | | | | (___ | | | | |_ _ _ __ ___ _ __ _ _ ___ *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* | | | | | \\___ \\ | | | | | | | | '_ ` _ \\| '_ \\| | | / __| *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* | |__| | |____ ____) | | |__| | | |_| | | | | | | |_) | |_| \\__ \\ *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* |_____/ \\_____|_____/ \\____/|_|\\__, |_| |_| |_| .__/ \\__,_|___/ *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* __/ | | | *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* |___/ |_| *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
|
||||
console.log('\x1b[36m%s\x1b[0m', "");
|
||||
|
||||
console.log("DCS Olympus configurator {{OLYMPUS_VERSION_NUMBER}}.{{OLYMPUS_COMMIT_HASH}}");
|
||||
console.log("");
|
||||
|
||||
/* Run the configurator */
|
||||
if (args.directory) {
|
||||
jsonPath = path.join(args.directory, "olympus.json");
|
||||
}
|
||||
else {
|
||||
/* Automatically detect possible DCS installation folders */
|
||||
regedit.list(shellFoldersKey, function(err, result) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
else {
|
||||
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined)
|
||||
{
|
||||
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
|
||||
const folders = fs.readdirSync(searchpath);
|
||||
var options = [];
|
||||
folders.forEach((folder) => {
|
||||
if (fs.existsSync(path.join(searchpath, folder, "Logs", "dcs.log"))) {
|
||||
options.push(folder);
|
||||
}
|
||||
})
|
||||
console.log("The following DCS Saved Games folders have been automatically detected.")
|
||||
options.forEach((folder, index) => {
|
||||
console.log(`(${index + 1}) ${folder}`)
|
||||
});
|
||||
while (true) {
|
||||
var newValue = prompt(`Please choose a folder onto which the configurator shall operate by typing the associated number: `)
|
||||
result = Number(newValue);
|
||||
|
||||
if (!isNaN(result) && Number.isInteger(result) && result > 0 && result <= options.length) {
|
||||
jsonPath = path.join(searchpath, options[result - 1], "Config", "olympus.json");
|
||||
break;
|
||||
}
|
||||
else {
|
||||
console.log(`Please type a number between 1 and ${options.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error("An error occured while trying to fetch the location of the DCS folder. Please type the folder location manually.")
|
||||
jsonPath = path.join(prompt(`DCS Saved Games folder location: `), "olympus.json");
|
||||
}
|
||||
console.log(`Configurator will run on ${jsonPath}, if this is incorrect please restart the configurator`)
|
||||
run();
|
||||
}
|
||||
})
|
||||
}
|
||||
4
client/copy.sh
Executable file
4
client/copy.sh
Executable file
@ -0,0 +1,4 @@
|
||||
cp ./node_modules/leaflet/dist/leaflet.css ./public/stylesheets/leaflet/leaflet.css
|
||||
cp ./node_modules/leaflet-gesture-handling/dist/leaflet-gesture-handling.css ./public/stylesheets/leaflet/leaflet-gesture-handling.css
|
||||
cp ./node_modules/leaflet.nauticscale/dist/leaflet.nauticscale.js ./public/javascripts/leaflet.nauticscale.js
|
||||
cp ./node_modules/leaflet-path-drag/dist/L.Path.Drag.js ./public/javascripts/L.Path.Drag.js
|
||||
@ -44,6 +44,7 @@
|
||||
"@types/node": "^18.16.1",
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"@types/svg-injector": "^0.0.29",
|
||||
"ajv": "^8.12.0",
|
||||
"babelify": "^10.0.0",
|
||||
"browserify": "^17.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
|
||||
@ -60,11 +60,6 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.leaflet-container img.leaflet-tile {
|
||||
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
@ -651,7 +646,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||
}
|
||||
|
||||
/* Printing */
|
||||
|
||||
|
||||
@media print {
|
||||
/* Prevent printers from removing background-images of controls. */
|
||||
.leaflet-control {
|
||||
|
||||
@ -8,24 +8,20 @@
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#map-contextmenu>div:nth-child(2) {
|
||||
align-items: center;
|
||||
|
||||
|
||||
/* #map-contextmenu>div:nth-child(n+4)>div {
|
||||
width: 100%;
|
||||
} */
|
||||
|
||||
#map-contextmenu .spawn-mode {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding-right: 0px;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
#map-contextmenu>div:nth-child(3) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
#map-contextmenu>div:nth-child(n+4) {
|
||||
align-items: center;
|
||||
.ol-context-menu-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
@ -33,10 +29,6 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#map-contextmenu>div:nth-child(n+4)>div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contextmenu-advanced-options,
|
||||
.contextmenu-metadata {
|
||||
align-items: center;
|
||||
@ -143,20 +135,6 @@
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
.ol-tag-CA {
|
||||
background-color: #FF000022;
|
||||
}
|
||||
|
||||
.ol-tag-Radar {
|
||||
background-color: #00FF0022;
|
||||
}
|
||||
|
||||
.ol-tag-IR {
|
||||
background-color: #0000FF22;
|
||||
}
|
||||
*/
|
||||
|
||||
.unit-loadout-list {
|
||||
min-width: 0;
|
||||
}
|
||||
@ -187,7 +165,65 @@
|
||||
content: " (" attr(data-points) " points)";
|
||||
}
|
||||
|
||||
.upper-bar svg>* {
|
||||
#spawn-mode-tabs {
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top:0;
|
||||
translate: -6px -100%;
|
||||
z-index: 9998;
|
||||
}
|
||||
|
||||
#spawn-mode-tabs button {
|
||||
align-items: center;
|
||||
border-bottom:2px solid transparent;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-left-radius: var(--border-radius-sm);
|
||||
border-top-right-radius: var(--border-radius-sm);
|
||||
display: flex;
|
||||
height:32px;
|
||||
justify-content: center;
|
||||
margin:0;
|
||||
width:38px;
|
||||
}
|
||||
|
||||
#spawn-mode-tabs button:hover {
|
||||
background-color: var(--background-steel);
|
||||
}
|
||||
|
||||
[data-coalition="blue"] + #spawn-mode-tabs button {
|
||||
border-bottom-color: var(--primary-blue);
|
||||
}
|
||||
|
||||
|
||||
[data-coalition="red"] + #spawn-mode-tabs button {
|
||||
border-bottom-color: var(--primary-red);
|
||||
}
|
||||
|
||||
|
||||
[data-coalition="neutral"] + #spawn-mode-tabs button {
|
||||
border-bottom-color: var(--primary-neutral);
|
||||
}
|
||||
|
||||
#spawn-mode-tabs button svg {
|
||||
height:24px;
|
||||
margin:6px;
|
||||
width:24px;
|
||||
}
|
||||
|
||||
.upper-bar {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.upper-bar svg>*,
|
||||
#spawn-mode-tabs button svg * {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
@ -200,24 +236,78 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#spawn-history-menu {
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
max-height: 300px;
|
||||
row-gap: 6px;
|
||||
}
|
||||
|
||||
#spawn-history-menu button {
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
display:flex;
|
||||
height:32px;
|
||||
text-align: left;
|
||||
padding:0;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
#spawn-history-menu button span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#spawn-history-menu button svg {
|
||||
border-radius: var(--border-radius-sm);
|
||||
height:24px;
|
||||
padding:4px;
|
||||
width:24px;
|
||||
}
|
||||
|
||||
#spawn-history-menu button:hover {
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#spawn-history-menu button:hover svg * {
|
||||
fill:white !important;
|
||||
}
|
||||
|
||||
#spawn-history-menu button[data-spawned-coalition="blue"] svg {
|
||||
background-color: var(--primary-blue);
|
||||
}
|
||||
|
||||
#spawn-history-menu button[data-spawned-coalition="red"] svg {
|
||||
background-color: var(--primary-red);
|
||||
}
|
||||
|
||||
#spawn-history-menu button[data-spawned-coalition="neutral"] svg {
|
||||
background-color: var(--primary-neutral);
|
||||
}
|
||||
|
||||
[data-coalition="blue"]#active-coalition-label,
|
||||
[data-coalition="blue"].deploy-unit-button,
|
||||
[data-coalition="blue"]#spawn-airbase-aircraft-button,
|
||||
[data-coalition="blue"].create-iads-button {
|
||||
[data-coalition="blue"].create-iads-button,
|
||||
[data-coalition="blue"] + #spawn-mode-tabs button.selected {
|
||||
background-color: var(--primary-blue)
|
||||
}
|
||||
|
||||
[data-coalition="red"]#active-coalition-label,
|
||||
[data-coalition="red"].deploy-unit-button,
|
||||
[data-coalition="red"]#spawn-airbase-aircraft-button,
|
||||
[data-coalition="red"].create-iads-button {
|
||||
[data-coalition="red"].create-iads-button,
|
||||
[data-coalition="red"] + #spawn-mode-tabs button.selected {
|
||||
background-color: var(--primary-red)
|
||||
}
|
||||
|
||||
[data-coalition="neutral"]#active-coalition-label,
|
||||
[data-coalition="neutral"].deploy-unit-button,
|
||||
[data-coalition="neutral"]#spawn-airbase-aircraft-button,
|
||||
[data-coalition="neutral"].create-iads-button {
|
||||
[data-coalition="neutral"].create-iads-button,
|
||||
[data-coalition="neutral"] + #spawn-mode-tabs button.selected {
|
||||
background-color: var(--primary-neutral)
|
||||
}
|
||||
|
||||
|
||||
@ -171,6 +171,10 @@ button svg.fill-coalition[data-coalition="red"] * {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ol-select[disabled] {
|
||||
color: var(--ol-dialog-disabled-text-color);
|
||||
}
|
||||
|
||||
.ol-select>.ol-select-value {
|
||||
align-content: center;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
@ -215,6 +219,10 @@ button svg.fill-coalition[data-coalition="red"] * {
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.ol-select[disabled]:not(.ol-select-image)>.ol-select-value:after {
|
||||
opacity: 15%;
|
||||
}
|
||||
|
||||
.ol-select:not(.ol-select-image)>.ol-select-value.ol-select-warning:after {
|
||||
background-image: url("/resources/theme/images/icons/chevron-down-warning.svg") !important;
|
||||
}
|
||||
@ -1312,6 +1320,11 @@ dl.ol-data-grid dd {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
|
||||
.ol-dialog label[disabled] {
|
||||
color: var(--ol-dialog-disabled-text-color)
|
||||
}
|
||||
|
||||
.ol-dialog-content table th {
|
||||
background-color: var(--background-grey);
|
||||
color:white;
|
||||
@ -1383,6 +1396,10 @@ dl.ol-data-grid dd {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ol-text-input input[disabled] {
|
||||
color:var(--ol-dialog-disabled-text-color);
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
@ -1422,6 +1439,11 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.ol-button-apply[disabled] {
|
||||
border-color: var(--ol-dialog-disabled-text-color);
|
||||
color:var(--ol-dialog-disabled-text-color);
|
||||
}
|
||||
|
||||
.ol-button-apply::before {
|
||||
content: "\2713";
|
||||
}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="12" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M169.4 470.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 370.8 224 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 306.7L54.6 265.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"/></svg>
|
||||
|
After Width: | Height: | Size: 473 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z"/></svg>
|
||||
|
After Width: | Height: | Size: 718 B |
@ -48,6 +48,8 @@
|
||||
--ol-switch-off:#686868;
|
||||
--ol-switch-undefined:#383838;
|
||||
|
||||
--ol-dialog-disabled-text-color: #ffffff20;
|
||||
|
||||
/*** General border radii **/
|
||||
--border-radius-xs: 2px;
|
||||
--border-radius-sm: 5px;
|
||||
|
||||
@ -26,6 +26,23 @@ export const ROEs: string[] = ["free", "designated", "", "return", "hold"];
|
||||
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
|
||||
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
|
||||
|
||||
export const ERAS = [{
|
||||
"name": "Early Cold War",
|
||||
"chronologicalOrder": 2
|
||||
}, {
|
||||
"name": "Late Cold War",
|
||||
"chronologicalOrder": 4
|
||||
}, {
|
||||
"name": "Mid Cold War",
|
||||
"chronologicalOrder": 3
|
||||
}, {
|
||||
"name": "Modern",
|
||||
"chronologicalOrder": 5
|
||||
}, {
|
||||
"name": "WW2",
|
||||
"chronologicalOrder": 1
|
||||
}];
|
||||
|
||||
export const ROEDescriptions: string[] = [
|
||||
"Free (Attack anyone)",
|
||||
"Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
|
||||
@ -204,16 +221,19 @@ export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
|
||||
"toggles": ["dcs"],
|
||||
"tooltip": "Toggle DCS-controlled units' visibility"
|
||||
}, {
|
||||
"category": "Aircraft",
|
||||
"image": "visibility/aircraft.svg",
|
||||
"name": "Aircraft",
|
||||
"toggles": ["aircraft"],
|
||||
"tooltip": "Toggle aircraft's visibility"
|
||||
}, {
|
||||
"category": "Helicopter",
|
||||
"image": "visibility/helicopter.svg",
|
||||
"name": "Helicopter",
|
||||
"toggles": ["helicopter"],
|
||||
"tooltip": "Toggle helicopters' visibility"
|
||||
}, {
|
||||
"category": "AirDefence",
|
||||
"image": "visibility/groundunit-sam.svg",
|
||||
"name": "Air defence",
|
||||
"toggles": ["groundunit-sam"],
|
||||
@ -224,6 +244,7 @@ export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
|
||||
"toggles": ["groundunit"],
|
||||
"tooltip": "Toggle ground units' visibility"
|
||||
}, {
|
||||
"category": "GroundUnit",
|
||||
"image": "visibility/navyunit.svg",
|
||||
"name": "Naval",
|
||||
"toggles": ["navyunit"],
|
||||
|
||||
@ -5,8 +5,10 @@ import { Switch } from "../controls/switch";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
|
||||
import { AirDefenceUnitSpawnMenu, AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { SmokeMarker } from "../map/markers/smokemarker";
|
||||
import { UnitSpawnTable } from "../interfaces";
|
||||
import { getCategoryBlueprintIconSVG, getUnitDatabaseByCategory } from "../other/utils";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
|
||||
/** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user.
|
||||
* It allows to spawn units, create explosions and smoke, and edit CoalitionAreas.
|
||||
@ -96,6 +98,8 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.getContainer()?.addEventListener("hide", () => this.#airDefenceUnitSpawnMenu.clearCirclesPreviews());
|
||||
this.getContainer()?.addEventListener("hide", () => this.#groundUnitSpawnMenu.clearCirclesPreviews());
|
||||
this.getContainer()?.addEventListener("hide", () => this.#navyUnitSpawnMenu.clearCirclesPreviews());
|
||||
|
||||
this.#setupHistory();
|
||||
}
|
||||
|
||||
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
|
||||
@ -257,4 +261,91 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#groundUnitSpawnMenu.setCountries();
|
||||
this.#navyUnitSpawnMenu.setCountries();
|
||||
}
|
||||
|
||||
/** Handles all of the logic for historal logging.
|
||||
*
|
||||
*/
|
||||
#setupHistory() {
|
||||
/* Set up the tab clicks */
|
||||
const spawnModes = this.getContainer()?.querySelectorAll(".spawn-mode");
|
||||
const activeCoalitionLabel = document.getElementById("active-coalition-label");
|
||||
const tabs = this.getContainer()?.querySelectorAll(".spawn-mode-tab");
|
||||
|
||||
// Default selected tab to the "spawn now" option
|
||||
if (tabs) tabs[tabs.length-1].classList.add("selected");
|
||||
|
||||
tabs?.forEach((btn:Element) => {
|
||||
btn.addEventListener("click", (ev:MouseEventInit) => {
|
||||
// Highlight tab
|
||||
tabs.forEach(tab => tab.classList.remove("selected"));
|
||||
btn.classList.add("selected");
|
||||
|
||||
// Hide/reset
|
||||
spawnModes?.forEach(div => div.classList.add("hide"));
|
||||
|
||||
const prevSiblings = [];
|
||||
let prev = btn.previousElementSibling;
|
||||
|
||||
/* Tabs and content windows are assumed to be in the same order */
|
||||
// Count previous
|
||||
while ( prev ) {
|
||||
prevSiblings.push(prev);
|
||||
prev = prev.previousElementSibling;
|
||||
}
|
||||
|
||||
// Show content
|
||||
if (spawnModes && spawnModes[prevSiblings.length]) {
|
||||
spawnModes[prevSiblings.length].classList.remove("hide");
|
||||
}
|
||||
|
||||
// We don't want to see the "Spawn [coalition] unit" label
|
||||
if (activeCoalitionLabel) activeCoalitionLabel.classList.toggle("hide", !btn.hasAttribute("data-show-label"));
|
||||
});
|
||||
});
|
||||
|
||||
const history = <HTMLDivElement>document.getElementById("spawn-history-menu");
|
||||
const maxEntries = 20;
|
||||
|
||||
/** Listen for unit spawned **/
|
||||
document.addEventListener( "unitSpawned", (ev:CustomEventInit) => {
|
||||
const buttons = history.querySelectorAll("button");
|
||||
const detail:any = ev.detail;
|
||||
if (buttons.length === 0) history.innerHTML = ""; // Take out any "no data" messages
|
||||
const button = document.createElement("button");
|
||||
button.title = "Click to spawn";
|
||||
button.setAttribute("data-spawned-coalition", detail.coalition);
|
||||
button.setAttribute("data-unit-type", detail.unitSpawnTable[0].unitType);
|
||||
button.setAttribute("data-unit-qty", detail.unitSpawnTable.length);
|
||||
|
||||
const db = getUnitDatabaseByCategory(detail.category);
|
||||
button.innerHTML = `<img src="${getCategoryBlueprintIconSVG(detail.category, detail.unitSpawnTable[0].unitType)}" /><span>${db?.getByName(detail.unitSpawnTable[0].unitType)?.label} (${detail.unitSpawnTable.length})</span>`;
|
||||
|
||||
// Remove a previous instance to save clogging up the list
|
||||
const previous:any = [].slice.call(buttons).find( (button:Element) => (
|
||||
detail.coalition === button.getAttribute("data-spawned-coalition") &&
|
||||
detail.unitSpawnTable[0].unitType === button.getAttribute("data-unit-type") &&
|
||||
detail.unitSpawnTable.length === parseInt(button.getAttribute("data-unit-qty") || "-1")));
|
||||
|
||||
if (previous instanceof HTMLElement) previous.remove();
|
||||
|
||||
/* Click to do the spawn */
|
||||
button.addEventListener("click", (ev:MouseEventInit) => {
|
||||
detail.unitSpawnTable.forEach((table:UnitSpawnTable, i:number) => {
|
||||
table.location = this.getLatLng(); // Set to new menu location
|
||||
table.location.lat += 0.00015 * i;
|
||||
});
|
||||
getApp().getUnitsManager().spawnUnits(detail.category, detail.unitSpawnTable, detail.coalition, detail.immediate, detail.airbase, detail.country);
|
||||
this.hide();
|
||||
});
|
||||
|
||||
/* Insert into DOM */
|
||||
history.prepend(button);
|
||||
SVGInjector(button.querySelectorAll("img"));
|
||||
|
||||
/* Trim down to max number of entries */
|
||||
while (history.querySelectorAll("button").length > maxEntries) {
|
||||
history.childNodes[maxEntries].remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,9 @@ export class Dropdown {
|
||||
|
||||
if (options != null) this.setOptions(options);
|
||||
|
||||
(this.#container.querySelector(".ol-select-value") as HTMLElement)?.addEventListener("click", (ev) => { this.#toggle(); });
|
||||
(this.#container.querySelector(".ol-select-value") as HTMLElement)?.addEventListener("click", (ev) => {
|
||||
if (!this.#container.hasAttribute("disabled") && !this.#container.closest("disabled")) this.#toggle();
|
||||
});
|
||||
|
||||
document.addEventListener("click", (ev) => {
|
||||
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#container.contains(ev.target as Node))) {
|
||||
|
||||
@ -38,6 +38,7 @@ require("../../public/javascripts/leaflet.nauticscale.js")
|
||||
require("../../public/javascripts/L.Path.Drag.js")
|
||||
|
||||
export type MapMarkerVisibilityControl = {
|
||||
"category"?: string;
|
||||
"image": string;
|
||||
"isProtected"?: boolean,
|
||||
"name": string,
|
||||
|
||||
@ -2,7 +2,7 @@ import { LatLng } from "leaflet";
|
||||
import { getApp } from "..";
|
||||
import { Airbase } from "./airbase";
|
||||
import { Bullseye } from "./bullseye";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
|
||||
import { BLUE_COMMANDER, ERAS, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
|
||||
@ -26,13 +26,16 @@ export class MissionManager {
|
||||
#coalitions: {red: string[], blue: string[]} = {red: [], blue: []};
|
||||
|
||||
constructor() {
|
||||
document.addEventListener("showCommandModeDialog", () => this.showCommandModeDialog());
|
||||
document.addEventListener("applycommandModeOptions", () => this.#applycommandModeOptions());
|
||||
document.addEventListener("showCommandModeDialog", () => this.showCommandModeDialog());
|
||||
document.addEventListener("toggleSpawnRestrictions", (ev:CustomEventInit) => {
|
||||
this.#toggleSpawnRestrictions(ev.detail._element.checked)
|
||||
});
|
||||
|
||||
/* command-mode settings dialog */
|
||||
this.#commandModeDialog = document.querySelector("#command-mode-settings-dialog") as HTMLElement;
|
||||
|
||||
this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
|
||||
|
||||
}
|
||||
|
||||
/** Update location of bullseyes
|
||||
@ -211,12 +214,18 @@ export class MissionManager {
|
||||
}
|
||||
|
||||
showCommandModeDialog() {
|
||||
const options = this.getCommandModeOptions()
|
||||
const { restrictSpawns, restrictToCoalition, setupTime } = options;
|
||||
this.#toggleSpawnRestrictions(restrictSpawns);
|
||||
|
||||
/* Create the checkboxes to select the unit eras */
|
||||
var eras = aircraftDatabase.getEras().concat(helicopterDatabase.getEras()).concat(groundUnitDatabase.getEras()).concat(navyUnitDatabase.getEras());
|
||||
eras = eras.filter((item: string, index: number) => eras.indexOf(item) === index).sort();
|
||||
this.#commandModeErasDropdown.setOptionsElements(eras.map((era: string) => {
|
||||
return createCheckboxOption(era, `Enable ${era} units spawns`, this.getCommandModeOptions().eras.includes(era));
|
||||
}));
|
||||
this.#commandModeErasDropdown.setOptionsElements(
|
||||
ERAS.sort((eraA, eraB) => {
|
||||
return ( eraA.chronologicalOrder > eraB.chronologicalOrder ) ? 1 : -1;
|
||||
}).map((era) => {
|
||||
return createCheckboxOption(era.name, `Enable ${era} units spawns`, this.getCommandModeOptions().eras.includes(era.name));
|
||||
})
|
||||
);
|
||||
|
||||
this.#commandModeDialog.classList.remove("hide");
|
||||
|
||||
@ -226,11 +235,11 @@ export class MissionManager {
|
||||
const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
|
||||
const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
|
||||
|
||||
restrictSpawnsCheckbox.checked = this.getCommandModeOptions().restrictSpawns;
|
||||
restrictToCoalitionCheckbox.checked = this.getCommandModeOptions().restrictToCoalition;
|
||||
blueSpawnPointsInput.value = String(this.getCommandModeOptions().spawnPoints.blue);
|
||||
redSpawnPointsInput.value = String(this.getCommandModeOptions().spawnPoints.red);
|
||||
setupTimeInput.value = String(Math.floor(this.getCommandModeOptions().setupTime / 60.0));
|
||||
restrictSpawnsCheckbox.checked = restrictSpawns;
|
||||
restrictToCoalitionCheckbox.checked = restrictToCoalition;
|
||||
blueSpawnPointsInput.value = String(options.spawnPoints.blue);
|
||||
redSpawnPointsInput.value = String(options.spawnPoints.red);
|
||||
setupTimeInput.value = String(Math.floor(setupTime / 60.0));
|
||||
}
|
||||
|
||||
#applycommandModeOptions() {
|
||||
@ -309,4 +318,10 @@ export class MissionManager {
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
#toggleSpawnRestrictions(restrictionsEnabled:boolean) {
|
||||
this.#commandModeDialog.querySelectorAll("input, label, .ol-select").forEach( el => {
|
||||
if (!el.closest("#restrict-spawns")) el.toggleAttribute("disabled", !restrictionsEnabled);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@ import { Manager } from "./other/manager";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { ServerManager } from "./server/servermanager";
|
||||
import { sha256 } from 'js-sha256';
|
||||
import Ajv from "ajv"
|
||||
|
||||
import { BLUE_COMMANDER, FILL_SELECTED_RING, GAME_MASTER, HIDE_UNITS_SHORT_RANGE_RINGS, RED_COMMANDER, SHOW_UNITS_ACQUISITION_RINGS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_LABELS } from "./constants/constants";
|
||||
import { aircraftDatabase } from "./unit/databases/aircraftdatabase";
|
||||
@ -27,6 +28,8 @@ import { navyUnitDatabase } from "./unit/databases/navyunitdatabase";
|
||||
import { UnitListPanel } from "./panels/unitlistpanel";
|
||||
import { ContextManager } from "./context/contextmanager";
|
||||
import { Context } from "./context/context";
|
||||
import { AirDefenceUnitSpawnMenu } from "./controls/unitspawnmenu";
|
||||
import { AirbasesJSONSchemaValidator } from "./schemas/schema";
|
||||
|
||||
var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
|
||||
|
||||
@ -193,6 +196,9 @@ export class OlympusApp {
|
||||
this.#unitsManager = new UnitsManager();
|
||||
this.#weaponsManager = new WeaponsManager();
|
||||
|
||||
/* Validate data */
|
||||
this.#validateData();
|
||||
|
||||
// Toolbars
|
||||
this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar"))
|
||||
.add("commandModeToolbar", new CommandModeToolbar("command-mode-toolbar"));
|
||||
@ -447,4 +453,28 @@ export class OlympusApp {
|
||||
img.addEventListener("load", () => { SVGInjector(img); });
|
||||
})
|
||||
}
|
||||
|
||||
#validateData() {
|
||||
|
||||
const airbasesValidator = new AirbasesJSONSchemaValidator();
|
||||
|
||||
/*
|
||||
const validator = new Ajv();
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
foo: {type: "integer"},
|
||||
bar: {type: "string"},
|
||||
},
|
||||
required: ["foo"],
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
const data = this.#getRunwayData();
|
||||
|
||||
const validate = validator.compile(schema);
|
||||
const valid = validate(data);
|
||||
if (!valid) console.log(validate.errors);
|
||||
//*/
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@ import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { Buffer } from "buffer";
|
||||
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
|
||||
import { GROUND_UNIT_AIR_DEFENCE_REGEX, ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
import { DateAndTime, UnitBlueprint } from "../interfaces";
|
||||
@ -379,6 +379,20 @@ export function getUnitDatabaseByCategory(category: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getCategoryBlueprintIconSVG(category:string, unitName:string) {
|
||||
|
||||
const path = "/resources/theme/images/buttons/visibility/";
|
||||
|
||||
// We can just send these back okay
|
||||
if (["Aircraft", "Helicopter", "NavyUnit"].includes(category)) return `${path}${category.toLowerCase()}.svg`;
|
||||
|
||||
// Return if not a ground units as it's therefore something we don't recognise
|
||||
if (category !== "GroundUnit") return false;
|
||||
|
||||
/** We need to get the unit detail for ground units so we can work out if it's an air defence unit or not **/
|
||||
return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unitName) ? `${path}groundunit-sam.svg` : `${path}groundunit.svg`;
|
||||
}
|
||||
|
||||
export function base64ToBytes(base64: string) {
|
||||
return Buffer.from(base64, 'base64').buffer;
|
||||
}
|
||||
|
||||
67
client/src/schemas/airbases.schema.json
Normal file
67
client/src/schemas/airbases.schema.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"airfields": {
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"elevation": {
|
||||
"type": "string",
|
||||
"pattern": "^(0|([1-9][0-9]*))?$"
|
||||
},
|
||||
"ICAO": {
|
||||
"type": "string"
|
||||
},
|
||||
"runways": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"headings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ILS": {
|
||||
"type": "string",
|
||||
"pattern": "^(1[0-9]{1,2}\\.[0-9][05])?$"
|
||||
},
|
||||
"magHeading": {
|
||||
"type": "string",
|
||||
"pattern": "^([0-2][0-9]{2})|(3(([0-5][0-9])|(60)))?$"
|
||||
}
|
||||
},
|
||||
"required": ["magHeading"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"length": {
|
||||
"type": "string",
|
||||
"pattern": "^[1-9][0-9]{3,4}$"
|
||||
}
|
||||
},
|
||||
"required": [ "headings", "length" ]
|
||||
}
|
||||
},
|
||||
"TACAN": {
|
||||
"type": "string",
|
||||
"pattern": "^([1-9][0-9]{1,2}X)?$"
|
||||
}
|
||||
},
|
||||
"required": [ "elevation", "runways" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["airfields"]
|
||||
}
|
||||
425
client/src/schemas/importdata.schema.json
Normal file
425
client/src/schemas/importdata.schema.json
Normal file
@ -0,0 +1,425 @@
|
||||
{
|
||||
"$defs": {
|
||||
"coalitionName": {
|
||||
"enum": [
|
||||
"blue",
|
||||
"neutral",
|
||||
"red"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"lat": {
|
||||
"maximum": 90,
|
||||
"minimum": -90,
|
||||
"type": "number"
|
||||
},
|
||||
"lng": {
|
||||
"maximum": 180,
|
||||
"minimum": -180,
|
||||
"type": "number"
|
||||
},
|
||||
"vec2": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"lat": {
|
||||
"$ref": "#/$defs/lat"
|
||||
},
|
||||
"lng": {
|
||||
"$ref": "#/$defs/lng"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lat",
|
||||
"lng"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"vec3": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"alt": {
|
||||
"type": "number"
|
||||
},
|
||||
"lat": {
|
||||
"$ref": "#/$defs/lat"
|
||||
},
|
||||
"lng": {
|
||||
"$ref": "#/$defs/lng"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"alt",
|
||||
"lat",
|
||||
"lng"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"activePath": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/vec3"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"alive": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ammo": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"category": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"guidance": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"missileCategory": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"minLength": 3,
|
||||
"type": "string"
|
||||
},
|
||||
"quantity": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"quantity",
|
||||
"name",
|
||||
"guidance",
|
||||
"category",
|
||||
"missileCategory"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"categoryDisplayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"coalition": {
|
||||
"$ref": "#/$defs/coalitionName"
|
||||
},
|
||||
"contacts": {
|
||||
"type": "array"
|
||||
},
|
||||
"controlled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"country": {
|
||||
"type": "number"
|
||||
},
|
||||
"desiredAltitude": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"desiredAltitudeType": {
|
||||
"enum": [
|
||||
"AGL",
|
||||
"ASL"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"desiredSpeed": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"desiredSpeedType": {
|
||||
"enum": [
|
||||
"CAS",
|
||||
"GS"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"emissionsCountermeasures": {
|
||||
"enum": [
|
||||
"attac",
|
||||
"defend",
|
||||
"free",
|
||||
"silent"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"followRoads": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"formationOffset": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"x": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"y": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"z": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"x",
|
||||
"y",
|
||||
"z"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"fuel": {
|
||||
"maximum": 100,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"generalSettings": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"prohibitAA": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"prohibitAfterburner": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"prohibitAG": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"prohibitAirWpn": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"prohibitJettison": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"prohibitAA",
|
||||
"prohibitAfterburner",
|
||||
"prohibitAG",
|
||||
"prohibitAirWpn",
|
||||
"prohibitJettison"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"hasTask": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"heading": {
|
||||
"type": "number"
|
||||
},
|
||||
"health": {
|
||||
"maximum": 100,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"horizontalVelocity": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"human": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ID": {
|
||||
"type": "number"
|
||||
},
|
||||
"isActiveAWACS": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isActiveTanker": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isLeader": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"leaderID": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"onOff": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"operateAs": {
|
||||
"$ref": "#/$defs/coalitionName"
|
||||
},
|
||||
"position": {
|
||||
"$ref": "#/$defs/vec3"
|
||||
},
|
||||
"radio": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"callsign": {
|
||||
"type": "number"
|
||||
},
|
||||
"callsignNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"frequency": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"frequency",
|
||||
"callsign",
|
||||
"callsignNumber"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"reactionToThreat": {
|
||||
"enum": [
|
||||
"evade",
|
||||
"maneouvre",
|
||||
"none",
|
||||
"passive"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ROE": {
|
||||
"enum": [
|
||||
"designated",
|
||||
"free",
|
||||
"hold",
|
||||
"return"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"shotsIntensity": {
|
||||
"maximum": 3,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
},
|
||||
"shotsScatter": {
|
||||
"maximum": 3,
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
},
|
||||
"speed": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"TACAN": {
|
||||
"properties": {
|
||||
"callsign": {
|
||||
"type": "string"
|
||||
},
|
||||
"channel": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"isOn": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"XY": {
|
||||
"enum": [
|
||||
"X",
|
||||
"Y"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"callsign",
|
||||
"channel",
|
||||
"isOn",
|
||||
"XY"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"targetID": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"targetPosition": {
|
||||
"$ref": "#/$defs/vec2"
|
||||
},
|
||||
"task": {
|
||||
"type": "string"
|
||||
},
|
||||
"track": {
|
||||
"type": "number"
|
||||
},
|
||||
"unitName": {
|
||||
"type": "string"
|
||||
},
|
||||
"verticalVelocity": {
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"activePath",
|
||||
"alive",
|
||||
"ammo",
|
||||
"category",
|
||||
"categoryDisplayName",
|
||||
"coalition",
|
||||
"contacts",
|
||||
"controlled",
|
||||
"country",
|
||||
"desiredAltitude",
|
||||
"desiredAltitudeType",
|
||||
"desiredSpeed",
|
||||
"desiredSpeedType",
|
||||
"emissionsCountermeasures",
|
||||
"followRoads",
|
||||
"formationOffset",
|
||||
"fuel",
|
||||
"generalSettings",
|
||||
"groupName",
|
||||
"hasTask",
|
||||
"heading",
|
||||
"health",
|
||||
"horizontalVelocity",
|
||||
"human",
|
||||
"ID",
|
||||
"isActiveAWACS",
|
||||
"isActiveTanker",
|
||||
"isLeader",
|
||||
"leaderID",
|
||||
"name",
|
||||
"onOff",
|
||||
"operateAs",
|
||||
"position",
|
||||
"radio",
|
||||
"reactionToThreat",
|
||||
"ROE",
|
||||
"shotsIntensity",
|
||||
"shotsScatter",
|
||||
"speed",
|
||||
"state",
|
||||
"TACAN",
|
||||
"targetID",
|
||||
"targetPosition",
|
||||
"task",
|
||||
"track",
|
||||
"unitName",
|
||||
"verticalVelocity"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
82
client/src/schemas/schema.ts
Normal file
82
client/src/schemas/schema.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import Ajv from "ajv";
|
||||
import { AnySchemaObject } from "ajv/dist/core";
|
||||
|
||||
|
||||
// For future extension
|
||||
abstract class JSONSchemaValidator {
|
||||
|
||||
#ajv:Ajv;
|
||||
#compiledValidator:any;
|
||||
#schema!:AnySchemaObject;
|
||||
|
||||
constructor( schema:AnySchemaObject ) {
|
||||
this.#schema = schema;
|
||||
|
||||
this.#ajv = new Ajv({
|
||||
"allErrors": true
|
||||
});
|
||||
|
||||
this.#compiledValidator = this.getAjv().compile(this.getSchema());
|
||||
|
||||
}
|
||||
|
||||
getAjv() {
|
||||
return this.#ajv;
|
||||
}
|
||||
|
||||
getCompiledValidator() {
|
||||
return this.#compiledValidator;
|
||||
}
|
||||
|
||||
getErrors() {
|
||||
return this.getCompiledValidator().errors;
|
||||
}
|
||||
|
||||
getSchema() {
|
||||
return this.#schema;
|
||||
}
|
||||
|
||||
validate(data:any) {
|
||||
return (this.getCompiledValidator())(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class AirbasesJSONSchemaValidator extends JSONSchemaValidator {
|
||||
|
||||
constructor() {
|
||||
|
||||
const schema = require("../schemas/airbases.schema.json");
|
||||
|
||||
super( schema );
|
||||
|
||||
[
|
||||
require( "../../public/databases/airbases/caucasus.json" ),
|
||||
require( "../../public/databases/airbases/falklands.json" ),
|
||||
require( "../../public/databases/airbases/marianas.json" ),
|
||||
require( "../../public/databases/airbases/nevada.json" ),
|
||||
require( "../../public/databases/airbases/normandy.json" ),
|
||||
require( "../../public/databases/airbases/persiangulf.json" ),
|
||||
require( "../../public/databases/airbases/sinaimap.json" ),
|
||||
require( "../../public/databases/airbases/syria.json" ),
|
||||
require( "../../public/databases/airbases/thechannel.json" )
|
||||
].forEach( data => {
|
||||
const validate = this.getAjv().compile(this.getSchema());
|
||||
const valid = validate(data);
|
||||
|
||||
if (!valid) console.error(validate.errors);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class ImportFileJSONSchemaValidator extends JSONSchemaValidator {
|
||||
|
||||
constructor() {
|
||||
const schema = require("../schemas/importdata.schema.json");
|
||||
super( schema );
|
||||
}
|
||||
|
||||
}
|
||||
@ -25,6 +25,10 @@ export class UnitDataFileExport extends UnitDataFile {
|
||||
* Show the form to start the export journey
|
||||
*/
|
||||
showForm(units: Unit[]) {
|
||||
this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
|
||||
el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
|
||||
});
|
||||
|
||||
const data: any = {};
|
||||
const unitCanBeExported = (unit: Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory());
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { getApp } from "../..";
|
||||
import { Dialog } from "../../dialog/dialog";
|
||||
import { UnitData } from "../../interfaces";
|
||||
import { ImportFileJSONSchemaValidator } from "../../schemas/schema";
|
||||
import { UnitDataFile } from "./unitdatafile";
|
||||
|
||||
export class UnitDataFileImport extends UnitDataFile {
|
||||
@ -48,21 +49,65 @@ export class UnitDataFileImport extends UnitDataFile {
|
||||
|
||||
unitsManager.spawnUnits(category, unitsToSpawn, coalition, false);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
for (let groupName in groups) {
|
||||
if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: UnitData) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) {
|
||||
var aliveUnits = groups[groupName].filter((unit: UnitData) => { return unit.alive });
|
||||
var units = aliveUnits.map((unit: UnitData) => {
|
||||
return { unitType: unit.name, location: unit.position, liveryID: "" }
|
||||
});
|
||||
getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
|
||||
selectFile() {
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.addEventListener("change", (e: any) => {
|
||||
var file = e.target.files[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
//*/
|
||||
var reader = new FileReader();
|
||||
reader.onload = (e: any) => {
|
||||
|
||||
try {
|
||||
this.#fileData = JSON.parse(e.target.result);
|
||||
|
||||
const validator = new ImportFileJSONSchemaValidator();
|
||||
if (!validator.validate(this.#fileData)) {
|
||||
const errors = validator.getErrors().reduce((acc:any, error:any) => {
|
||||
let errorString = error.instancePath.substring(1) + ": " + error.message;
|
||||
if (error.params) {
|
||||
const {allowedValues} = error.params;
|
||||
if (allowedValues)
|
||||
errorString += ": " + allowedValues.join(', ');
|
||||
}
|
||||
acc.push(errorString);
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
this.#showFileDataErrors(errors);
|
||||
} else {
|
||||
this.#showForm();
|
||||
}
|
||||
} catch(e:any) {
|
||||
this.#showFileDataErrors([e]);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
})
|
||||
input.click();
|
||||
}
|
||||
|
||||
#showFileDataErrors( reasons:string[]) {
|
||||
|
||||
this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
|
||||
el.classList.toggle("hide", el.getAttribute("data-on-error") === "hide");
|
||||
});
|
||||
|
||||
const reasonsList = this.dialog.getElement().querySelector(".import-error-reasons");
|
||||
if (reasonsList instanceof HTMLElement)
|
||||
reasonsList.innerHTML = `<li>${reasons.join("</li><li>")}</li>`;
|
||||
|
||||
this.dialog.show();
|
||||
}
|
||||
|
||||
#showForm() {
|
||||
this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
|
||||
el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
|
||||
});
|
||||
|
||||
const data: any = {};
|
||||
|
||||
for (const [group, units] of Object.entries(this.#fileData)) {
|
||||
@ -87,44 +132,11 @@ export class UnitDataFileImport extends UnitDataFile {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
groups.filter((unit:Unit) => unitCanBeImported(unit)).forEach((unit:Unit) => {
|
||||
const category = unit.getCategoryLabel();
|
||||
const coalition = unit.getCoalition();
|
||||
|
||||
if (!data.hasOwnProperty(category)) {
|
||||
data[category] = {};
|
||||
}
|
||||
|
||||
if (!data[category].hasOwnProperty(coalition))
|
||||
data[category][coalition] = [];
|
||||
|
||||
data[category][coalition].push(unit);
|
||||
});
|
||||
//*/
|
||||
this.data = data;
|
||||
this.buildCategoryCoalitionTable();
|
||||
this.dialog.show();
|
||||
}
|
||||
|
||||
selectFile() {
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.addEventListener("change", (e: any) => {
|
||||
var file = e.target.files[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
var reader = new FileReader();
|
||||
reader.onload = (e: any) => {
|
||||
this.#fileData = JSON.parse(e.target.result);
|
||||
this.#showForm();
|
||||
};
|
||||
reader.readAsText(file);
|
||||
})
|
||||
input.click();
|
||||
}
|
||||
|
||||
#unitDataCanBeImported(unitData: UnitData) {
|
||||
return unitData.alive && this.#unitGroupDataCanBeImported([unitData]);
|
||||
}
|
||||
|
||||
@ -1427,6 +1427,16 @@ export class UnitsManager {
|
||||
if (spawnPoints <= getApp().getMissionManager().getAvailableSpawnPoints()) {
|
||||
getApp().getMissionManager().setSpentSpawnPoints(spawnPoints);
|
||||
spawnFunction();
|
||||
document.dispatchEvent( new CustomEvent( "unitSpawned", {
|
||||
"detail": {
|
||||
"airbase": airbase,
|
||||
"category": category,
|
||||
"coalition": coalition,
|
||||
"country": country,
|
||||
"immediate": immediate,
|
||||
"unitSpawnTable": units
|
||||
}
|
||||
}));
|
||||
return true;
|
||||
} else {
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!");
|
||||
|
||||
@ -1,58 +1,71 @@
|
||||
<div id="map-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
|
||||
<div id="active-coalition-label" data-coalition="blue"></div>
|
||||
<div class="upper-bar ol-panel">
|
||||
<div class="switch-control coalition no-label"><div id="coalition-switch" class="ol-switch"></div></div>
|
||||
<button data-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "aircraft" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="helicopter-spawn-button" title="Spawn helicopter" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "helicopter" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/helicopter.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="air-defence-spawn-button" title="Spawn air defence unit" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "air-defence" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/sam.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="groundunit-spawn-button" title="Spawn ground unit" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "groundunit" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/groundunit.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="coalition-area-button" title="Edit coalition area" data-on-click="editCoalitionArea"
|
||||
class="ol-context-menu-button"><img src="/resources/theme/images/buttons/other/edit.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="more-options-button" title="More options" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "more" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/more.svg" inject-svg></button>
|
||||
<div id="spawn-mode-tabs">
|
||||
<button id="spawn-tab-history" class="ol-context-menu-button spawn-mode-tab"><img src="resources/theme/images/buttons/other/clock-rotate-left-solid.svg" inject-svg></button>
|
||||
<button id="spawn-tab-new" class="ol-context-menu-button spawn-mode-tab" data-show-label><img src="resources/theme/images/buttons/other/arrow-down-solid.svg" inject-svg></button>
|
||||
</div>
|
||||
<div id="more-options-button-bar" class="upper-bar ol-panel hide">
|
||||
<button data-coalition="blue" id="navyunit-spawn-button" title="Spawn navy unit" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "navyunit" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/navyunit.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "smoke" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/smoke.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="explosion-spawn-button" title="Explosion" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "explosion" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/explosion.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="polygon-draw-button" title="Enter polygon draw mode" data-on-click="toggleCoalitionAreaDraw"
|
||||
data-on-click-params='{"type": "polygon"}' class="ol-context-menu-button"><img src="resources/theme/images/buttons/tools/draw-polygon-solid.svg" inject-svg></button>
|
||||
<div class="spawn-mode ol-panel ol-context-menu-panel hide">
|
||||
<h4>Spawn history</h4>
|
||||
<div id="spawn-history-menu" class="ol-scrollable">
|
||||
<p>You do not have any units to show.</p>
|
||||
</div>
|
||||
<div id="aircraft-spawn-menu" class="ol-context-menu-panel ol-panel hide">
|
||||
<!-- Here the aircraft spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="helicopter-spawn-menu" class="ol-context-menu-panel ol-panel hide">
|
||||
<!-- Here the helicopter spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="air-defence-spawn-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<!-- Here the air defence units' spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="groundunit-spawn-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<!-- Here the ground units' spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="navyunit-spawn-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<!-- Here the navy units' spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="smoke-spawn-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<button class="smoke-button" title="" data-smoke-color="white" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "white" }'>White smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="blue" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "blue" }'>Blue smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="red" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "red" }'>Red smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="green" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "green" }'>Green smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="orange" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "orange" }'>Orange smoke</button>
|
||||
</div>
|
||||
<div id="explosion-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "normal", "strength": 1 }'>Small explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "normal", "strength": 10 }'>Big explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "phosphorous"}'>White phosphorous</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "napalm"}'>Napalm</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "secondary"}'>Explosion with debries</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "fire"}'>Static fire</button>
|
||||
<div class="spawn-mode">
|
||||
<div class="upper-bar ol-panel">
|
||||
<div class="switch-control coalition no-label"><div id="coalition-switch" class="ol-switch"></div></div>
|
||||
<button data-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "aircraft" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="helicopter-spawn-button" title="Spawn helicopter" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "helicopter" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/helicopter.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="air-defence-spawn-button" title="Spawn air defence unit" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "air-defence" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/sam.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="groundunit-spawn-button" title="Spawn ground unit" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "groundunit" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/groundunit.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="coalition-area-button" title="Edit coalition area" data-on-click="editCoalitionArea"
|
||||
class="ol-context-menu-button"><img src="/resources/theme/images/buttons/other/edit.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="more-options-button" title="More options" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "more" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/more.svg" inject-svg></button>
|
||||
|
||||
</div>
|
||||
<div id="more-options-button-bar" class="upper-bar ol-panel hide">
|
||||
<button data-coalition="blue" id="navyunit-spawn-button" title="Spawn navy unit" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "navyunit" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/navyunit.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "smoke" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/smoke.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="explosion-spawn-button" title="Explosion" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "explosion" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/explosion.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="polygon-draw-button" title="Enter polygon draw mode" data-on-click="toggleCoalitionAreaDraw"
|
||||
data-on-click-params='{"type": "polygon"}' class="ol-context-menu-button"><img src="resources/theme/images/buttons/tools/draw-polygon-solid.svg" inject-svg></button>
|
||||
</div>
|
||||
<div id="aircraft-spawn-menu" class="ol-context-menu-panel ol-panel hide">
|
||||
<!-- Here the aircraft spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="helicopter-spawn-menu" class="ol-context-menu-panel ol-panel hide">
|
||||
<!-- Here the helicopter spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="air-defence-spawn-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<!-- Here the air defence units' spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="groundunit-spawn-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<!-- Here the ground units' spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="navyunit-spawn-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<!-- Here the navy units' spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="smoke-spawn-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<button class="smoke-button" title="" data-smoke-color="white" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "white" }'>White smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="blue" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "blue" }'>Blue smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="red" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "red" }'>Red smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="green" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "green" }'>Green smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="orange" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "orange" }'>Orange smoke</button>
|
||||
</div>
|
||||
<div id="explosion-menu" class="ol-panel ol-context-menu-panel hide">
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "normal", "strength": 1 }'>Small explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "normal", "strength": 10 }'>Big explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "phosphorous"}'>White phosphorous</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "napalm"}'>Napalm</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "secondary"}'>Explosion with debries</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "fire"}'>Static fire</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -8,11 +8,13 @@
|
||||
<div class="ol-dialog-content">
|
||||
<div id="restrict-spawns" class="ol-checkbox">
|
||||
<label title="If false, no spawn restrictions will be applied">
|
||||
<input type="checkbox"/>
|
||||
Restrict spawns
|
||||
<input type="checkbox" data-on-click="toggleSpawnRestrictions"/>
|
||||
Enable spawn restrictions
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id="restrict-to-coalition" class="ol-checkbox">
|
||||
<label title="If true, commanders will be allowed to only spawn units that belong to their coalition. E.g. blue commanders will be able to spawn F/A-18 Hornets, but not MiG-29s.">
|
||||
<input type="checkbox"/>
|
||||
@ -25,7 +27,7 @@
|
||||
|
||||
<div class="ol-group">
|
||||
<div id="setup-time" class="ol-text-input">
|
||||
<input type="number" min="-99999" max="99999" step="1" value="10">
|
||||
<input type="number" min="-99999" max="99999" step="1" value="10" />
|
||||
</div>
|
||||
<label>minutes</label>
|
||||
</div>
|
||||
|
||||
@ -4,26 +4,40 @@
|
||||
</div>
|
||||
|
||||
<div class="ol-dialog-content">
|
||||
<p><%= textContent %></p>
|
||||
|
||||
<% if (showFilenameInput) { %>
|
||||
<div class="export-filename-container">
|
||||
<label>Filename:</label>
|
||||
<input id="export-filename">
|
||||
<img src="resources/theme/images/icons/keyboard-solid.svg">
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="import-form" data-on-error="hide">
|
||||
<p><%= textContent %></p>
|
||||
|
||||
<% if (showFilenameInput) { %>
|
||||
<div class="export-filename-container">
|
||||
<label>Filename:</label>
|
||||
<input id="export-filename">
|
||||
<img src="resources/theme/images/icons/keyboard-solid.svg">
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<table class="categories-coalitions">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table class="categories-coalitions">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="import-errors" data-on-error="show">
|
||||
|
||||
<div class="ol-dialog-footer ol-group">
|
||||
<button class="start-transfer"><%= submitButtonText %></button>
|
||||
<button data-on-click="closeDialog">Close</button>
|
||||
<p>Data could not be imported because:</p>
|
||||
|
||||
<ul class="import-error-reasons"></ul>
|
||||
|
||||
<div>Please correct the error(s) and run the import again.</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="ol-dialog-footer ol-group">
|
||||
<button class="start-transfer" data-on-error="hide"><%= submitButtonText %></button>
|
||||
<button data-on-click="closeDialog">Close</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
@ -0,0 +1,9 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
client:
|
||||
build: client
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- ./olympus.json:/usr/src/olympus.json
|
||||
Loading…
x
Reference in New Issue
Block a user