diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index 99e8b35c..91a50f7f 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -197,6 +197,17 @@ padding: 0px 10px; } +.contextmenu-options-container { + display: flex; + align-items: center; + justify-content: space-between; + padding-left: 10px; +} + +.contextmenu-options-container>*:nth-child(2) { + width: 120px; +} + /* Unit context menu */ #unit-contextmenu { display: flex; diff --git a/client/src/controls/airbasecontextmenu.ts b/client/src/controls/airbasecontextmenu.ts index 5ee53166..c53bfb9a 100644 --- a/client/src/controls/airbasecontextmenu.ts +++ b/client/src/controls/airbasecontextmenu.ts @@ -50,7 +50,7 @@ export class AirbaseContextMenu extends ContextMenu { } setCoalition(coalition: string) { - (this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.activeCoalition = coalition; + (this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.coalition = coalition; } enableLandButton(enableLandButton: boolean) { @@ -62,6 +62,7 @@ export class AirbaseContextMenu extends ContextMenu { setActiveCoalition(this.#airbase.getCoalition()); getMap().showMapContextMenu({ originalEvent: { x: this.getX(), y: this.getY(), latlng: this.getLatLng() } }); getMap().getMapContextMenu().hideUpperBar(); + getMap().getMapContextMenu().hideAltitudeSlider(); getMap().getMapContextMenu().showSubMenu("aircraft"); getMap().getMapContextMenu().setAirbaseName(this.#airbase.getName()); getMap().getMapContextMenu().setLatLng(this.#airbase.getLatLng()); diff --git a/client/src/controls/coalitionareacontextmenu.ts b/client/src/controls/coalitionareacontextmenu.ts index dcb9bc23..5abdd79a 100644 --- a/client/src/controls/coalitionareacontextmenu.ts +++ b/client/src/controls/coalitionareacontextmenu.ts @@ -1,5 +1,6 @@ +import { LatLng } from "leaflet"; import { getMap, getUnitsManager } from ".."; -import { IADSRoles } from "../constants/constants"; +import { GAME_MASTER, IADSRoles } from "../constants/constants"; import { CoalitionArea } from "../map/coalitionarea"; import { ContextMenu } from "./contextmenu"; import { Dropdown } from "./dropdown"; @@ -75,6 +76,12 @@ export class CoalitionAreaContextMenu extends ContextMenu { this.hide(); } + show(x: number, y: number, latlng: LatLng) { + super.show(x, y, latlng); + if (getUnitsManager().getCommandMode() !== GAME_MASTER) + this.#coalitionSwitch.hide() + } + showSubMenu(type: string) { this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads"); this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads"); @@ -104,9 +111,11 @@ export class CoalitionAreaContextMenu extends ContextMenu { } #onSwitchClick(value: boolean) { - this.getCoalitionArea()?.setCoalition(value ? "red" : "blue"); - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { - element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) - }); + if (getUnitsManager().getCommandMode() == GAME_MASTER) { + this.getCoalitionArea()?.setCoalition(value ? "red" : "blue"); + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { + element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) + }); + } } } \ No newline at end of file diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 8db22841..0976f983 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -1,5 +1,5 @@ import { LatLng } from "leaflet"; -import { getActiveCoalition, getMap, setActiveCoalition } from ".."; +import { getActiveCoalition, getMap, getUnitsManager, setActiveCoalition } from ".."; import { spawnAircrafts, spawnExplosion, spawnGroundUnits, spawnSmoke } from "../server/server"; import { aircraftDatabase } from "../units/aircraftdatabase"; import { groundUnitsDatabase } from "../units/groundunitsdatabase"; @@ -8,16 +8,19 @@ import { Dropdown } from "./dropdown"; import { Switch } from "./switch"; import { Slider } from "./slider"; import { ftToM } from "../other/utils"; +import { GAME_MASTER } from "../constants/constants"; export class MapContextMenu extends ContextMenu { #coalitionSwitch: Switch; #aircraftRoleDropdown: Dropdown; #aircraftTypeDropdown: Dropdown; + #aircraftCountDropdown: Dropdown; #aircraftLoadoutDropdown: Dropdown; #aircrafSpawnAltitudeSlider: Slider; #groundUnitRoleDropdown: Dropdown; #groundUnitTypeDropdown: Dropdown; - #spawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", loadout: "", airbaseName: "", altitude: ftToM(20000) }; + #groundCountDropdown: Dropdown; + #spawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", loadout: "", airbaseName: "", altitude: ftToM(20000), count: 1 }; constructor(id: string) { super(id); @@ -27,6 +30,9 @@ export class MapContextMenu extends ContextMenu { this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e)); this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role)); this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type)); + this.#aircraftCountDropdown = new Dropdown("aircraft-count-options", (type: string) => this.#setAircraftCount(type)); + this.#aircraftCountDropdown.setOptions(["1", "2", "3", "4"]); + this.#aircraftCountDropdown.setValue("1"); this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout)); this.#aircrafSpawnAltitudeSlider = new Slider("aircraft-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);}); this.#aircrafSpawnAltitudeSlider.setIncrement(500); @@ -34,6 +40,11 @@ export class MapContextMenu extends ContextMenu { this.#aircrafSpawnAltitudeSlider.setActive(true); this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role)); this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type)); + this.#groundCountDropdown = new Dropdown("ground-count-options", (type: string) => this.#setGroundCount(type)); + var groundCount = []; + for (let i = 1; i <= 10; i++) groundCount.push(String(i)); + this.#groundCountDropdown.setOptions(groundCount); + this.#groundCountDropdown.setValue("1"); document.addEventListener("mapContextMenuShow", (e: any) => { if (this.getVisibleSubMenu() !== e.detail.type) @@ -47,7 +58,12 @@ export class MapContextMenu extends ContextMenu { this.#spawnOptions.coalition = getActiveCoalition(); if (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); + var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, altitude: this.#spawnOptions.altitude, loadout: this.#spawnOptions.loadout}; + var units = []; + for (let i = 1; i < parseInt(this.#aircraftCountDropdown.getValue()) + 1; i++) { + units.push(unitTable); + } + spawnAircrafts(units, getActiveCoalition(), this.#spawnOptions.airbaseName, false); } }); @@ -56,7 +72,13 @@ export class MapContextMenu extends ContextMenu { this.#spawnOptions.coalition = getActiveCoalition(); if (this.#spawnOptions) { getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition()); - spawnGroundUnits([{unitName: this.#spawnOptions.name, latlng: this.#spawnOptions.latlng}], getActiveCoalition(), false); + var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng}; + var units = []; + for (let i = 1; i < parseInt(this.#groundCountDropdown.getValue()) + 1; i++) { + units.push(JSON.parse(JSON.stringify(unitTable))); + unitTable.location.lat += 0.0001; + } + spawnGroundUnits(units, getActiveCoalition(), false); } }); @@ -70,7 +92,6 @@ export class MapContextMenu extends ContextMenu { spawnExplosion(e.detail.strength, this.getLatLng()); }); - this.hide(); } @@ -79,6 +100,19 @@ export class MapContextMenu extends ContextMenu { super.show(x, y, latlng); this.#spawnOptions.latlng = latlng; this.showUpperBar(); + + this.showAltitudeSlider(); + + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) }); + if (getActiveCoalition() == "blue") + this.#coalitionSwitch.setValue(false); + else if (getActiveCoalition() == "red") + this.#coalitionSwitch.setValue(true); + else + this.#coalitionSwitch.setValue(undefined); + + if (getUnitsManager().getCommandMode() !== GAME_MASTER) + this.#coalitionSwitch.hide() } showSubMenu(type: string) { @@ -95,6 +129,8 @@ export class MapContextMenu extends ContextMenu { this.#resetAircraftType(); this.#resetGroundUnitRole(); this.#resetGroundUnitType(); + this.#aircraftCountDropdown.setValue("1"); + this.#groundCountDropdown.setValue("1"); this.clip(); this.setVisibleSubMenu(type); @@ -119,7 +155,6 @@ export class MapContextMenu extends ContextMenu { this.setVisibleSubMenu(null); } - showUpperBar() { this.getContainer()?.querySelector("#upper-bar")?.classList.toggle("hide", false); } @@ -128,6 +163,14 @@ export class MapContextMenu extends ContextMenu { this.getContainer()?.querySelector("#upper-bar")?.classList.toggle("hide", true); } + showAltitudeSlider() { + this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", false); + } + + hideAltitudeSlider() { + this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", true); + } + setAirbaseName(airbaseName: string) { this.#spawnOptions.airbaseName = airbaseName; } @@ -144,6 +187,7 @@ export class MapContextMenu extends ContextMenu { #onSwitchRightClick(e: any) { this.#coalitionSwitch.setValue(undefined); setActiveCoalition("neutral"); + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) }); } /********* Aircraft spawn menu *********/ @@ -178,6 +222,11 @@ export class MapContextMenu extends ContextMenu { this.clip(); } + #setAircraftCount(count: string) { + this.#spawnOptions.count = parseInt(count); + this.clip(); + } + #resetAircraftType() { (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; (this.getContainer()?.querySelector("#loadout-list")).replaceChildren(); @@ -236,6 +285,11 @@ export class MapContextMenu extends ContextMenu { this.clip(); } + #setGroundCount(count: string) { + this.#spawnOptions.count = parseInt(count); + this.clip(); + } + #resetGroundUnitType() { (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; this.clip(); diff --git a/client/src/index.ts b/client/src/index.ts index b86827f7..47742664 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -16,6 +16,7 @@ import { Popup } from "./popups/popup"; import { Dropdown } from "./controls/dropdown"; import { HotgroupPanel } from "./panels/hotgrouppanel"; import { SVGInjector } from "@tanem/svg-injector"; +import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants"; var map: Map; @@ -44,8 +45,8 @@ function setup() { featureSwitches = new FeatureSwitches(); /* Initialize base functionalitites */ - map = new Map('map-container'); unitsManager = new UnitsManager(); + map = new Map('map-container'); missionHandler = new MissionHandler(); /* Panels */ @@ -252,11 +253,21 @@ export function getHotgroupPanel() { } export function setActiveCoalition(newActiveCoalition: string) { - activeCoalition = newActiveCoalition; + if (getUnitsManager().getCommandMode() == GAME_MASTER) + activeCoalition = newActiveCoalition; } export function getActiveCoalition() { - return activeCoalition; + if (getUnitsManager().getCommandMode() == GAME_MASTER) + return activeCoalition; + else { + if (getUnitsManager().getCommandMode() == BLUE_COMMANDER) + return "blue"; + else if (getUnitsManager().getCommandMode() == RED_COMMANDER) + return "red"; + else + return "neutral"; + } } export function setLoginStatus(status: string) { diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts index 2e2ab0fe..27397061 100644 --- a/client/src/map/coalitionarea.ts +++ b/client/src/map/coalitionarea.ts @@ -1,7 +1,8 @@ import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet"; -import { getMap } from ".."; +import { getMap, getUnitsManager } from ".."; import { CoalitionAreaHandle } from "./coalitionareahandle"; import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle"; +import { BLUE_COMMANDER, RED_COMMANDER } from "../constants/constants"; export class CoalitionArea extends Polygon { #coalition: string = "blue"; @@ -19,7 +20,11 @@ export class CoalitionArea extends Polygon { super(latlngs, options); this.#setColors(); this.#registerCallbacks(); - + + if (getUnitsManager().getCommandMode() == BLUE_COMMANDER) + this.setCoalition("blue"); + else if (getUnitsManager().getCommandMode() == RED_COMMANDER) + this.setCoalition("red"); } setCoalition(coalition: string) { diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index 0fd69911..b8882a7c 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -247,7 +247,7 @@ export function getMarkerCategoryByName(name: string) { return (role?.includes("SAM")) ? "groundunit-sam" : "groundunit-other"; } else - return ""; // TODO add other unit types + return "groundunit-other"; // TODO add other unit types } export function getUnitDatabaseByCategory(category: string) { diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 1abf45ef..582f7ddd 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -36,7 +36,7 @@ export class UnitControlPanel extends Panel { /* Option buttons */ // Reversing the ROEs so that the least "aggressive" option is always on the left this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => { - return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); }); + return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions.slice(0).reverse()[index], () => { getUnitsManager().selectedUnitsSetROE(option); }); }); this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => { diff --git a/client/src/server/server.ts b/client/src/server/server.ts index fefee15f..d77df67d 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -324,9 +324,7 @@ export function startUpdate() { export function requestUpdate() { /* Main update rate = 250ms is minimum time, equal to server update time. */ if (!getPaused()) { - getUnits((buffer: ArrayBuffer) => { - getUnitsManager()?.update(buffer); - }, false); + getUnits((buffer: ArrayBuffer) => { getUnitsManager()?.update(buffer); }, false); } window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000); @@ -335,7 +333,6 @@ export function requestUpdate() { export function requestRefresh() { /* Main refresh rate = 5000ms. */ - if (!getPaused()) { getAirbases((data: AirbasesData) => getMissionData()?.update(data)); getBullseye((data: BullseyesData) => getMissionData()?.update(data)); diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index ee7be24f..14e6f44e 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -6,7 +6,7 @@ import { CustomMarker } from '../map/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './unitdatabase'; import { TargetMarker } from '../map/targetmarker'; -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 { BLUE_COMMANDER, BOMBING, CARPET_BOMBING, DLINK, DataIndexes, FIRE_AT_AREA, GAME_MASTER, 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'; @@ -223,6 +223,10 @@ export class Unit extends CustomMarker { if (updateMarker) this.#updateMarker(); + document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); + } + + drawLines() { // TODO dont delete the polylines of the detected units this.#clearContacts(); if (this.getSelected()) { @@ -234,8 +238,6 @@ export class Unit extends CustomMarker { this.#clearPath(); this.#clearTarget(); } - - document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); } getData() { @@ -365,11 +367,11 @@ export class Unit extends CustomMarker { } belongsToCommandedCoalition() { - if (getUnitsManager().getVisibilityMode() === HIDE_ALL) + if (getUnitsManager().getCommandMode() === HIDE_ALL) return false; - if (getUnitsManager().getVisibilityMode() === BLUE_COMMANDER && this.#coalition !== "blue") + if (getUnitsManager().getCommandMode() === BLUE_COMMANDER && this.#coalition !== "blue") return false; - if (getUnitsManager().getVisibilityMode() === RED_COMMANDER && this.#coalition !== "red") + if (getUnitsManager().getCommandMode() === RED_COMMANDER && this.#coalition !== "red") return false; return true; } @@ -486,7 +488,7 @@ export class Unit extends CustomMarker { hidden = true; if (hiddenUnits.includes(this.#coalition)) hidden = true; - if (getUnitsManager().getVisibilityMode() === HIDE_ALL) + if (getUnitsManager().getCommandMode() === HIDE_ALL) hidden = true; if (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0) { hidden = true; @@ -791,7 +793,8 @@ export class Unit extends CustomMarker { this.updateVisibility(); /* Draw the minimap marker */ - if (this.#alive) { + var drawMiniMapMarker = (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))); + if (this.#alive && drawMiniMapMarker) { if (this.#miniMapMarker == null) { this.#miniMapMarker = new CircleMarker(new LatLng(this.#position.lat, this.#position.lng), { radius: 0.5 }); if (this.#coalition == "neutral") @@ -985,7 +988,7 @@ export class Unit extends CustomMarker { } 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))) { + if (target && (getUnitsManager().getCommandMode() == GAME_MASTER || (this.belongsToCommandedCoalition() && getUnitsManager().getUnitDetectedMethods(target).some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))))) { this.#drawtargetPosition(target.getPosition()); } } @@ -1093,6 +1096,10 @@ export class NavyUnit extends Unit { }; } + getMarkerCategory() { + return "navyunit"; + } + getCategory() { return "NavyUnit"; } @@ -1109,7 +1116,7 @@ export class Weapon extends Unit { showState: false, showVvi: false, showHotgroup: false, - showUnitIcon: this.belongsToCommandedCoalition(), + showUnitIcon: true, showShortLabel: false, showFuel: false, showAmmo: false, diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 8980cefe..355753bb 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -16,7 +16,7 @@ export class UnitsManager { #selectionEventDisabled: boolean = false; #pasteDisabled: boolean = false; #hiddenTypes: string[] = []; - #visibilityMode: string = HIDE_ALL; + #commandMode: string = HIDE_ALL; constructor() { this.#units = {}; @@ -75,7 +75,7 @@ export class UnitsManager { update(buffer: ArrayBuffer) { var dataExtractor = new DataExtractor(buffer); var updateTime = Number(dataExtractor.extractUInt64()); - + var requestRefresh = false; while (dataExtractor.getSeekPosition() < buffer.byteLength) { const ID = dataExtractor.extractUInt32(); if (!(ID in this.#units)) { @@ -85,7 +85,7 @@ export class UnitsManager { this.addUnit(ID, category); } else { - // TODO request a refresh since we must have missed some packets + requestRefresh = true; } } this.#units[ID]?.setData(dataExtractor); @@ -98,6 +98,10 @@ export class UnitsManager { } setLastUpdateTime(updateTime); + + for (let ID in this.#units) { + this.#units[ID].drawLines(); + }; } setHiddenType(key: string, value: boolean) { @@ -115,21 +119,21 @@ export class UnitsManager { } setVisibilityMode(newVisibilityMode: string) { - if (newVisibilityMode !== this.#visibilityMode) { + if (newVisibilityMode !== this.#commandMode) { 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; + this.#commandMode = newVisibilityMode; for (let ID in this.#units) this.#units[ID].updateVisibility(); } } - getVisibilityMode() { - return this.#visibilityMode; + getCommandMode() { + return this.#commandMode; } selectUnit(ID: number, deselectAllUnits: boolean = true) { @@ -558,7 +562,7 @@ export class UnitsManager { const probability = Math.pow(1 - minDistance / 50e3, 5) * IADSRoles[role]; if (Math.random() < probability){ const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role); - spawnGroundUnits([{unitName: unitBlueprint.name, latlng: latlng}], coalitionArea.getCoalition(), true); + spawnGroundUnits([{unitType: unitBlueprint.name, location: latlng}], coalitionArea.getCoalition(), true); getMap().addTemporaryMarker(latlng, unitBlueprint.name, coalitionArea.getCoalition()); } } @@ -569,10 +573,12 @@ export class UnitsManager { for (let ID in this.#units) { var unit = this.#units[ID]; if (!["Aircraft", "Helicopter"].includes(unit.getCategory())) { + var data: any = unit.getData(); + data.category = unit.getCategory(); if (unit.getGroupName() in unitsToExport) - unitsToExport[unit.getGroupName()].push(unit.getData()); + unitsToExport[unit.getGroupName()].push(data); else - unitsToExport[unit.getGroupName()] = [unit.getData()]; + unitsToExport[unit.getGroupName()] = [data]; } } var a = document.createElement("a"); @@ -594,7 +600,12 @@ export class UnitsManager { reader.onload = function(e: any) { var contents = e.target.result; var groups = JSON.parse(contents); - + for (let groupName in groups) { + if (groupName !== "" && groups[groupName].length > 0 && groups[groupName].every((unit: any) => {return unit.category == "GroundUnit";})) { + var units = groups[groupName].map((unit: any) => {return {unitType: unit.name, location: unit.position}}); + spawnGroundUnits(units, groups[groupName][0].coalition, true); + } + } }; reader.readAsText(file); }) diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs index eed3f719..ee41d2e4 100644 --- a/client/views/other/contextmenus.ejs +++ b/client/views/other/contextmenus.ejs @@ -38,6 +38,15 @@ +
+
Group members
+
+
+
+ +
+
+
Spawn altitude @@ -74,6 +83,15 @@
+
+
Group members
+
+
+
+ +
+
+
@@ -104,7 +122,7 @@

