mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Add basic visibility functions and ability to spawn multiple units in group
This commit is contained in:
parent
30568e54f7
commit
327d5c74d9
@ -3,7 +3,6 @@ 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 airbasesRouter = require('./routes/api/airbases');
|
||||
@ -37,15 +36,7 @@ if (config["server"] != undefined)
|
||||
module.exports = app;
|
||||
|
||||
const DemoDataGenerator = require('./demo.js');
|
||||
var demoDataGenerator = new DemoDataGenerator(10);
|
||||
app.get('/demo/units', (req, res) => demoDataGenerator.units(req, res));
|
||||
app.get('/demo/logs', (req, res) => demoDataGenerator.logs(req, res));
|
||||
app.get('/demo/bullseyes', (req, res) => demoDataGenerator.bullseyes(req, res));
|
||||
app.get('/demo/airbases', (req, res) => demoDataGenerator.airbases(req, res));
|
||||
app.get('/demo/mission', (req, res) => demoDataGenerator.mission(req, res));
|
||||
|
||||
app.use('/demo', basicAuth({
|
||||
users: { 'admin': 'socks' }
|
||||
}))
|
||||
var demoDataGenerator = new DemoDataGenerator(app);
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
var basicAuth = require('express-basic-auth')
|
||||
var enc = new TextEncoder();
|
||||
|
||||
const DEMO_UNIT_DATA = {
|
||||
["1"]:{ alive: true, human: false, controlled: true, coalition: 2, country: 0, name: "KC-135", unitName: "Cool guy 1-1", groupName: "Cool group 1", state: 3, task: "Being cool!",
|
||||
["1"]:{ category: "Aircraft", alive: true, human: false, controlled: true, coalition: 2, country: 0, name: "KC-135", unitName: "Cool guy 1-1 who also has a very long name", groupName: "Cool group 1", state: 3, task: "Being cool!",
|
||||
hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, heading: 45, isTanker: true, isAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
@ -14,15 +15,15 @@ const DEMO_UNIT_DATA = {
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [],
|
||||
activePath: [ {lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0} ]
|
||||
contacts: [{ID: 2, detectionMethod: 1}],
|
||||
activePath: [{lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0}]
|
||||
},
|
||||
["2"]:{ alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "KC-135", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 36.9, lng: -116, alt: 1000 }, speed: 200, heading: 0, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
["2"]:{ category: "Aircraft", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "FA-18C_hornet", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 36.9, lng: -116, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 38, lng: -117, alt: 1000 },
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 2,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
@ -30,15 +31,57 @@ const DEMO_UNIT_DATA = {
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 1, detectionMethod: 4}],
|
||||
activePath: [ {lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0} ]
|
||||
contacts: [{ID: 1, detectionMethod: 16}],
|
||||
activePath: [ ]
|
||||
}, ["3"]:{ category: "GroundUnit", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "M-60", unitName: "Cool guy 1-3", groupName: "Cool group 3", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 2,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 1, detectionMethod: 16}],
|
||||
activePath: [ ]
|
||||
}, ["4"]:{ category: "GroundUnit", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "M-60", unitName: "Cool guy 1-4", groupName: "Cool group 3", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 37.1, lng: -116.1, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 2,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 1, detectionMethod: 16}],
|
||||
activePath: [ ]
|
||||
}
|
||||
}
|
||||
|
||||
class DemoDataGenerator {
|
||||
constructor()
|
||||
constructor(app)
|
||||
{
|
||||
|
||||
app.get('/demo/units', (req, res) => this.units(req, res));
|
||||
app.get('/demo/logs', (req, res) => this.logs(req, res));
|
||||
app.get('/demo/bullseyes', (req, res) => this.bullseyes(req, res));
|
||||
app.get('/demo/airbases', (req, res) => this.airbases(req, res));
|
||||
app.get('/demo/mission', (req, res) => this.mission(req, res));
|
||||
|
||||
app.use('/demo', basicAuth({
|
||||
users: {
|
||||
'admin': 'socks',
|
||||
'blue': 'bluesocks',
|
||||
'red': 'redsocks'
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
units(req, res){
|
||||
@ -48,7 +91,7 @@ class DemoDataGenerator {
|
||||
for (let idx in DEMO_UNIT_DATA) {
|
||||
const unit = DEMO_UNIT_DATA[idx];
|
||||
array = this.concat(array, this.uint32ToByteArray(idx));
|
||||
array = this.appendString(array, "Aircraft", 1);
|
||||
array = this.appendString(array, unit.category, 1);
|
||||
array = this.appendUint8(array, unit.alive, 2);
|
||||
array = this.appendUint8(array, unit.human, 3);
|
||||
array = this.appendUint8(array, unit.controlled, 4);
|
||||
@ -302,6 +345,21 @@ class DemoDataGenerator {
|
||||
mission(req, res){
|
||||
var ret = {mission: {theatre: "Nevada"}};
|
||||
ret.time = Date.now();
|
||||
var auth = req.get("Authorization");
|
||||
if (auth) {
|
||||
var username = atob(auth.replace("Basic ", "")).split(":")[0];
|
||||
switch (username) {
|
||||
case "admin":
|
||||
ret.mission.visibilityMode = "Game master";
|
||||
break
|
||||
case "blue":
|
||||
ret.mission.visibilityMode = "Blue commander";
|
||||
break;
|
||||
case "red":
|
||||
ret.mission.visibilityMode = "Red commander";
|
||||
break;
|
||||
}
|
||||
}
|
||||
res.send(JSON.stringify(ret));
|
||||
}
|
||||
|
||||
|
||||
@ -150,7 +150,7 @@
|
||||
1px -1px 0 #000,
|
||||
-1px 1px 0 #000,
|
||||
1px 1px 0 #000;
|
||||
translate: -60px 0;
|
||||
right: 100%;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@ -171,7 +171,7 @@
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-summary .unit-callsign:hover {
|
||||
[data-object|="unit"]:hover .unit-summary .unit-callsign{
|
||||
direction: rtl;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@ -812,16 +812,16 @@ nav.ol-panel> :last-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
#login-status {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#connection-status[data-status="connecting"]::before {
|
||||
#login-status[data-status="connecting"]::before {
|
||||
animation: blinker 1s linear infinite;
|
||||
content: "Connecting...";
|
||||
}
|
||||
|
||||
#connection-status[data-status="failed"]::before {
|
||||
#login-status[data-status="failed"]::before {
|
||||
color: var(--primary-red);
|
||||
content: "Incorrect username/password!";
|
||||
}
|
||||
@ -865,6 +865,19 @@ nav.ol-panel> :last-child {
|
||||
translate: 0% -300%;
|
||||
}
|
||||
|
||||
#visibiliy-mode {
|
||||
font-size: 14px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#visibiliy-mode[data-mode="Blue commander"] {
|
||||
color: var(--primary-blue);
|
||||
}
|
||||
|
||||
#visibiliy-mode[data-mode="Red commander"] {
|
||||
color: var(--primary-red);
|
||||
}
|
||||
|
||||
.ol-destination-preview-icon {
|
||||
background-image: url("/resources/theme/images/markers/move.svg");
|
||||
height: 52px;
|
||||
|
||||
3
client/src/@types/dom.d.ts
vendored
3
client/src/@types/dom.d.ts
vendored
@ -17,7 +17,8 @@ interface CustomEventMap {
|
||||
"groupCreation": CustomEvent<Unit[]>,
|
||||
"groupDeletion": CustomEvent<Unit[]>,
|
||||
"mapStateChanged": CustomEvent<string>,
|
||||
"mapContextMenu": CustomEvent<>
|
||||
"mapContextMenu": CustomEvent<>,
|
||||
"visibilityModeChanged": CustomEvent<string>,
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
5
client/src/@types/server.d.ts
vendored
5
client/src/@types/server.d.ts
vendored
@ -1,8 +1,3 @@
|
||||
interface UnitsData {
|
||||
units: string,
|
||||
sessionHash: string
|
||||
}
|
||||
|
||||
interface AirbasesData {
|
||||
airbases: {[key: string]: any},
|
||||
}
|
||||
|
||||
3
client/src/@types/unit.d.ts
vendored
3
client/src/@types/unit.d.ts
vendored
@ -8,7 +8,8 @@ interface UnitIconOptions {
|
||||
showShortLabel: boolean,
|
||||
showFuel: boolean,
|
||||
showAmmo: boolean,
|
||||
showSummary: boolean,
|
||||
showSummary: boolean,
|
||||
showCallsign: boolean,
|
||||
rotateToHeading: boolean
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,16 @@
|
||||
import { LatLng, LatLngBounds, TileLayer, tileLayer } from "leaflet";
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
|
||||
export const HIDE_ALL = "Hide all";
|
||||
export const GAME_MASTER = "Game master";
|
||||
export const BLUE_COMMANDER = "Blue commander";
|
||||
export const RED_COMMANDER = "Red commander";
|
||||
|
||||
export const VISUAL = 1;
|
||||
export const OPTIC = 2;
|
||||
export const RADAR = 4;
|
||||
export const IRST = 8;
|
||||
export const RWR = 16;
|
||||
export const DLINK = 32;
|
||||
|
||||
export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area"];
|
||||
export const ROEs: string[] = ["free", "designated", "return", "hold"];
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getActiveCoalition, getMap, setActiveCoalition } from "..";
|
||||
import { spawnAircraft, spawnExplosion, spawnGroundUnit, spawnSmoke } from "../server/server";
|
||||
import { spawnAircrafts, spawnExplosion, spawnGroundUnits, spawnSmoke } from "../server/server";
|
||||
import { aircraftDatabase } from "../units/aircraftdatabase";
|
||||
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
@ -9,17 +9,6 @@ import { Switch } from "./switch";
|
||||
import { Slider } from "./slider";
|
||||
import { ftToM } from "../other/utils";
|
||||
|
||||
export interface SpawnOptions {
|
||||
role: string;
|
||||
name: string;
|
||||
latlng: LatLng;
|
||||
coalition: string;
|
||||
loadout?: string | null;
|
||||
airbaseName?: string | null;
|
||||
altitude?: number | null;
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
export class MapContextMenu extends ContextMenu {
|
||||
#coalitionSwitch: Switch;
|
||||
#aircraftRoleDropdown: Dropdown;
|
||||
@ -28,7 +17,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
#aircrafSpawnAltitudeSlider: Slider;
|
||||
#groundUnitRoleDropdown: Dropdown;
|
||||
#groundUnitTypeDropdown: Dropdown;
|
||||
#spawnOptions: SpawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) };
|
||||
#spawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", loadout: "", airbaseName: "", altitude: ftToM(20000) };
|
||||
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
@ -57,8 +46,8 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.hide();
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions);
|
||||
spawnAircraft(this.#spawnOptions);
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
|
||||
spawnAircrafts([{unitName: this.#spawnOptions.name, latlng: this.#spawnOptions.latlng, loadout: this.#spawnOptions.loadout}], getActiveCoalition(), this.#spawnOptions.airbaseName, false);
|
||||
}
|
||||
});
|
||||
|
||||
@ -66,8 +55,8 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.hide();
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions);
|
||||
spawnGroundUnit(this.#spawnOptions);
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
|
||||
spawnGroundUnits([{unitName: this.#spawnOptions.name, latlng: this.#spawnOptions.latlng}], getActiveCoalition(), false);
|
||||
}
|
||||
});
|
||||
|
||||
@ -86,7 +75,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
}
|
||||
|
||||
show(x: number, y: number, latlng: LatLng) {
|
||||
this.#spawnOptions.airbaseName = null;
|
||||
this.#spawnOptions.airbaseName = "";
|
||||
super.show(x, y, latlng);
|
||||
this.#spawnOptions.latlng = latlng;
|
||||
this.showUpperBar();
|
||||
|
||||
@ -184,12 +184,12 @@ function setupEvents() {
|
||||
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));
|
||||
setCredentials(username, password);
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
startUpdate();
|
||||
|
||||
setConnectionStatus("connecting");
|
||||
setLoginStatus("connecting");
|
||||
})
|
||||
|
||||
document.addEventListener("reloadPage", () => {
|
||||
@ -259,8 +259,8 @@ export function getActiveCoalition() {
|
||||
return activeCoalition;
|
||||
}
|
||||
|
||||
export function setConnectionStatus(status: string) {
|
||||
const el = document.querySelector("#connection-status") as HTMLElement;
|
||||
export function setLoginStatus(status: string) {
|
||||
const el = document.querySelector("#login-status") as HTMLElement;
|
||||
if (el)
|
||||
el.dataset["status"] = status;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import * as L from "leaflet"
|
||||
import { getUnitsManager } from "..";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
import { MapContextMenu, SpawnOptions } from "../controls/mapcontextmenu";
|
||||
import { MapContextMenu } from "../controls/mapcontextmenu";
|
||||
import { UnitContextMenu } from "../controls/unitcontextmenu";
|
||||
import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
@ -390,8 +390,8 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
|
||||
addTemporaryMarker(spawnOptions: SpawnOptions) {
|
||||
var marker = new TemporaryUnitMarker(spawnOptions);
|
||||
addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string) {
|
||||
var marker = new TemporaryUnitMarker(latlng, name, coalition);
|
||||
marker.addTo(this);
|
||||
this.#temporaryMarkers.push(marker);
|
||||
}
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { SpawnOptions } from "../controls/mapcontextmenu";
|
||||
import { DivIcon } from "leaflet";
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
|
||||
|
||||
export class TemporaryUnitMarker extends CustomMarker {
|
||||
#spawnOptions: SpawnOptions;
|
||||
#name: string;
|
||||
#coalition: string;
|
||||
|
||||
constructor(spawnOptions: SpawnOptions) {
|
||||
super(spawnOptions.latlng, {interactive: false});
|
||||
this.#spawnOptions = spawnOptions;
|
||||
constructor(latlng: LatLng, name: string, coalition: string) {
|
||||
super(latlng, {interactive: false});
|
||||
this.#name = name;
|
||||
this.#coalition = coalition;
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
const category = getMarkerCategoryByName(this.#spawnOptions.name);
|
||||
const category = getMarkerCategoryByName(this.#name);
|
||||
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
@ -26,7 +27,7 @@ export class TemporaryUnitMarker extends CustomMarker {
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("unit");
|
||||
el.setAttribute("data-object", `unit-${category}`);
|
||||
el.setAttribute("data-coalition", this.#spawnOptions.coalition);
|
||||
el.setAttribute("data-coalition", this.#coalition);
|
||||
|
||||
// Main icon
|
||||
var unitIcon = document.createElement("div");
|
||||
@ -42,7 +43,7 @@ export class TemporaryUnitMarker extends CustomMarker {
|
||||
if (category == "aircraft" || category == "helicopter") {
|
||||
var shortLabel = document.createElement("div");
|
||||
shortLabel.classList.add("unit-short-label");
|
||||
shortLabel.innerText = getUnitDatabaseByCategory(category)?.getByName(this.#spawnOptions.name)?.shortLabel || "";
|
||||
shortLabel.innerText = getUnitDatabaseByCategory(category)?.getByName(this.#name)?.shortLabel || "";
|
||||
el.append(shortLabel);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getInfoPopup, getMap } from "..";
|
||||
import { getInfoPopup, getMap, getUnitsManager } from "..";
|
||||
import { Airbase } from "./airbase";
|
||||
import { Bullseye } from "./bullseye";
|
||||
|
||||
@ -72,6 +72,9 @@ export class MissionHandler {
|
||||
getInfoPopup().setText("Map set to " + this.#theatre);
|
||||
}
|
||||
|
||||
if ("visibilityMode" in data.mission)
|
||||
getUnitsManager().setVisibilityMode(data.mission.visibilityMode);
|
||||
|
||||
if ("date" in data.mission)
|
||||
this.#date = data.mission.date;
|
||||
if ("elapsedTime" in data.mission)
|
||||
|
||||
@ -35,6 +35,16 @@ export function distance(lat1: number, lon1: number, lat2: number, lon2: number)
|
||||
return d;
|
||||
}
|
||||
|
||||
export function coordinatesFromBearingAndDistance(lat1: number, lon1: number, brng: number, dist: number) {
|
||||
const R = 6371e3; // metres
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
const λ1 = deg2rad(lon1);
|
||||
const φ2 = Math.asin( Math.sin(φ1)*Math.cos(dist/R) + Math.cos(φ1)*Math.sin(dist/R)*Math.cos(brng) );
|
||||
const λ2 = λ1 + Math.atan2(Math.sin(brng)*Math.sin(dist/R)*Math.cos(φ1), Math.cos(dist/R)-Math.sin(φ1)*Math.sin(φ2));
|
||||
|
||||
return {lat: rad2deg(φ2), lng: rad2deg(λ2)};
|
||||
}
|
||||
|
||||
export function ConvertDDToDMS(D: number, lng: boolean) {
|
||||
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
|
||||
var deg = 0 | (D < 0 ? (D = -D) : D);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { LatLng } from 'leaflet';
|
||||
import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..';
|
||||
import { SpawnOptions } from '../controls/mapcontextmenu';
|
||||
import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setLoginStatus } from '..';
|
||||
import { GeneralSettings, Radio, TACAN } from '../@types/unit';
|
||||
import { ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
|
||||
|
||||
@ -16,7 +15,7 @@ const BULLSEYE_URI = "bullseyes";
|
||||
const MISSION_URI = "mission";
|
||||
|
||||
var username = "";
|
||||
var credentials = "";
|
||||
var password = "";
|
||||
|
||||
var sessionHash: string | null = null;
|
||||
var lastUpdateTime = 0;
|
||||
@ -26,12 +25,12 @@ export function toggleDemoEnabled() {
|
||||
demoEnabled = !demoEnabled;
|
||||
}
|
||||
|
||||
export function setCredentials(newUsername: string, newCredentials: string) {
|
||||
export function setCredentials(newUsername: string, newPassword: string) {
|
||||
username = newUsername;
|
||||
credentials = newCredentials;
|
||||
password = newPassword;
|
||||
}
|
||||
|
||||
export function GET(callback: CallableFunction, uri: string, options?: { time?: number }) {
|
||||
export function GET(callback: CallableFunction, uri: string, options?: { time?: number }, responseType?: string) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
|
||||
/* Assemble the request options string */
|
||||
@ -39,26 +38,31 @@ export function GET(callback: CallableFunction, uri: string, options?: { time?:
|
||||
if (options?.time != undefined)
|
||||
optionsString = `time=${options.time}`;
|
||||
|
||||
/* On the connection */
|
||||
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
|
||||
if (credentials)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
|
||||
|
||||
if (uri === UNITS_URI)
|
||||
xmlHttp.responseType = "arraybuffer";
|
||||
/* If provided, set the credentials */
|
||||
if (username && password)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${username}:${password}`));
|
||||
|
||||
/* If specified, set the response type */
|
||||
if (responseType)
|
||||
xmlHttp.responseType = responseType as XMLHttpRequestResponseType;
|
||||
|
||||
xmlHttp.onload = function (e) {
|
||||
if (xmlHttp.status == 200) {
|
||||
/* Success */
|
||||
setConnected(true);
|
||||
if (xmlHttp.responseType == 'arraybuffer')
|
||||
callback(xmlHttp.response);
|
||||
else {
|
||||
var data = JSON.parse(xmlHttp.responseText);
|
||||
callback(data);
|
||||
}
|
||||
else
|
||||
callback(JSON.parse(xmlHttp.responseText));
|
||||
} else if (xmlHttp.status == 401) {
|
||||
/* Bad credentials */
|
||||
console.error("Incorrect username/password");
|
||||
setConnectionStatus("failed");
|
||||
setLoginStatus("failed");
|
||||
} else {
|
||||
/* Failure, probably disconnected */
|
||||
setConnected(false);
|
||||
}
|
||||
};
|
||||
@ -73,8 +77,8 @@ export function POST(request: object, callback: CallableFunction) {
|
||||
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);
|
||||
if (username && password)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${username}:${password}`));
|
||||
xmlHttp.onreadystatechange = () => {
|
||||
callback();
|
||||
};
|
||||
@ -120,7 +124,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 }, 'arraybuffer');
|
||||
}
|
||||
|
||||
export function addDestination(ID: number, path: any) {
|
||||
@ -141,15 +145,15 @@ export function spawnExplosion(intensity: number, latlng: LatLng) {
|
||||
POST(data, () => { });
|
||||
}
|
||||
|
||||
export function spawnGroundUnit(spawnOptions: SpawnOptions) {
|
||||
var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "immediate": spawnOptions.immediate? true: false };
|
||||
var data = { "spawnGround": command }
|
||||
export function spawnGroundUnits(units: any, coalition: string, immediate: boolean) {
|
||||
var command = { "units": units, "coalition": coalition, "immediate": immediate };
|
||||
var data = { "spawnGroundUnits": command }
|
||||
POST(data, () => { });
|
||||
}
|
||||
|
||||
export function spawnAircraft(spawnOptions: SpawnOptions) {
|
||||
var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "", "immediate": spawnOptions.immediate? true: false };
|
||||
var data = { "spawnAir": command }
|
||||
export function spawnAircrafts(units: any, coalition: string, airbaseName: string, immediate: boolean) {
|
||||
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "immediate": immediate };
|
||||
var data = { "spawnAircrafts": command }
|
||||
POST(data, () => { });
|
||||
}
|
||||
|
||||
@ -319,11 +323,11 @@ export function startUpdate() {
|
||||
|
||||
export function requestUpdate() {
|
||||
/* Main update rate = 250ms is minimum time, equal to server update time. */
|
||||
getUnits((buffer: ArrayBuffer) => {
|
||||
if (!getPaused()) {
|
||||
if (!getPaused()) {
|
||||
getUnits((buffer: ArrayBuffer) => {
|
||||
getUnitsManager()?.update(buffer);
|
||||
}
|
||||
}, false);
|
||||
}, false);
|
||||
}
|
||||
window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
|
||||
|
||||
getConnectionStatusPanel()?.update(getConnected());
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet';
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet';
|
||||
import { getMap, getUnitsManager } from '..';
|
||||
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg } from '../other/utils';
|
||||
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, coordinatesFromBearingAndDistance, deg2rad } from '../other/utils';
|
||||
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
import { UnitDatabase } from './unitdatabase';
|
||||
import { TargetMarker } from '../map/targetmarker';
|
||||
import { BOMBING, CARPET_BOMBING, DataIndexes, FIRE_AT_AREA, IDLE, MOVE_UNIT, ROEs, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
|
||||
import { BLUE_COMMANDER, BOMBING, CARPET_BOMBING, DLINK, DataIndexes, FIRE_AT_AREA, HIDE_ALL, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, RED_COMMANDER, ROEs, RWR, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
|
||||
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, UnitIconOptions } from '../@types/unit';
|
||||
import { DataExtractor } from './dataextractor';
|
||||
|
||||
@ -88,6 +88,7 @@ export class Unit extends CustomMarker {
|
||||
#targetPositionPolyline: Polyline;
|
||||
#timer: number = 0;
|
||||
#hotgroup: number | null = null;
|
||||
#detectionMethods: number[] = [];
|
||||
|
||||
getAlive() {return this.#alive};
|
||||
getHuman() {return this.#human};
|
||||
@ -296,7 +297,8 @@ export class Unit extends CustomMarker {
|
||||
showShortLabel: false,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
showSummary: true,
|
||||
showCallsign: true,
|
||||
rotateToHeading: false
|
||||
}
|
||||
}
|
||||
@ -309,7 +311,7 @@ export class Unit extends CustomMarker {
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
/* Only alive units can be selected. Some units are not selectable (weapons) */
|
||||
if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
|
||||
if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected && this.belongsToCommandedCoalition()) {
|
||||
this.#selected = selected;
|
||||
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected);
|
||||
if (selected) {
|
||||
@ -362,6 +364,16 @@ export class Unit extends CustomMarker {
|
||||
return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.#groupName === this.#groupName; });
|
||||
}
|
||||
|
||||
belongsToCommandedCoalition() {
|
||||
if (getUnitsManager().getVisibilityMode() === HIDE_ALL)
|
||||
return false;
|
||||
if (getUnitsManager().getVisibilityMode() === BLUE_COMMANDER && this.#coalition !== "blue")
|
||||
return false;
|
||||
if (getUnitsManager().getVisibilityMode() === RED_COMMANDER && this.#coalition !== "red")
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/********************** Icon *************************/
|
||||
createIcon(): void {
|
||||
/* Set the icon */
|
||||
@ -453,7 +465,7 @@ export class Unit extends CustomMarker {
|
||||
altitude.classList.add("unit-altitude");
|
||||
var speed = document.createElement("div");
|
||||
speed.classList.add("unit-speed");
|
||||
summary.appendChild(callsign);
|
||||
if (this.getIconOptions().showCallsign) summary.appendChild(callsign);
|
||||
summary.appendChild(altitude);
|
||||
summary.appendChild(speed);
|
||||
el.appendChild(summary);
|
||||
@ -468,12 +480,17 @@ export class Unit extends CustomMarker {
|
||||
const hiddenUnits = getUnitsManager().getHiddenTypes();
|
||||
if (this.#human && hiddenUnits.includes("human"))
|
||||
hidden = true;
|
||||
else if (this.#controlled == false && hiddenUnits.includes("dcs"))
|
||||
if (this.#controlled == false && hiddenUnits.includes("dcs"))
|
||||
hidden = true;
|
||||
else if (hiddenUnits.includes(this.getMarkerCategory()))
|
||||
if (hiddenUnits.includes(this.getMarkerCategory()))
|
||||
hidden = true;
|
||||
else if (hiddenUnits.includes(this.#coalition))
|
||||
if (hiddenUnits.includes(this.#coalition))
|
||||
hidden = true;
|
||||
if (getUnitsManager().getVisibilityMode() === HIDE_ALL)
|
||||
hidden = true;
|
||||
if (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0) {
|
||||
hidden = true;
|
||||
}
|
||||
this.setHidden(hidden || !this.#alive);
|
||||
}
|
||||
|
||||
@ -495,6 +512,22 @@ export class Unit extends CustomMarker {
|
||||
return this.#hidden;
|
||||
}
|
||||
|
||||
setDetectionMethods(newDetectionMethods: number[]) {
|
||||
if (!this.belongsToCommandedCoalition()) {
|
||||
/* Check if the detection methods of this unit have changed */
|
||||
if (this.#detectionMethods.length !== newDetectionMethods.length || this.getDetectionMethods().some(value => !newDetectionMethods.includes(value))) {
|
||||
/* Force a redraw of the unit to reflect the new status of the detection methods */
|
||||
this.setHidden(true);
|
||||
this.#detectionMethods = newDetectionMethods;
|
||||
this.updateVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDetectionMethods() {
|
||||
return this.#detectionMethods;
|
||||
}
|
||||
|
||||
getLeader() {
|
||||
return getUnitsManager().getUnitByID(this.#leaderID);
|
||||
}
|
||||
@ -912,19 +945,28 @@ export class Unit extends CustomMarker {
|
||||
var contactData = this.#contacts[index];
|
||||
var contact = getUnitsManager().getUnitByID(contactData.ID)
|
||||
if (contact != null) {
|
||||
var startLatLng = new LatLng(this.#position.lat, this.#position.lng)
|
||||
var endLatLng = new LatLng(contact.#position.lat, contact.#position.lng)
|
||||
var startLatLng = new LatLng(this.#position.lat, this.#position.lng);
|
||||
var endLatLng: LatLng;
|
||||
if (contactData.detectionMethod === RWR) {
|
||||
var bearingToContact = bearing(this.#position.lat, this.#position.lng, contact.#position.lat, contact.#position.lng);
|
||||
var startXY = getMap().latLngToContainerPoint(startLatLng);
|
||||
var endX = startXY.x + 80 * Math.sin(deg2rad(bearingToContact));
|
||||
var endY = startXY.y - 80 * Math.cos(deg2rad(bearingToContact));
|
||||
endLatLng = getMap().containerPointToLatLng(new Point(endX, endY));
|
||||
}
|
||||
else
|
||||
endLatLng = new LatLng(contact.#position.lat, contact.#position.lng);
|
||||
|
||||
var color;
|
||||
if (contactData.detectionMethod === 1)
|
||||
if (contactData.detectionMethod === VISUAL || contactData.detectionMethod === OPTIC)
|
||||
color = "#FF00FF";
|
||||
else if (contactData.detectionMethod === 4)
|
||||
else if (contactData.detectionMethod === RADAR || contactData.detectionMethod === IRST)
|
||||
color = "#FFFF00";
|
||||
else if (contactData.detectionMethod === 16)
|
||||
else if (contactData.detectionMethod === RWR)
|
||||
color = "#00FF00";
|
||||
else
|
||||
color = "#FFFFFF";
|
||||
var contactPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" });
|
||||
var contactPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 1, smoothFactor: 1, dashArray: "4, 8" });
|
||||
contactPolyline.addTo(getMap());
|
||||
this.#contactsPolylines.push(contactPolyline)
|
||||
}
|
||||
@ -941,10 +983,11 @@ export class Unit extends CustomMarker {
|
||||
if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0) {
|
||||
this.#drawtargetPosition(this.#targetPosition);
|
||||
}
|
||||
else if (this.#targetID != 0 && getUnitsManager().getUnitByID(this.#targetID)) {
|
||||
const position = getUnitsManager().getUnitByID(this.#targetID)?.getPosition();
|
||||
if (position)
|
||||
this.#drawtargetPosition(position);
|
||||
else if (this.#targetID != 0) {
|
||||
const target = getUnitsManager().getUnitByID(this.#targetID);
|
||||
if (target && getUnitsManager().getUnitDetectedMethods(target).some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))) {
|
||||
this.#drawtargetPosition(target.getPosition());
|
||||
}
|
||||
}
|
||||
else
|
||||
this.#clearTarget();
|
||||
@ -971,14 +1014,15 @@ export class Unit extends CustomMarker {
|
||||
export class AirUnit extends Unit {
|
||||
getIconOptions() {
|
||||
return {
|
||||
showState: true,
|
||||
showVvi: true,
|
||||
showHotgroup: true,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: true,
|
||||
showFuel: true,
|
||||
showAmmo: true,
|
||||
showSummary: true,
|
||||
showState: this.belongsToCommandedCoalition(),
|
||||
showVvi: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showHotgroup: this.belongsToCommandedCoalition(),
|
||||
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showShortLabel: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))),
|
||||
showFuel: this.belongsToCommandedCoalition(),
|
||||
showAmmo: this.belongsToCommandedCoalition(),
|
||||
showSummary: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showCallsign: this.belongsToCommandedCoalition(),
|
||||
rotateToHeading: false
|
||||
};
|
||||
}
|
||||
@ -1011,14 +1055,15 @@ export class GroundUnit extends Unit {
|
||||
|
||||
getIconOptions() {
|
||||
return {
|
||||
showState: true,
|
||||
showState: this.belongsToCommandedCoalition(),
|
||||
showVvi: false,
|
||||
showHotgroup: true,
|
||||
showUnitIcon: true,
|
||||
showHotgroup: this.belongsToCommandedCoalition(),
|
||||
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showShortLabel: false,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
showCallsign: this.belongsToCommandedCoalition(),
|
||||
rotateToHeading: false
|
||||
};
|
||||
}
|
||||
@ -1035,14 +1080,15 @@ export class NavyUnit extends Unit {
|
||||
|
||||
getIconOptions() {
|
||||
return {
|
||||
showState: true,
|
||||
showState: this.belongsToCommandedCoalition(),
|
||||
showVvi: false,
|
||||
showHotgroup: true,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: true,
|
||||
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showShortLabel: false,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
showCallsign: this.belongsToCommandedCoalition(),
|
||||
rotateToHeading: false
|
||||
};
|
||||
}
|
||||
@ -1063,11 +1109,12 @@ export class Weapon extends Unit {
|
||||
showState: false,
|
||||
showVvi: false,
|
||||
showHotgroup: false,
|
||||
showUnitIcon: true,
|
||||
showUnitIcon: this.belongsToCommandedCoalition(),
|
||||
showShortLabel: false,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
showCallsign: false,
|
||||
rotateToHeading: true
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler } from "..";
|
||||
import { Unit } from "./unit";
|
||||
import { cloneUnit, setLastUpdateTime, spawnGroundUnit } from "../server/server";
|
||||
import { cloneUnit, setLastUpdateTime, spawnGroundUnits } from "../server/server";
|
||||
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils";
|
||||
import { CoalitionArea } from "../map/coalitionarea";
|
||||
import { Airbase } from "../missionhandler/airbase";
|
||||
import { groundUnitsDatabase } from "./groundunitsdatabase";
|
||||
import { DataIndexes, IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants";
|
||||
import { DataIndexes, HIDE_ALL, IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants";
|
||||
import { DataExtractor } from "./dataextractor";
|
||||
import { Contact } from "../@types/unit";
|
||||
|
||||
export class UnitsManager {
|
||||
#units: { [ID: number]: Unit };
|
||||
@ -15,6 +16,7 @@ export class UnitsManager {
|
||||
#selectionEventDisabled: boolean = false;
|
||||
#pasteDisabled: boolean = false;
|
||||
#hiddenTypes: string[] = [];
|
||||
#visibilityMode: string = HIDE_ALL;
|
||||
|
||||
constructor() {
|
||||
this.#units = {};
|
||||
@ -27,6 +29,8 @@ export class UnitsManager {
|
||||
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete());
|
||||
document.addEventListener('explodeSelectedUnits', () => this.selectedUnitsDelete(true));
|
||||
document.addEventListener('keyup', (event) => this.#onKeyUp(event));
|
||||
document.addEventListener('exportToFile', () => this.exportToFile());
|
||||
document.addEventListener('importFromFile', () => this.importFromFile());
|
||||
}
|
||||
|
||||
getSelectableAircraft() {
|
||||
@ -87,6 +91,12 @@ export class UnitsManager {
|
||||
this.#units[ID]?.setData(dataExtractor);
|
||||
}
|
||||
|
||||
for (let ID in this.#units) {
|
||||
var unit = this.#units[ID];
|
||||
if (!unit.belongsToCommandedCoalition())
|
||||
unit.setDetectionMethods(this.getUnitDetectedMethods(unit));
|
||||
}
|
||||
|
||||
setLastUpdateTime(updateTime);
|
||||
}
|
||||
|
||||
@ -104,6 +114,24 @@ export class UnitsManager {
|
||||
return this.#hiddenTypes;
|
||||
}
|
||||
|
||||
setVisibilityMode(newVisibilityMode: string) {
|
||||
if (newVisibilityMode !== this.#visibilityMode) {
|
||||
document.dispatchEvent(new CustomEvent("visibilityModeChanged", { detail: this }));
|
||||
const el = document.getElementById("visibiliy-mode");
|
||||
if (el) {
|
||||
el.dataset.mode = newVisibilityMode;
|
||||
el.textContent = newVisibilityMode.toUpperCase();
|
||||
}
|
||||
this.#visibilityMode = newVisibilityMode;
|
||||
for (let ID in this.#units)
|
||||
this.#units[ID].updateVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
getVisibilityMode() {
|
||||
return this.#visibilityMode;
|
||||
}
|
||||
|
||||
selectUnit(ID: number, deselectAllUnits: boolean = true) {
|
||||
if (deselectAllUnits)
|
||||
this.getSelectedUnits().filter((unit: Unit) => unit.ID !== ID).forEach((unit: Unit) => unit.setSelected(false));
|
||||
@ -479,6 +507,20 @@ export class UnitsManager {
|
||||
this.#showActionMessage(selectedUnits, `unit bombing point`);
|
||||
}
|
||||
|
||||
getUnitDetectedMethods(unit: Unit) {
|
||||
var detectionMethods: number[] = [];
|
||||
for (let idx in this.#units) {
|
||||
if (this.#units[idx].getCoalition() !== "neutral" && this.#units[idx].getCoalition() != unit.getCoalition())
|
||||
{
|
||||
this.#units[idx].getContacts().forEach((contact: Contact) => {
|
||||
if (contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod))
|
||||
detectionMethods.push(contact.detectionMethod);
|
||||
});
|
||||
}
|
||||
}
|
||||
return detectionMethods;
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
copyUnits() {
|
||||
this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
|
||||
@ -516,13 +558,49 @@ export class UnitsManager {
|
||||
const probability = Math.pow(1 - minDistance / 50e3, 5) * IADSRoles[role];
|
||||
if (Math.random() < probability){
|
||||
const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role);
|
||||
const spawnOptions = {role: role, latlng: latlng, name: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true};
|
||||
spawnGroundUnit(spawnOptions);
|
||||
getMap().addTemporaryMarker(spawnOptions);
|
||||
spawnGroundUnits([{unitName: unitBlueprint.name, latlng: latlng}], coalitionArea.getCoalition(), true);
|
||||
getMap().addTemporaryMarker(latlng, unitBlueprint.name, coalitionArea.getCoalition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exportToFile() {
|
||||
var unitsToExport: {[key: string]: any} = {};
|
||||
for (let ID in this.#units) {
|
||||
var unit = this.#units[ID];
|
||||
if (!["Aircraft", "Helicopter"].includes(unit.getCategory())) {
|
||||
if (unit.getGroupName() in unitsToExport)
|
||||
unitsToExport[unit.getGroupName()].push(unit.getData());
|
||||
else
|
||||
unitsToExport[unit.getGroupName()] = [unit.getData()];
|
||||
}
|
||||
}
|
||||
var a = document.createElement("a");
|
||||
var file = new Blob([JSON.stringify(unitsToExport)], {type: 'text/plain'});
|
||||
a.href = URL.createObjectURL(file);
|
||||
a.download = 'export.json';
|
||||
a.click();
|
||||
}
|
||||
|
||||
importFromFile() {
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.addEventListener("change", (e: any) => {
|
||||
var file = e.target.files[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e: any) {
|
||||
var contents = e.target.result;
|
||||
var groups = JSON.parse(contents);
|
||||
|
||||
};
|
||||
reader.readAsText(file);
|
||||
})
|
||||
input.click();
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onKeyUp(event: KeyboardEvent) {
|
||||
if (!keyEventWasInInput(event) && event.key === "Delete" ) {
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<button id="connection-button" class="ol-button-apply" data-on-click="tryConnection">Connect</button>
|
||||
</div>
|
||||
|
||||
<h5 id="connection-status"><br></h5>
|
||||
<h5 id="login-status"><br></h5>
|
||||
|
||||
<div id="legal-stuff">
|
||||
<h5>DISCLAIMER</h5>
|
||||
|
||||
@ -14,12 +14,20 @@
|
||||
<div>
|
||||
<a href="https://github.com/Pax1601/DCSOlympus" target="_blank">Github</a>
|
||||
</div>
|
||||
<div data-on-click="exportToFile">
|
||||
<button>Export to file</button>
|
||||
</div>
|
||||
<div data-on-click="importFromFile">
|
||||
<button>Import from file</button>
|
||||
</div>
|
||||
<div data-on-click="reloadPage">
|
||||
<a href="" target="_blank" data-on-click="reloadPage">Restart Olympus</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span id="visibiliy-mode"></span>
|
||||
|
||||
<div id="map-type" class="ol-select">
|
||||
<div class="ol-select-value map-source-dropdown">
|
||||
<span>ArcGIS Satellite</span>
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
"port": 30000
|
||||
},
|
||||
"authentication": {
|
||||
"password": "password"
|
||||
"gameMasterPassword": "password",
|
||||
"blueCommanderPassword": "bluepassword",
|
||||
"redCommanderPassword": "redpassword"
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,105 +303,105 @@ function Olympus.explosion(intensity, lat, lng)
|
||||
trigger.action.explosion(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)), intensity)
|
||||
end
|
||||
|
||||
-- Spawns a single ground unit
|
||||
function Olympus.spawnGroundUnit(coalition, unitType, lat, lng)
|
||||
Olympus.debug("Olympus.spawnGroundUnit " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..")", 2)
|
||||
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
|
||||
-- Spawns a new unit or group
|
||||
function Olympus.spawnUnits(spawnTable)
|
||||
Olympus.debug("Olympus.spawnUnits " .. serializeTable(spawnTable), 2)
|
||||
|
||||
local unitTable = {}
|
||||
local route = {}
|
||||
local category = nil
|
||||
|
||||
if Olympus.hasKey(templates, unitType) then
|
||||
for idx, value in pairs(templates[unitType].units) do
|
||||
unitTable[#unitTable + 1] = {
|
||||
["type"] = value.name,
|
||||
["x"] = spawnLocation.x + value.dx,
|
||||
["y"] = spawnLocation.z + value.dy,
|
||||
["playerCanDrive"] = true,
|
||||
["heading"] = 0,
|
||||
["skill"] = "High"
|
||||
}
|
||||
end
|
||||
else
|
||||
unitTable =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["type"] = unitType,
|
||||
["x"] = spawnLocation.x,
|
||||
["y"] = spawnLocation.z,
|
||||
["playerCanDrive"] = true,
|
||||
["heading"] = 0,
|
||||
["skill"] = "High"
|
||||
},
|
||||
}
|
||||
if spawnTable.category == 'Aircraft' then
|
||||
unitTable = Olympus.generateAirUnitsTable(spawnTable.units)
|
||||
route = Olympus.generateAirUnitsRoute(spawnTable)
|
||||
category = 'airplane'
|
||||
elseif spawnTable.category == 'GroundUnit' then
|
||||
unitTable = Olympus.generateGroundUnitsTable(spawnTable.units)
|
||||
category = 'vehicle'
|
||||
end
|
||||
|
||||
local countryID = Olympus.getCountryIDByCoalition(coalition)
|
||||
|
||||
local countryID = Olympus.getCountryIDByCoalition(spawnTable.coalition)
|
||||
local vars =
|
||||
{
|
||||
units = unitTable,
|
||||
country = countryID,
|
||||
category = 'vehicle',
|
||||
name = "Ground-" .. Olympus.unitCounter,
|
||||
category = category,
|
||||
route = route,
|
||||
name = "Olympus-" .. Olympus.unitCounter,
|
||||
}
|
||||
mist.dynAdd(vars)
|
||||
|
||||
Olympus.unitCounter = Olympus.unitCounter + 1
|
||||
Olympus.debug("Olympus.spawnGround completed succesfully", 2)
|
||||
end
|
||||
Olympus.debug("Olympus.spawnUnits completed succesfully", 2)
|
||||
end
|
||||
|
||||
-- Spawns a single aircraft. Spawn options are:
|
||||
-- payloadName: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType
|
||||
-- airbaseName: a string, if present the aircraft will spawn on the ground of the selected airbase
|
||||
-- payload: a table, if present the unit will receive this specific payload. Overrides payloadName
|
||||
function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions)
|
||||
local payloadName = spawnOptions["payloadName"]
|
||||
local airbaseName = spawnOptions["airbaseName"]
|
||||
local payload = spawnOptions["payload"]
|
||||
|
||||
Olympus.debug("Olympus.spawnAircraft " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..", " .. alt .. ")", 2)
|
||||
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
|
||||
|
||||
if payload == nil then
|
||||
if payloadName and payloadName ~= "" and Olympus.unitPayloads[unitType][payloadName] then
|
||||
payload = Olympus.unitPayloads[unitType][payloadName]
|
||||
-- Generates ground units table, either single or from template
|
||||
function Olympus.generateGroundUnitsTable(units)
|
||||
for idx, unit in pairs(units) do
|
||||
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
|
||||
local unitTable = {}
|
||||
if Olympus.hasKey(templates, unit.unitType) then
|
||||
for idx, value in pairs(templates[unit.unitType].units) do
|
||||
unitTable[#unitTable + 1] = {
|
||||
["type"] = value.name,
|
||||
["x"] = spawnLocation.x + value.dx,
|
||||
["y"] = spawnLocation.z + value.dy,
|
||||
["playerCanDrive"] = true,
|
||||
["heading"] = 0,
|
||||
["skill"] = "High"
|
||||
}
|
||||
end
|
||||
else
|
||||
payload = {}
|
||||
unitTable[#unitTable + 1] =
|
||||
{
|
||||
["type"] = unit.unitType,
|
||||
["x"] = unit.x,
|
||||
["y"] = unit.z,
|
||||
["playerCanDrive"] = true,
|
||||
["heading"] = 0,
|
||||
["skill"] = "High"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local countryID = Olympus.getCountryIDByCoalition(coalition)
|
||||
|
||||
local unitTable =
|
||||
{
|
||||
[1] =
|
||||
return unitTable
|
||||
end
|
||||
|
||||
-- Generates unit table for a air unit.
|
||||
function Olympus.generateAirUnitsTable(units)
|
||||
local unitTable = {}
|
||||
for idx, unit in pairs(units) do
|
||||
local payloadName = unit.payloadName -- payloadName: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType
|
||||
local payload = unit.payload -- payload: a table, if present the unit will receive this specific payload. Overrides payloadName
|
||||
|
||||
if payload == nil then
|
||||
if payloadName and payloadName ~= "" and Olympus.unitPayloads[unit.unitType][payloadName] then
|
||||
payload = Olympus.unitPayloads[unit.unitType][payloadName]
|
||||
else
|
||||
payload = {}
|
||||
end
|
||||
end
|
||||
|
||||
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
|
||||
unitTable[#unitTable + 1] =
|
||||
{
|
||||
["type"] = unitType,
|
||||
["type"] = unit.unitType,
|
||||
["x"] = spawnLocation.x,
|
||||
["y"] = spawnLocation.z,
|
||||
["alt"] = alt,
|
||||
["alt_type"] = "BARO",
|
||||
["alt"] = unit.alt,
|
||||
["alt_type"] = "BARO",
|
||||
["skill"] = "Excellent",
|
||||
["payload"] =
|
||||
{
|
||||
["pylons"] = payload,
|
||||
["fuel"] = 999999,
|
||||
["flare"] = 60,
|
||||
["ammo_type"] = 1,
|
||||
["chaff"] = 60,
|
||||
["gun"] = 100,
|
||||
},
|
||||
["payload"] = { ["pylons"] = payload, ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100, },
|
||||
["heading"] = 0,
|
||||
["callsign"] =
|
||||
{
|
||||
[1] = 1,
|
||||
[2] = 1,
|
||||
[3] = 1,
|
||||
["name"] = "Olympus" .. Olympus.unitCounter,
|
||||
},
|
||||
["name"] = "Olympus-" .. Olympus.unitCounter
|
||||
},
|
||||
}
|
||||
["callsign"] = { [1] = 1, [2] = 1, [3] = 1, ["name"] = "Olympus" .. Olympus.unitCounter.. "-" .. #unitTable + 1 },
|
||||
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1
|
||||
}
|
||||
end
|
||||
return unitTable
|
||||
|
||||
function Olympus.generateAirUnitsRoute(spawnTable)
|
||||
local airbaseName = spawnTable.airbaseName -- airbaseName: a string, if present the aircraft will spawn on the ground of the selected airbase
|
||||
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(spawnTable.lat, spawnTable.lng, 0))
|
||||
|
||||
-- If a airbase is provided the first waypoint is set as a From runway takeoff.
|
||||
local route = {}
|
||||
@ -416,10 +416,9 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions)
|
||||
[1] =
|
||||
{
|
||||
["action"] = "From Parking Area Hot",
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] = {["tasks"] = {},},
|
||||
["tasks"] = {
|
||||
[1] = {["number"] = 1, ["auto"] = true, ["id"] = "WrappedAction", ["enabled"] = true, ["params"] = {["action"] = {["id"] = "EPLRS", ["params"] = {["value"] = true}, }, }, },
|
||||
[2] = {["number"] = 2, ["auto"] = false, ["id"] = "Orbit", ["enabled"] = true, ["params"] = {["pattern"] = "Circle"}, },
|
||||
},
|
||||
["type"] = "TakeOffParkingHot",
|
||||
["ETA"] = 0,
|
||||
@ -442,69 +441,18 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions)
|
||||
{
|
||||
["alt"] = alt,
|
||||
["alt_type"] = "BARO",
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] =
|
||||
{
|
||||
["tasks"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["number"] = 1,
|
||||
["auto"] = true,
|
||||
["id"] = "WrappedAction",
|
||||
["enabled"] = true,
|
||||
["params"] =
|
||||
{
|
||||
["action"] =
|
||||
{
|
||||
["id"] = "EPLRS",
|
||||
["params"] =
|
||||
{
|
||||
["value"] = true
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[2] =
|
||||
{
|
||||
["number"] = 2,
|
||||
["auto"] = false,
|
||||
["id"] = "Orbit",
|
||||
["enabled"] = true,
|
||||
["params"] =
|
||||
{
|
||||
["pattern"] = "Circle"
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = {["number"] = 1, ["auto"] = true, ["id"] = "WrappedAction", ["enabled"] = true, ["params"] = {["action"] = {["id"] = "EPLRS", ["params"] = {["value"] = true}, }, }, },
|
||||
[2] = {["number"] = 2, ["auto"] = false, ["id"] = "Orbit", ["enabled"] = true, ["params"] = {["pattern"] = "Circle"}, },
|
||||
},
|
||||
["type"] = "Turning Point",
|
||||
["x"] = spawnLocation.x,
|
||||
["y"] = spawnLocation.z,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["points"]
|
||||
} -- end of ["route"]
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
local vars =
|
||||
{
|
||||
units = unitTable,
|
||||
country = countryID,
|
||||
category = 'airplane',
|
||||
name = "Olympus-" .. Olympus.unitCounter,
|
||||
route = route,
|
||||
task = 'CAP',
|
||||
}
|
||||
|
||||
local newGroup = mist.dynAdd(vars)
|
||||
|
||||
-- Save the payload to be reused in case the unit is cloned. TODO: save by ID not by name (it works but I like consistency)
|
||||
Olympus.payloadRegistry[vars.name] = payload
|
||||
Olympus.unitCounter = Olympus.unitCounter + 1
|
||||
Olympus.debug("Olympus.spawnAir completed successfully", 2)
|
||||
return route
|
||||
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.
|
||||
@ -513,15 +461,21 @@ function Olympus.clone(ID, lat, lng, category)
|
||||
local unit = Olympus.getUnitByID(ID)
|
||||
if unit then
|
||||
local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition())
|
||||
|
||||
if category == "Aircraft" then
|
||||
local spawnOptions = {
|
||||
payload = Olympus.payloadRegistry[unit:getName()]
|
||||
-- TODO: understand category in this script
|
||||
local spawnTable = {
|
||||
coalition = coalition,
|
||||
category = category,
|
||||
units = {
|
||||
[1] = {
|
||||
lat = lat,
|
||||
lng = lng,
|
||||
alt = unit:getPoint().y,
|
||||
unitType = unit:getTypeName(),
|
||||
payload = Olympus.payloadRegistry[unit:getName()]
|
||||
}
|
||||
}
|
||||
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, unit:getPoint().y, spawnOptions)
|
||||
elseif category == "GroundUnit" then
|
||||
Olympus.spawnGroundUnit(coalition, unit:getTypeName(), lat, lng)
|
||||
end
|
||||
}
|
||||
Olympus.spawnUnits(spawnTable)
|
||||
end
|
||||
Olympus.debug("Olympus.clone completed successfully", 2)
|
||||
end
|
||||
@ -765,4 +719,4 @@ end
|
||||
|
||||
timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1)
|
||||
|
||||
Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true)
|
||||
Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true)
|
||||
|
||||
@ -151,13 +151,13 @@ private:
|
||||
};
|
||||
|
||||
/* Spawn ground unit command */
|
||||
class SpawnGroundUnit : public Command
|
||||
class SpawnGroundUnits : public Command
|
||||
{
|
||||
public:
|
||||
SpawnGroundUnit(string coalition, string unitType, Coords location, bool immediate) :
|
||||
SpawnGroundUnits(string coalition, vector<string> unitTypes, vector<Coords> locations, bool immediate) :
|
||||
coalition(coalition),
|
||||
unitType(unitType),
|
||||
location(location),
|
||||
unitTypes(unitTypes),
|
||||
locations(locations),
|
||||
immediate(immediate)
|
||||
{
|
||||
priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW;
|
||||
@ -167,20 +167,20 @@ public:
|
||||
|
||||
private:
|
||||
const string coalition;
|
||||
const string unitType;
|
||||
const Coords location;
|
||||
const vector<string> unitTypes;
|
||||
const vector<Coords> locations;
|
||||
const bool immediate;
|
||||
};
|
||||
|
||||
/* Spawn air unit command */
|
||||
class SpawnAircraft : public Command
|
||||
class SpawnAircrafts : public Command
|
||||
{
|
||||
public:
|
||||
SpawnAircraft(string coalition, string unitType, Coords location, string payloadName, string airbaseName, bool immediate) :
|
||||
SpawnAircrafts(string coalition, vector<string> unitTypes, vector<Coords> locations, vector<string> payloadNames, string airbaseName, bool immediate) :
|
||||
coalition(coalition),
|
||||
unitType(unitType),
|
||||
location(location),
|
||||
payloadName(payloadName),
|
||||
unitTypes(unitTypes),
|
||||
locations(locations),
|
||||
payloadNames(payloadNames),
|
||||
airbaseName(airbaseName),
|
||||
immediate(immediate)
|
||||
{
|
||||
@ -191,9 +191,9 @@ public:
|
||||
|
||||
private:
|
||||
const string coalition;
|
||||
const string unitType;
|
||||
const Coords location;
|
||||
const string payloadName;
|
||||
const vector<string> unitTypes;
|
||||
const vector<Coords> locations;
|
||||
const vector<string> payloadNames;
|
||||
const string airbaseName;
|
||||
const bool immediate;
|
||||
};
|
||||
|
||||
@ -24,10 +24,16 @@ private:
|
||||
void handle_request(http_request request, function<void(json::value const&, json::value&)> action);
|
||||
void handle_put(http_request request);
|
||||
|
||||
string extractPassword(http_request& request);
|
||||
|
||||
void task();
|
||||
|
||||
atomic<bool> runListener;
|
||||
|
||||
string password = "";
|
||||
string gameMasterPassword = "";
|
||||
string blueCommanderPassword = "";
|
||||
string redCommanderPassword = "";
|
||||
string atcPassword = "";
|
||||
string observerPassword = "";
|
||||
};
|
||||
|
||||
|
||||
@ -37,38 +37,52 @@ string Smoke::getString(lua_State* L)
|
||||
return commandSS.str();
|
||||
}
|
||||
|
||||
/* Spawn ground command */
|
||||
string SpawnGroundUnit::getString(lua_State* L)
|
||||
/* Spawn ground units command */
|
||||
string SpawnGroundUnits::getString(lua_State* L)
|
||||
{
|
||||
if (unitTypes.size() != locations.size()) return "";
|
||||
|
||||
std::ostringstream unitsSS;
|
||||
unitsSS.precision(10);
|
||||
for (int i = 0; i < unitTypes.size(); i++) {
|
||||
unitsSS << "[" << i + 1 << "] = {"
|
||||
<< "unitType = " << "\"" << unitTypes[i] << "\"" << ", "
|
||||
<< "lat = " << locations[i].lat << ", "
|
||||
<< "lng = " << locations[i].lng << "}";
|
||||
}
|
||||
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.spawnGroundUnit, "
|
||||
<< "\"" << coalition << "\"" << ", "
|
||||
<< "\"" << unitType << "\"" << ", "
|
||||
<< location.lat << ", "
|
||||
<< location.lng;
|
||||
commandSS << "Olympus.spawnUnits, {"
|
||||
<< "category = " << "\"" << "GroundUnit" << "\"" << ", "
|
||||
<< "coalition = " << "\"" << coalition << "\"" << ", "
|
||||
<< "units = " << "\"" << unitsSS.str() << "\"" << "}";
|
||||
return commandSS.str();
|
||||
}
|
||||
|
||||
/* Spawn air command */
|
||||
string SpawnAircraft::getString(lua_State* L)
|
||||
/* Spawn aircrafts command */
|
||||
string SpawnAircrafts::getString(lua_State* L)
|
||||
{
|
||||
std::ostringstream optionsSS;
|
||||
optionsSS.precision(10);
|
||||
optionsSS << "{"
|
||||
<< "payloadName = \"" << payloadName << "\", "
|
||||
<< "airbaseName = \"" << airbaseName << "\", "
|
||||
<< "}";
|
||||
if (unitTypes.size() != locations.size() || unitTypes.size() != payloadNames.size()) return "";
|
||||
|
||||
std::ostringstream unitsSS;
|
||||
unitsSS.precision(10);
|
||||
for (int i = 0; i < unitTypes.size(); i++) {
|
||||
unitsSS << "[" << i + 1 << "] = {"
|
||||
<< "unitType = " << "\"" << unitTypes[i] << "\"" << ", "
|
||||
<< "lat = " << locations[i].lat << ", "
|
||||
<< "lng = " << locations[i].lng << ", "
|
||||
<< "alt = " << locations[i].alt << ", "
|
||||
<< "payloadName = \"" << payloadNames[i] << "\", " << "}";
|
||||
}
|
||||
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.spawnAircraft, "
|
||||
<< "\"" << coalition << "\"" << ", "
|
||||
<< "\"" << unitType << "\"" << ", "
|
||||
<< location.lat << ", "
|
||||
<< location.lng << ", "
|
||||
<< location.alt << ", "
|
||||
<< optionsSS.str();
|
||||
commandSS << "Olympus.spawnUnits, {"
|
||||
<< "category = " << "\"" << "Aircraft" << "\"" << ", "
|
||||
<< "coalition = " << "\"" << coalition << "\"" << ", "
|
||||
<< "airbaseName = \"" << airbaseName << "\", "
|
||||
<< "units = " << "\"" << unitsSS.str() << "\"" << "}";
|
||||
return commandSS.str();
|
||||
}
|
||||
|
||||
|
||||
@ -92,30 +92,49 @@ void Scheduler::handleRequest(string key, json::value value)
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
command = dynamic_cast<Command*>(new Smoke(color, loc));
|
||||
}
|
||||
else if (key.compare("spawnGround") == 0)
|
||||
else if (key.compare("spawnGroundUnits") == 0)
|
||||
{
|
||||
bool immediate = value[L"immediate"].as_bool();
|
||||
string coalition = to_string(value[L"coalition"]);
|
||||
string type = to_string(value[L"type"]);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
log("Spawning " + coalition + " ground unit of type " + type + " at (" + to_string(lat) + ", " + to_string(lng) + ")");
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
command = dynamic_cast<Command*>(new SpawnGroundUnit(coalition, type, loc, immediate));
|
||||
|
||||
vector<string> unitTypes;
|
||||
vector<Coords> locations;
|
||||
for (auto unit : value[L"units"].as_array()) {
|
||||
string unitType = to_string(unit[L"type"]);
|
||||
double lat = unit[L"location"][L"lat"].as_double();
|
||||
double lng = unit[L"location"][L"lng"].as_double();
|
||||
Coords location; location.lat = lat; location.lng = lng;
|
||||
log("Spawning " + coalition + " ground unit of type " + unitType + " at (" + to_string(lat) + ", " + to_string(lng) + ")");
|
||||
unitTypes.push_back(unitType);
|
||||
locations.push_back(location);
|
||||
}
|
||||
|
||||
command = dynamic_cast<Command*>(new SpawnGroundUnits(coalition, unitTypes, locations, immediate));
|
||||
}
|
||||
else if (key.compare("spawnAir") == 0)
|
||||
else if (key.compare("spawnAircrafts") == 0)
|
||||
{
|
||||
bool immediate = value[L"immediate"].as_bool();
|
||||
string coalition = to_string(value[L"coalition"]);
|
||||
string type = to_string(value[L"type"]);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
double altitude = value[L"altitude"].as_double();
|
||||
Coords loc; loc.lat = lat; loc.lng = lng; loc.alt = altitude;
|
||||
string payloadName = to_string(value[L"payloadName"]);
|
||||
string airbaseName = to_string(value[L"airbaseName"]);
|
||||
log("Spawning " + coalition + " air unit of type " + type + " with payload " + payloadName + " at (" + to_string(lat) + ", " + to_string(lng) + " " + airbaseName + ")");
|
||||
command = dynamic_cast<Command*>(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName, immediate));
|
||||
|
||||
vector<string> unitTypes;
|
||||
vector<Coords> locations;
|
||||
vector<string> payloadNames;
|
||||
for (auto unit : value[L"units"].as_array()) {
|
||||
string unitType = to_string(unit[L"type"]);
|
||||
double lat = unit[L"location"][L"lat"].as_double();
|
||||
double lng = unit[L"location"][L"lng"].as_double();
|
||||
double alt = value[L"altitude"].as_double();
|
||||
Coords location; location.lat = lat; location.lng = lng; location.alt = alt;
|
||||
string payloadName = to_string(value[L"payloadName"]);
|
||||
|
||||
log("Spawning " + coalition + " air unit unit of type " + unitType + " at (" + to_string(lat) + ", " + to_string(lng) + ")");
|
||||
unitTypes.push_back(unitType);
|
||||
locations.push_back(location);
|
||||
payloadNames.push_back(payloadName);
|
||||
}
|
||||
|
||||
command = dynamic_cast<Command*>(new SpawnAircrafts(coalition, unitTypes, locations, payloadNames, airbaseName, immediate));
|
||||
}
|
||||
else if (key.compare("attackUnit") == 0)
|
||||
{
|
||||
|
||||
@ -72,8 +72,9 @@ void Server::handle_get(http_request request)
|
||||
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
||||
|
||||
http_response response(status_codes::OK);
|
||||
string authorization = to_base64("admin:" + password);
|
||||
if (password.length() == 0 || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)) == 0))
|
||||
|
||||
string password = extractPassword(request);
|
||||
if (password.compare(gameMasterPassword) == 0 || password.compare(blueCommanderPassword) == 0 || password.compare(redCommanderPassword) == 0)
|
||||
{
|
||||
std::exception_ptr eptr;
|
||||
try {
|
||||
@ -114,9 +115,15 @@ void Server::handle_get(http_request request)
|
||||
answer[L"airbases"] = airbases;
|
||||
else if (URI.compare(BULLSEYE_URI) == 0)
|
||||
answer[L"bullseyes"] = bullseyes;
|
||||
else if (URI.compare(MISSION_URI) == 0)
|
||||
else if (URI.compare(MISSION_URI) == 0) {
|
||||
if (password.compare(gameMasterPassword) == 0)
|
||||
mission[L"visibilityMode"] = json::value(L"Game master");
|
||||
else if (password.compare(blueCommanderPassword) == 0)
|
||||
mission[L"visibilityMode"] = json::value(L"Blue commander");
|
||||
else if (password.compare(redCommanderPassword) == 0)
|
||||
mission[L"visibilityMode"] = json::value(L"Red commander");
|
||||
answer[L"mission"] = mission;
|
||||
|
||||
}
|
||||
|
||||
answer[L"time"] = json::value::string(to_wstring(ms.count()));
|
||||
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
|
||||
@ -144,8 +151,10 @@ void Server::handle_get(http_request request)
|
||||
void Server::handle_request(http_request request, function<void(json::value const&, json::value&)> action)
|
||||
{
|
||||
http_response response(status_codes::OK);
|
||||
string authorization = to_base64("admin:" + password);
|
||||
if (password.length() == 0 || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)) == 0))
|
||||
|
||||
//TODO: limit what a user can do depending on the password
|
||||
string password = extractPassword(request);
|
||||
if (password.compare(gameMasterPassword) == 0 || password.compare(blueCommanderPassword) == 0 || password.compare(redCommanderPassword) == 0)
|
||||
{
|
||||
auto answer = json::value::object();
|
||||
request.extract_json().then([&answer, &action](pplx::task<json::value> task)
|
||||
@ -200,6 +209,30 @@ void Server::handle_put(http_request request)
|
||||
});
|
||||
}
|
||||
|
||||
string Server::extractPassword(http_request& request) {
|
||||
if (request.headers().has(L"Authorization")) {
|
||||
string authorization = to_string(request.headers().find(L"Authorization")->second);
|
||||
string s = "Basic ";
|
||||
string::size_type i = authorization.find(s);
|
||||
|
||||
if (i != std::string::npos)
|
||||
authorization.erase(i, s.length());
|
||||
else
|
||||
return "";
|
||||
|
||||
string decoded = from_base64(authorization);
|
||||
i = authorization.find(":");
|
||||
if (i != std::string::npos)
|
||||
decoded.erase(0, i);
|
||||
else
|
||||
return "";
|
||||
|
||||
return decoded;
|
||||
}
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
void Server::task()
|
||||
{
|
||||
string address = REST_ADDRESS;
|
||||
@ -225,7 +258,9 @@ void Server::task()
|
||||
if (config.is_object() && config.has_object_field(L"authentication") &&
|
||||
config[L"authentication"].has_string_field(L"password"))
|
||||
{
|
||||
password = to_string(config[L"authentication"][L"password"]);
|
||||
gameMasterPassword = to_string(config[L"authentication"][L"gameMasterPassword"]);
|
||||
blueCommanderPassword = to_string(config[L"authentication"][L"blueCommanderPassword"]);
|
||||
redCommanderPassword = to_string(config[L"authentication"][L"redCommanderPassword"]);
|
||||
}
|
||||
else
|
||||
log("Error reading configuration file. No password set.");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user