diff --git a/client/package-lock.json b/client/package-lock.json index 7d7b205d..a21cccf9 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "DCSOlympus", - "version": "v0.4.0-alpha", + "version": "v0.4.1-alpha", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "DCSOlympus", - "version": "v0.4.0-alpha", + "version": "v0.4.1-alpha", "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", diff --git a/client/package.json b/client/package.json index 95934695..adeb53ef 100644 --- a/client/package.json +++ b/client/package.json @@ -2,7 +2,7 @@ "name": "DCSOlympus", "node-main": "./bin/www", "main": "http://localhost:3000", - "version": "v0.4.0-alpha", + "version": "v0.4.1-alpha", "private": true, "scripts": { "copy": "copy.bat", diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index be4eb2a5..fc208481 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -140,6 +140,7 @@ export const CARPET_BOMBING = "Carpet bombing"; export const FIRE_AT_AREA = "Fire at area"; export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; +export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["groundunit-sam", "groundunit-sam-radar", "groundunit-sam-launcher"], ["groundunit-other", "groundunit-ewr"], ["navyunit"], ["airbase"]]; export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"]; diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 4bd58cda..51574d35 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker"; import { TemporaryUnitMarker } from "./temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS } from "../constants/constants"; +import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes } from "../constants/constants"; import { TargetMarker } from "./targetmarker"; import { CoalitionArea } from "./coalitionarea"; import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; @@ -120,7 +120,7 @@ export class Map extends L.Map { document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { const el = ev.detail._element; el?.classList.toggle("off"); - getUnitsManager().setHiddenType(ev.detail.type, !el?.classList.contains("off")); + ev.detail.types.forEach((type: string) => getUnitsManager().setHiddenType(type, !el?.classList.contains("off"))); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); @@ -150,7 +150,7 @@ export class Map extends L.Map { /* Option buttons */ this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => { - return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"type": "${option}"}`); + return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"types": "${visibilityControlsTypes[index]}"}`); }); document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); diff --git a/client/src/mission/missionhandler.ts b/client/src/mission/missionhandler.ts index e1c9bbe3..4ce104b6 100644 --- a/client/src/mission/missionhandler.ts +++ b/client/src/mission/missionhandler.ts @@ -184,8 +184,9 @@ export class MissionHandler { #setcommandModeOptions(commandModeOptions: CommandModeOptions) { /* Refresh all the data if we have exited the NONE state */ + var requestRefresh = false; if (this.#commandModeOptions.commandMode === NONE && commandModeOptions.commandMode !== NONE) - refreshAll(); + requestRefresh = true; /* Refresh the page if we have lost Game Master priviledges */ if (this.#commandModeOptions.commandMode === GAME_MASTER && commandModeOptions.commandMode !== GAME_MASTER) @@ -214,6 +215,9 @@ export class MissionHandler { document.querySelector("#spawn-points-container")?.classList.toggle("hide", this.getCommandModeOptions().commandMode === GAME_MASTER || !this.getCommandModeOptions().restrictSpawns); document.querySelector("#command-mode-settings-button")?.classList.toggle("hide", this.getCommandModeOptions().commandMode !== GAME_MASTER); + + if (requestRefresh) + refreshAll(); } #onAirbaseClick(e: any) { diff --git a/client/src/server/dataextractor.ts b/client/src/server/dataextractor.ts index 278d4a72..d22b6201 100644 --- a/client/src/server/dataextractor.ts +++ b/client/src/server/dataextractor.ts @@ -13,6 +13,10 @@ export class DataExtractor { this.#decoder = new TextDecoder("utf-8"); } + setSeekPosition(seekPosition: number) { + this.#seekPosition = seekPosition; + } + getSeekPosition() { return this.#seekPosition; } @@ -67,7 +71,13 @@ export class DataExtractor { var stringBuffer = this.#buffer.slice(this.#seekPosition, this.#seekPosition + length); var view = new Int8Array(stringBuffer); var stringLength = length; - view.forEach((value: number, idx: number) => { if (value === 0) stringLength = idx; }); + view.every((value: number, idx: number) => { + if (value === 0) { + stringLength = idx; + return false; + } else + return true; + }); const value = this.#decoder.decode(stringBuffer); this.#seekPosition += length; return value.substring(0, stringLength).trim(); diff --git a/client/src/server/server.ts b/client/src/server/server.ts index ce9c5f9c..4c8ef77b 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -413,6 +413,15 @@ export function startUpdate() { getConnectionStatusPanel()?.update(getConnected()); } }, 5000); + + window.setInterval(() => { + if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) { + getWeapons((buffer: ArrayBuffer) => { + var time = getWeaponsManager()?.update(buffer); + return time; + }, false); + } + }, 5000); } export function refreshAll() { @@ -437,7 +446,7 @@ export function refreshAll() { getWeapons((buffer: ArrayBuffer) => { var time = getWeaponsManager()?.update(buffer); return time; - }, false); + }, true); getUnits((buffer: ArrayBuffer) => { var time = getUnitsManager()?.update(buffer); diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 289b47d4..abe147cb 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -190,7 +190,7 @@ export class Unit extends CustomMarker { case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break; case DataIndexes.human: this.#human = dataExtractor.extractBool(); break; case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break; - case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break; + case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); updateMarker = true; break; case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break; case DataIndexes.name: this.#name = dataExtractor.extractString(); break; case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break; @@ -500,7 +500,7 @@ export class Unit extends CustomMarker { (this.#controlled == false && hiddenUnits.includes("dcs")) || (hiddenUnits.includes(this.getMarkerCategory())) || (hiddenUnits.includes(this.#coalition)) || - (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0) || + (!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) || (getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) && !(this.getSelected()); @@ -748,6 +748,11 @@ export class Unit extends CustomMarker { options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" }; } + if ((selectedUnits.length === 0 && this.getCategory() == "NavyUnit") || selectedUnitTypes.length === 1 && ["NavyUnit"].includes(selectedUnitTypes[0])) { + if (selectedUnits.concat([this]).every((unit: Unit) => { return ["Cruiser", "Destroyer", "Frigate"].includes(this.getType()) })) + options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" }; + } + if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined) options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." }; diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index b0f4bead..047c64ec 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -1,7 +1,7 @@ import { LatLng, LatLngBounds } from "leaflet"; import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsManager } from ".."; import { Unit } from "./unit"; -import { cloneUnit, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server"; +import { cloneUnit, deleteUnit, refreshAll, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server"; import { bearingAndDistanceToLatLng, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils"; import { CoalitionArea } from "../map/coalitionarea"; import { groundUnitDatabase } from "./groundunitdatabase"; @@ -35,7 +35,7 @@ export class UnitsManager { document.addEventListener('exportToFile', () => this.exportToFile()); document.addEventListener('importFromFile', () => this.importFromFile()); document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true}); - document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility())}); + document.addEventListener('commandModeOptionsChanged', () => {Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility())}); } getSelectableAircraft() { @@ -76,7 +76,6 @@ 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)) { @@ -86,18 +85,40 @@ export class UnitsManager { this.addUnit(ID, category); } else { - requestRefresh = true; + /* Inconsistent data, we need to wait for a refresh */ + return updateTime; } } - this.#units[ID]?.setData(dataExtractor); + this.#units[ID]?.setData(dataExtractor); } if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) { + /* Create a dictionary of empty detection methods arrays */ + var detectionMethods: {[key: string]: number[]} = {}; for (let ID in this.#units) { - var unit = this.#units[ID]; - if (unit.getAlive() && !unit.belongsToCommandedCoalition()) - unit.setDetectionMethods(this.getUnitDetectedMethods(unit)); + const unit = this.#units[ID]; + detectionMethods[ID] = []; } + + /* Fill the array with the detection methods */ + for (let ID in this.#units) { + const unit = this.#units[ID]; + if (unit.getAlive() && unit.belongsToCommandedCoalition()){ + const contacts = unit.getContacts(); + contacts.forEach((contact: Contact) => { + const contactID = contact.ID; + if (!(detectionMethods[contactID].includes(contact.detectionMethod))) + detectionMethods[contactID]?.push(contact.detectionMethod); + }) + } + } + + /* Set the detection methods for every unit */ + for (let ID in this.#units) { + const unit = this.#units[ID]; + unit.setDetectionMethods(detectionMethods[ID]); + } + this.#requestDetectionUpdate = false; } @@ -650,10 +671,10 @@ export class UnitsManager { 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";})) { + if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: any) => {return unit.category == "GroundUnit";}) || groups[groupName].every((unit: any) => {return unit.category == "NavyUnit";}))) { var aliveUnits = groups[groupName].filter((unit: any) => {return unit.alive}); var units = aliveUnits.map((unit: any) => {return {unitType: unit.name, location: unit.position}}); - getUnitsManager().spawnUnits("GroundUnit", units, groups[groupName][0].coalition, true); + getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true); } } }; diff --git a/client/src/weapon/weaponsmanager.ts b/client/src/weapon/weaponsmanager.ts index 3a3a9b2e..e3accd27 100644 --- a/client/src/weapon/weaponsmanager.ts +++ b/client/src/weapon/weaponsmanager.ts @@ -39,7 +39,6 @@ export class WeaponsManager { 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.#weapons)) { @@ -49,7 +48,8 @@ export class WeaponsManager { this.addWeapon(ID, category); } else { - requestRefresh = true; + /* Inconsistent data, we need to wait for a refresh */ + return updateTime; } } this.#weapons[ID]?.setData(dataExtractor); diff --git a/client/views/other/dialogs.ejs b/client/views/other/dialogs.ejs index 542e0d88..1d00ce6d 100644 --- a/client/views/other/dialogs.ejs +++ b/client/views/other/dialogs.ejs @@ -3,7 +3,7 @@