Parking available:

- +
diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 6ec47dae..1c3ecd59 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.3.0-alpha" -local debug = FALSE +local debug = false Olympus.unitCounter = 1 Olympus.payloadRegistry = {} @@ -305,10 +305,10 @@ end -- Spawns a new unit or group function Olympus.spawnUnits(spawnTable) - Olympus.debug("Olympus.spawnUnits " .. serializeTable(spawnTable), 2) + Olympus.debug("Olympus.spawnUnits " .. Olympus.serializeTable(spawnTable), 2) - local unitTable = {} - local route = {} + local unitTable = nil + local route = nil local category = nil if spawnTable.category == 'Aircraft' then @@ -328,6 +328,7 @@ function Olympus.spawnUnits(spawnTable) category = category, route = route, name = "Olympus-" .. Olympus.unitCounter, + task = 'CAP' } mist.dynAdd(vars) @@ -337,16 +338,16 @@ end -- Generates ground units table, either single or from template function Olympus.generateGroundUnitsTable(units) + local unitTable = {} for idx, unit in pairs(units) do local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0)) - local unitTable = {} if Olympus.hasKey(templates, unit.unitType) then for idx, value in pairs(templates[unit.unitType].units) do - unitTable[#unitTable + 1] = { + unitTable[#unitTable + 1] = + { ["type"] = value.name, ["x"] = spawnLocation.x + value.dx, ["y"] = spawnLocation.z + value.dy, - ["playerCanDrive"] = true, ["heading"] = 0, ["skill"] = "High" } @@ -355,9 +356,8 @@ function Olympus.generateGroundUnitsTable(units) unitTable[#unitTable + 1] = { ["type"] = unit.unitType, - ["x"] = unit.x, - ["y"] = unit.z, - ["playerCanDrive"] = true, + ["x"] = spawnLocation.x, + ["y"] = spawnLocation.z, ["heading"] = 0, ["skill"] = "High" } @@ -371,12 +371,12 @@ end 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 + local loadout = unit.loadout -- loadout: 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 loadout if payload == nil then - if payloadName and payloadName ~= "" and Olympus.unitPayloads[unit.unitType][payloadName] then - payload = Olympus.unitPayloads[unit.unitType][payloadName] + if loadout and loadout ~= "" and Olympus.unitPayloads[unit.unitType][loadout] then + payload = Olympus.unitPayloads[unit.unitType][loadout] else payload = {} end @@ -398,10 +398,11 @@ function Olympus.generateAirUnitsTable(units) } end return unitTable +end 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)) + local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(spawnTable.units[1].lat, spawnTable.units[1].lng, 0)) -- If a airbase is provided the first waypoint is set as a From runway takeoff. local route = {} diff --git a/src/core/include/commands.h b/src/core/include/commands.h index 91d42162..bd819edb 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -176,11 +176,11 @@ private: class SpawnAircrafts : public Command { public: - SpawnAircrafts(string coalition, vector unitTypes, vector locations, vector payloadNames, string airbaseName, bool immediate) : + SpawnAircrafts(string coalition, vector unitTypes, vector locations, vector loadouts, string airbaseName, bool immediate) : coalition(coalition), unitTypes(unitTypes), locations(locations), - payloadNames(payloadNames), + loadouts(loadouts), airbaseName(airbaseName), immediate(immediate) { @@ -193,7 +193,7 @@ private: const string coalition; const vector unitTypes; const vector locations; - const vector payloadNames; + const vector loadouts; const string airbaseName; const bool immediate; }; diff --git a/src/core/src/commands.cpp b/src/core/src/commands.cpp index 450158a0..2d9bbb9a 100644 --- a/src/core/src/commands.cpp +++ b/src/core/src/commands.cpp @@ -48,7 +48,7 @@ string SpawnGroundUnits::getString(lua_State* L) unitsSS << "[" << i + 1 << "] = {" << "unitType = " << "\"" << unitTypes[i] << "\"" << ", " << "lat = " << locations[i].lat << ", " - << "lng = " << locations[i].lng << "}"; + << "lng = " << locations[i].lng << "},"; } std::ostringstream commandSS; @@ -56,14 +56,14 @@ string SpawnGroundUnits::getString(lua_State* L) commandSS << "Olympus.spawnUnits, {" << "category = " << "\"" << "GroundUnit" << "\"" << ", " << "coalition = " << "\"" << coalition << "\"" << ", " - << "units = " << "\"" << unitsSS.str() << "\"" << "}"; + << "units = " << "{" << unitsSS.str() << "}" << "}"; return commandSS.str(); } /* Spawn aircrafts command */ string SpawnAircrafts::getString(lua_State* L) { - if (unitTypes.size() != locations.size() || unitTypes.size() != payloadNames.size()) return ""; + if (unitTypes.size() != locations.size() || unitTypes.size() != loadouts.size()) return ""; std::ostringstream unitsSS; unitsSS.precision(10); @@ -73,7 +73,7 @@ string SpawnAircrafts::getString(lua_State* L) << "lat = " << locations[i].lat << ", " << "lng = " << locations[i].lng << ", " << "alt = " << locations[i].alt << ", " - << "payloadName = \"" << payloadNames[i] << "\", " << "}"; + << "loadout = \"" << loadouts[i] << "\"" << "},"; } std::ostringstream commandSS; @@ -82,7 +82,7 @@ string SpawnAircrafts::getString(lua_State* L) << "category = " << "\"" << "Aircraft" << "\"" << ", " << "coalition = " << "\"" << coalition << "\"" << ", " << "airbaseName = \"" << airbaseName << "\", " - << "units = " << "\"" << unitsSS.str() << "\"" << "}"; + << "units = " << "{" << unitsSS.str() << "}" << "}"; return commandSS.str(); } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 799faea0..739a21eb 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -100,7 +100,7 @@ void Scheduler::handleRequest(string key, json::value value) vector unitTypes; vector locations; for (auto unit : value[L"units"].as_array()) { - string unitType = to_string(unit[L"type"]); + string unitType = to_string(unit[L"unitType"]); 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; @@ -119,22 +119,22 @@ void Scheduler::handleRequest(string key, json::value value) vector unitTypes; vector locations; - vector payloadNames; + vector loadouts; for (auto unit : value[L"units"].as_array()) { - string unitType = to_string(unit[L"type"]); + string unitType = to_string(unit[L"unitType"]); 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(); + double alt = unit[L"altitude"].as_double(); Coords location; location.lat = lat; location.lng = lng; location.alt = alt; - string payloadName = to_string(value[L"payloadName"]); + string loadout = to_string(unit[L"loadout"]); 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); + loadouts.push_back(loadout); } - command = dynamic_cast(new SpawnAircrafts(coalition, unitTypes, locations, payloadNames, airbaseName, immediate)); + command = dynamic_cast(new SpawnAircrafts(coalition, unitTypes, locations, loadouts, airbaseName, immediate)); } else if (key.compare("attackUnit") == 0) { diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index 7a27bcec..1c300f38 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -221,9 +221,9 @@ string Server::extractPassword(http_request& request) { return ""; string decoded = from_base64(authorization); - i = authorization.find(":"); - if (i != std::string::npos) - decoded.erase(0, i); + i = decoded.find(":"); + if (i != string::npos && i+1 < decoded.length()) + decoded.erase(0, i+1); else return ""; @@ -255,12 +255,11 @@ void Server::task() else log("Error reading configuration file. Starting server on " + address); - if (config.is_object() && config.has_object_field(L"authentication") && - config[L"authentication"].has_string_field(L"password")) + if (config.is_object() && config.has_object_field(L"authentication")) { - gameMasterPassword = to_string(config[L"authentication"][L"gameMasterPassword"]); - blueCommanderPassword = to_string(config[L"authentication"][L"blueCommanderPassword"]); - redCommanderPassword = to_string(config[L"authentication"][L"redCommanderPassword"]); + if (config[L"authentication"].has_string_field(L"gameMasterPassword")) gameMasterPassword = to_string(config[L"authentication"][L"gameMasterPassword"]); + if (config[L"authentication"].has_string_field(L"blueCommanderPassword")) blueCommanderPassword = to_string(config[L"authentication"][L"blueCommanderPassword"]); + if (config[L"authentication"].has_string_field(L"redCommanderPassword")) redCommanderPassword = to_string(config[L"authentication"][L"redCommanderPassword"]); } else log("Error reading configuration file. No password set."); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index dd912cf7..74b810cd 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -84,7 +84,7 @@ void Unit::runAILoop() { const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return; - if (checkTaskFailed() && state != State::IDLE && State::LAND) + if (checkTaskFailed() && state != State::IDLE && state != State::LAND) setState(State::IDLE); AIloop(); @@ -188,10 +188,10 @@ bool Unit::hasFreshData(unsigned long long time) { void Unit::getData(stringstream& ss, unsigned long long time) { Unit* leader = this; - if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) + if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) leader = unitsManager->getGroupLeader(this); - if (!leader->hasFreshData(time)) return; + if (leader == nullptr || (!leader->hasFreshData(time) && !hasFreshData(time))) return; const unsigned char endOfData = DataIndex::endOfData; ss.write((const char*)&ID, sizeof(ID)); diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 3c96ef9c..d3b181e0 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -39,6 +39,7 @@ bool UnitsManager::isUnitInGroup(Unit* unit) { if (unit != nullptr) { string groupName = unit->getGroupName(); + if (groupName.length() == 0) return false; for (auto const& p : units) { if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit) @@ -50,8 +51,10 @@ bool UnitsManager::isUnitInGroup(Unit* unit) bool UnitsManager::isUnitGroupLeader(Unit* unit) { - if (unit != nullptr) - return unit == getGroupLeader(unit); + if (unit != nullptr) { + Unit* leader = getGroupLeader(unit); + return leader == nullptr? false: unit == getGroupLeader(unit); + } else return false; } @@ -61,7 +64,7 @@ Unit* UnitsManager::getGroupLeader(Unit* unit) { if (unit != nullptr) { string groupName = unit->getGroupName(); - + if (groupName.length() == 0) return nullptr; /* Find the first unit that has the same groupName */ for (auto const& p : units) {