diff --git a/client/demo.js b/client/demo.js
index d7fcebf0..6c7c354b 100644
--- a/client/demo.js
+++ b/client/demo.js
@@ -15,7 +15,7 @@ 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: 2, detectionMethod: 1}],
+ contacts: [{ID: 2, detectionMethod: 1}, {ID: 3, detectionMethod: 4}],
activePath: [{lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0}]
},
["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",
@@ -33,7 +33,7 @@ const DEMO_UNIT_DATA = {
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 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",
+ }, ["3"]:{ category: "Missile", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "", 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 },
@@ -343,7 +343,7 @@ class DemoDataGenerator {
};
mission(req, res){
- var ret = {mission: {theatre: "Syria"}};
+ var ret = {mission: {theatre: "Nevada"}};
ret.time = Date.now();
var auth = req.get("Authorization");
if (auth) {
diff --git a/client/public/stylesheets/layout/layout.css b/client/public/stylesheets/layout/layout.css
index 7ae3c7b5..63a525e6 100644
--- a/client/public/stylesheets/layout/layout.css
+++ b/client/public/stylesheets/layout/layout.css
@@ -38,7 +38,7 @@
font-size: 12px;
position: absolute;
right: 10px;
- width: 180px;
+ width: 250px;
z-index: 9999;
}
diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css
index f35985db..b6f2db41 100644
--- a/client/public/stylesheets/olympus.css
+++ b/client/public/stylesheets/olympus.css
@@ -20,6 +20,7 @@
html * {
font-family: 'Open Sans', sans-serif !important;
+ user-select: none;
}
body {
diff --git a/client/public/stylesheets/panels/connectionstatus.css b/client/public/stylesheets/panels/connectionstatus.css
index 2b7dcec4..715850a9 100644
--- a/client/public/stylesheets/panels/connectionstatus.css
+++ b/client/public/stylesheets/panels/connectionstatus.css
@@ -11,7 +11,7 @@
}
#connection-status-panel[data-is-connected] dt::before {
- content: "Connected";
+ content: "Connected FPS: " attr(data-framerate) " Load: " attr(data-load);
}
#connection-status-panel[data-is-connected] dd::after {
diff --git a/client/public/themes/olympus/images/units/groundunit-ewr.svg b/client/public/themes/olympus/images/units/groundunit-ewr.svg
new file mode 100644
index 00000000..2105f061
--- /dev/null
+++ b/client/public/themes/olympus/images/units/groundunit-ewr.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/client/public/themes/olympus/images/units/groundunit-sam-launcher.svg b/client/public/themes/olympus/images/units/groundunit-sam-launcher.svg
new file mode 100644
index 00000000..1732a5b0
--- /dev/null
+++ b/client/public/themes/olympus/images/units/groundunit-sam-launcher.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/client/public/themes/olympus/images/units/groundunit-sam-radar.svg b/client/public/themes/olympus/images/units/groundunit-sam-radar.svg
new file mode 100644
index 00000000..0472f81e
--- /dev/null
+++ b/client/public/themes/olympus/images/units/groundunit-sam-radar.svg
@@ -0,0 +1,50 @@
+
+
diff --git a/client/src/@types/dom.d.ts b/client/src/@types/dom.d.ts
index 9d260bb8..22659e3b 100644
--- a/client/src/@types/dom.d.ts
+++ b/client/src/@types/dom.d.ts
@@ -19,6 +19,7 @@ interface CustomEventMap {
"mapStateChanged": CustomEvent,
"mapContextMenu": CustomEvent<>,
"visibilityModeChanged": CustomEvent,
+ "contactsUpdated": CustomEvent,
}
declare global {
diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts
index 66053969..7e47dff6 100644
--- a/client/src/constants/constants.ts
+++ b/client/src/constants/constants.ts
@@ -142,8 +142,8 @@ export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "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 Sites", "Radar"];
-export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Sites": 0.1, "Radar": 0.05};
+export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];
+export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05};
export enum DataIndexes {
startOfData = 0,
@@ -184,5 +184,6 @@ export enum DataIndexes {
ammo,
contacts,
activePath,
+ isLeader,
endOfData = 255
};
\ No newline at end of file
diff --git a/client/src/map/map.ts b/client/src/map/map.ts
index 1d3053e5..88758a00 100644
--- a/client/src/map/map.ts
+++ b/client/src/map/map.ts
@@ -67,7 +67,7 @@ export class Map extends L.Map {
constructor(ID: string) {
/* Init the leaflet map */
//@ts-ignore Needed because the boxSelect option is non-standard
- super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
+ super(ID, { preferCanvas: true, doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
this.setView([37.23, -115.8], 10);
this.#ID = ID;
@@ -295,6 +295,10 @@ export class Map extends L.Map {
}
}
+ getCenterUnit() {
+ return this.#centerUnit;
+ }
+
setTheatre(theatre: string) {
var bounds = new L.LatLngBounds([-90, -180], [90, 180]);
var miniMapZoom = 5;
@@ -424,9 +428,6 @@ export class Map extends L.Map {
#onDoubleClick(e: any) {
this.deselectAllCoalitionAreas();
-
- var db = groundUnitDatabase;
- db.generateTestGrid(this.getMouseCoordinates())
}
#onContextMenu(e: any) {
diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts
index de64dab7..36ef8280 100644
--- a/client/src/other/utils.ts
+++ b/client/src/other/utils.ts
@@ -280,9 +280,17 @@ export function getMarkerCategoryByName(name: string) {
else if (helicopterDatabase.getByName(name) != null)
return "helicopter";
else if (groundUnitDatabase.getByName(name) != null){
- // TODO this is very messy
var type = groundUnitDatabase.getByName(name)?.type;
- return (type?.includes("SAM")) ? "groundunit-sam" : "groundunit-other";
+ if (type === "SAM")
+ return "groundunit-sam";
+ else if (type === "SAM Search radar" || type === "SAM Track radar" || type === "SAM Search/Track radar")
+ return "groundunit-sam-radar";
+ else if (type === "SAM Launcher")
+ return "groundunit-sam-launcher";
+ else if (type === "Radar")
+ return "groundunit-ewr";
+ else
+ return "groundunit-other";
}
else
return "groundunit-other"; // TODO add other unit types
diff --git a/client/src/panels/connectionstatuspanel.ts b/client/src/panels/connectionstatuspanel.ts
index 54a3f7f6..686595c2 100644
--- a/client/src/panels/connectionstatuspanel.ts
+++ b/client/src/panels/connectionstatuspanel.ts
@@ -8,4 +8,12 @@ export class ConnectionStatusPanel extends Panel {
update(connected: boolean) {
this.getElement().toggleAttribute( "data-is-connected", connected );
}
+
+ setMetrics(frameRate: number, load: number) {
+ const dt = this.getElement().querySelector("dt");
+ if (dt) {
+ dt.dataset["framerate"] = String(frameRate);
+ dt.dataset["load"] = String(load);
+ }
+ }
}
\ No newline at end of file
diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts
index d25d7404..1232ce45 100644
--- a/client/src/panels/unitcontrolpanel.ts
+++ b/client/src/panels/unitcontrolpanel.ts
@@ -141,7 +141,7 @@ export class UnitControlPanel extends Panel {
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1);
/* Flight controls */
- var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
+ var desiredAltitude = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});
diff --git a/client/src/server/server.ts b/client/src/server/server.ts
index 26841dfb..afa39e74 100644
--- a/client/src/server/server.ts
+++ b/client/src/server/server.ts
@@ -18,7 +18,7 @@ var username = "";
var password = "";
var sessionHash: string | null = null;
-var lastUpdateTime = 0;
+var lastUpdateTimes: {[key: string]: number} = {}
var demoEnabled = false;
export function toggleDemoEnabled() {
@@ -54,9 +54,14 @@ export function GET(callback: CallableFunction, uri: string, options?: { time?:
/* Success */
setConnected(true);
if (xmlHttp.responseType == 'arraybuffer')
- callback(xmlHttp.response);
- else
- callback(JSON.parse(xmlHttp.responseText));
+ lastUpdateTimes[uri] = callback(xmlHttp.response);
+ else {
+ const result = JSON.parse(xmlHttp.responseText);
+ lastUpdateTimes[uri] = callback(result);
+
+ if ("frameRate" in result && "load" in result)
+ getConnectionStatusPanel().setMetrics(result.frameRate, result.load);
+ }
} else if (xmlHttp.status == 401) {
/* Bad credentials */
console.error("Incorrect username/password");
@@ -103,10 +108,6 @@ export function setAddress(address: string, port: number) {
console.log(`Setting REST address to ${REST_ADDRESS}`)
}
-export function setLastUpdateTime(newLastUpdateTime: number) {
- lastUpdateTime = newLastUpdateTime;
-}
-
export function getAirbases(callback: CallableFunction) {
GET(callback, AIRBASES_URI);
}
@@ -115,8 +116,8 @@ export function getBullseye(callback: CallableFunction) {
GET(callback, BULLSEYE_URI);
}
-export function getLogs(callback: CallableFunction) {
- GET(callback, LOGS_URI);
+export function getLogs(callback: CallableFunction, refresh: boolean = false) {
+ GET(callback, LOGS_URI, { time: refresh ? 0 : lastUpdateTimes[LOGS_URI]});
}
export function getMission(callback: CallableFunction) {
@@ -124,7 +125,7 @@ export function getMission(callback: CallableFunction) {
}
export function getUnits(callback: CallableFunction, refresh: boolean = false) {
- GET(callback, `${UNITS_URI}`, { time: refresh ? 0 : lastUpdateTime }, 'arraybuffer');
+ GET(callback, UNITS_URI, { time: refresh ? 0 : lastUpdateTimes[UNITS_URI] }, 'arraybuffer');
}
export function addDestination(ID: number, path: any) {
@@ -327,7 +328,7 @@ export function startUpdate() {
getMissionData()?.update(data);
checkSessionHash(data.sessionHash);
});
- getUnits((buffer: ArrayBuffer) => getUnitsManager()?.update(buffer), true /* Does a full refresh */);
+ getUnits((buffer: ArrayBuffer) => {return getUnitsManager()?.update(buffer), true /* Does a full refresh */});
requestUpdate();
requestRefresh();
@@ -336,7 +337,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) => { return getUnitsManager()?.update(buffer); }, false);
}
window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
@@ -348,6 +349,13 @@ export function requestRefresh() {
if (!getPaused()) {
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
+ getLogs((data: any) => {
+ for (let key in data.logs) {
+ if (key != "requestTime")
+ console.log(data.logs[key]);
+ }
+ return data.time;
+ });
getMission((data: any) => {
checkSessionHash(data.sessionHash);
getMissionData()?.update(data)
diff --git a/client/src/units/groundunitdatabase.ts b/client/src/units/groundunitdatabase.ts
index acc10dcc..1f635904 100644
--- a/client/src/units/groundunitdatabase.ts
+++ b/client/src/units/groundunitdatabase.ts
@@ -12,7 +12,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-2 SAM Battery",
"range": "Long",
"filename": "",
- "type": "SAM Sites"
+ "type": "SAM Site"
},
"SA-3 SAM Battery": {
"name": "SA-3 SAM Battery",
@@ -22,7 +22,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-3 SAM Battery",
"range": "Medium",
"filename": "",
- "type": "SAM Sites"
+ "type": "SAM Site"
},
"SA-6 SAM Battery": {
"name": "SA-6 SAM Battery",
@@ -32,7 +32,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-6 SAM Battery",
"range": "Medium",
"filename": "",
- "type": "SAM Sites"
+ "type": "SAM Site"
},
"SA-10 SAM Battery": {
"name": "SA-10 SAM Battery",
@@ -42,7 +42,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-10 SAM Battery",
"range": "Long",
"filename": "",
- "type": "SAM Sites"
+ "type": "SAM Site"
},
"SA-11 SAM Battery": {
"name": "SA-11 SAM Battery",
@@ -52,7 +52,17 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-11 SAM Battery",
"range": "Medium",
"filename": "",
- "type": "SAM Sites"
+ "type": "SAM Site"
+ },
+ "SA-5 SAM Battery": {
+ "name": "SA-5 SAM Battery",
+ "coalition": "Red",
+ "era": "Mid Cold War",
+ "label": "SA-5 SAM Battery",
+ "shortLabel": "SA-5 SAM Battery",
+ "range": "Long",
+ "filename": "",
+ "type": "SAM Site"
},
"Patriot site": {
"name": "Patriot site",
@@ -62,7 +72,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Patriot site",
"range": "Long",
"filename": "",
- "type": "SAM Sites"
+ "type": "SAM Site"
},
"Hawk SAM Battery": {
"name": "Hawk SAM Battery",
@@ -72,7 +82,25 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Hawk SAM Battery",
"range": "Medium",
"filename": "",
- "type": "SAM Sites"
+ "type": "SAM Site"
+ },
+ "SNR_75V": {
+ "name": "SNR_75V",
+ "coalition": "Red",
+ "era": "Early Cold War",
+ "label": "SA-2 Fan Song",
+ "shortLabel": "SNR 75V",
+ "filename": "",
+ "type": "SAM Track radar"
+ },
+ "S_75M_Volhov": {
+ "name": "S_75M_Volhov",
+ "coalition": "Red",
+ "era": "Early Cold War",
+ "label": "SA-2 Launcher",
+ "shortLabel": "S75M Volhov",
+ "filename": "",
+ "type": "SAM Launcher"
},
"2B11 mortar": {
"name": "2B11 mortar",
@@ -457,61 +485,61 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Kub 2P25 ln",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-6 Kub 2P25 ln",
+ "label": "SA-6 Launcher",
"shortLabel": "Kub 2P25 ln",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Launcher"
},
"5p73 s-125 ln": {
"name": "5p73 s-125 ln",
"coalition": "Red",
"era": "Early Cold War",
- "label": "SA-3 5p73 s-125 ln",
+ "label": "SA-3 Launcher",
"shortLabel": "5p73 s-125 ln",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Launcher"
},
"S-300PS 5P85C ln": {
"name": "S-300PS 5P85C ln",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-10 S-300PS 5P85C ln",
+ "label": "SA-10 Launcher (5P85C)",
"shortLabel": "S-300PS 5P85C ln",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Launcher"
},
"S-300PS 5P85D ln": {
"name": "S-300PS 5P85D ln",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-10 S-300PS 5P85D ln",
+ "label": "SA-10 Launcher (5P85D)",
"shortLabel": "S-300PS 5P85D ln",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Launcher"
},
"SA-11 Buk LN 9A310M1": {
"name": "SA-11 Buk LN 9A310M1",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-11 Buk LN 9A310M1",
+ "label": "SA-11 Launcher",
"shortLabel": "SA-11 Buk LN 9A310M1",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Launcher"
},
"Osa 9A33 ln": {
"name": "Osa 9A33 ln",
"coalition": "Red",
"era": "Mid Cold War",
- "label": "SA-8 Osa 9A33 ln",
+ "label": "SA-8 Launcher",
"shortLabel": "Osa 9A33 ln",
"range": "Short",
"filename": "",
- "type": "SAM"
+ "type": "SAM Launcher"
},
"Tor 9A331": {
"name": "Tor 9A331",
@@ -547,11 +575,11 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "SA-11 Buk CC 9S470M1",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-11 Buk CC 9S470M1",
+ "label": "SA-11 Command Post",
"shortLabel": "SA-11 Buk CC 9S470M1",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Support vehicle"
},
"SA-8 Osa LD 9T217": {
"name": "SA-8 Osa LD 9T217",
@@ -567,21 +595,21 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Patriot AMG",
"coalition": "Blue",
"era": "Modern",
- "label": "Patriot AMG",
+ "label": "Patriot Antenna Mast Group",
"shortLabel": "Patriot AMG",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Support vehicle"
},
"Patriot ECS": {
"name": "Patriot ECS",
"coalition": "Blue",
"era": "Modern",
- "label": "Patriot ECS",
+ "label": "Patriot Engagement Control Station",
"shortLabel": "Patriot ECS",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Support vehicle"
},
"Gepard": {
"name": "Gepard",
@@ -596,11 +624,11 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Hawk pcp",
"coalition": "Blue",
"era": "Late Cold War",
- "label": "Hawk pcp",
+ "label": "Hawk Platoon Command Post",
"shortLabel": "Hawk pcp",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Support vehicle"
},
"SA-18 Igla manpad": {
"name": "SA-18 Igla manpad",
@@ -632,6 +660,36 @@ export class GroundUnitDatabase extends UnitDatabase {
"filename": "",
"type": "MANPADS"
},
+ "RPC_5N62V": {
+ "name": "RPC_5N62V",
+ "coalition": "Red",
+ "era": "Mid Cold War",
+ "label": "SA-5 Square Pair",
+ "shortLabel": "RPC 5N62V",
+ "range": "Long",
+ "filename": "",
+ "type": "SAM Track radar"
+ },
+ "RLS_19J6": {
+ "name": "RLS_19J6",
+ "coalition": "Red",
+ "era": "Mid Cold War",
+ "label": "SA-5 Thin Shield",
+ "shortLabel": "RLS 19J6",
+ "range": "Long",
+ "filename": "",
+ "type": "SAM Search radar"
+ },
+ "S-200_Launcher": {
+ "name": "S-200_Launcher",
+ "coalition": "Red",
+ "era": "Mid Cold War",
+ "label": "SA-5 Launcher",
+ "shortLabel": "S-200 Launcher",
+ "range": "Long",
+ "filename": "",
+ "type": "SAM Launcher"
+ },
"Vulcan": {
"name": "Vulcan",
"coalition": "Blue",
@@ -645,10 +703,10 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Hawk ln",
"coalition": "Blue",
"era": "Late Cold War",
- "label": "Hawk ln",
+ "label": "Hawk Launcher",
"shortLabel": "Hawk ln",
"filename": "",
- "type": "SAM"
+ "type": "SAM Launcher"
},
"M48 Chaparral": {
"name": "M48 Chaparral",
@@ -672,11 +730,11 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Patriot ln",
"coalition": "Blue",
"era": "Late Cold War",
- "label": "Patriot ln",
+ "label": "Patriot Launcher",
"shortLabel": "Patriot ln",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Launcher"
},
"M1097 Avenger": {
"name": "M1097 Avenger",
@@ -691,21 +749,21 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Patriot EPP",
"coalition": "Blue",
"era": "Late Cold War",
- "label": "Patriot EPP",
+ "label": "Patriot Electric Power Plant",
"shortLabel": "Patriot EPP",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Support vehicle"
},
"Patriot cp": {
"name": "Patriot cp",
"coalition": "Blue",
"era": "Late Cold War",
- "label": "Patriot cp",
+ "label": "Patriot Command Post",
"shortLabel": "Patriot cp",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Support vehicle"
},
"Roland ADS": {
"name": "Roland ADS",
@@ -720,11 +778,11 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "S-300PS 54K6 cp",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-10 S-300PS 54K6 cp",
+ "label": "SA-10 Command Post",
"shortLabel": "S-300PS 54K6 cp",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Support vehicle"
},
"Stinger manpad GRG": {
"name": "Stinger manpad GRG",
@@ -734,7 +792,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Stinger manpad GRG",
"range": "Short",
"filename": "",
- "type": "SAM"
+ "type": "MANPADS"
},
"Stinger manpad dsr": {
"name": "Stinger manpad dsr",
@@ -754,7 +812,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Stinger comm dsr",
"range": "Short",
"filename": "",
- "type": "SAM"
+ "type": "MANPADS"
},
"Stinger manpad": {
"name": "Stinger manpad",
@@ -774,7 +832,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Stinger comm",
"range": "Short",
"filename": "",
- "type": "SAM"
+ "type": "MANPADS"
},
"ZSU-23-4 Shilka": {
"name": "ZSU-23-4 Shilka",
@@ -843,7 +901,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "1L13 EWR",
"coalition": "Red",
"era": "Late Cold War",
- "label": "1L13 EWR",
+ "label": "Box Spring",
"shortLabel": "1L13 EWR",
"filename": "",
"type": "Radar"
@@ -852,37 +910,37 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Kub 1S91 str",
"coalition": "Red",
"era": "Mid Cold War",
- "label": "SA-6 Kub 1S91 str",
+ "label": "SA-6 Straight flush",
"shortLabel": "Kub 1S91 str",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search/Track radar"
},
"S-300PS 40B6M tr": {
"name": "S-300PS 40B6M tr",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-10 S-300PS 40B6M tr",
+ "label": "SA-10 Tin Shield",
"shortLabel": "S-300PS 40B6M tr",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Track radar"
},
"S-300PS 40B6MD sr": {
"name": "S-300PS 40B6MD sr",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-10 S-300PS 40B6MD sr",
+ "label": "SA-10 Clam Shell",
"shortLabel": "S-300PS 40B6MD sr",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search radar"
},
"55G6 EWR": {
"name": "55G6 EWR",
"coalition": "Red",
"era": "Early Cold War",
- "label": "55G6 EWR",
+ "label": "Tall Rack",
"shortLabel": "55G6 EWR",
"filename": "",
"type": "Radar"
@@ -891,98 +949,98 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "S-300PS 64H6E sr",
"coalition": "Red",
"era": "Late Cold War",
- "label": "SA-10 S-300PS 64H6E sr",
+ "label": "SA-10 Big Bird",
"shortLabel": "S-300PS 64H6E sr",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search radar"
},
"SA-11 Buk SR 9S18M1": {
"name": "SA-11 Buk SR 9S18M1",
"coalition": "Red",
"era": "Mid Cold War",
- "label": "SA-11 Buk SR 9S18M1",
+ "label": "SA-11 Snown Drift",
"shortLabel": "SA-11 Buk SR 9S18M1",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search radar"
},
"Dog Ear radar": {
"name": "Dog Ear radar",
"coalition": "Red",
"era": "Mid Cold War",
- "label": "Dog Ear radar",
+ "label": "Dog Ear",
"shortLabel": "Dog Ear radar",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search radar"
},
"Hawk tr": {
"name": "Hawk tr",
"coalition": "Blue",
"era": "Early Cold War",
- "label": "Hawk tr",
+ "label": "Hawk Track radar",
"shortLabel": "Hawk tr",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Track radar"
},
"Hawk sr": {
"name": "Hawk sr",
"coalition": "Blue",
"era": "Early Cold War",
- "label": "Hawk sr",
+ "label": "Hawk Search radar",
"shortLabel": "Hawk sr",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search radar"
},
"Patriot str": {
"name": "Patriot str",
"coalition": "Blue",
"era": "Late Cold War",
- "label": "Patriot str",
+ "label": "Patriot Search/Track radar",
"shortLabel": "Patriot str",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search/Track radar"
},
"Hawk cwar": {
"name": "Hawk cwar",
"coalition": "Blue",
"era": "Early Cold War",
- "label": "Hawk cwar",
+ "label": "Hawk Continous Wave Acquisition Radar",
"shortLabel": "Hawk cwar",
"range": "Long",
"filename": "",
- "type": "SAM"
+ "type": "SAM Track radar"
},
"p-19 s-125 sr": {
"name": "p-19 s-125 sr",
"coalition": "Red",
"era": "Mid Cold War",
- "label": "SA-3 p-19 s-125 sr",
- "shortLabel": "p-19 s-125 sr",
+ "label": "SA-3 Flat Face B",
+ "shortLabel": "Flat Face B",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search radar"
},
"Roland Radar": {
"name": "Roland Radar",
"coalition": "Blue",
"era": "Mid Cold War",
- "label": "Roland Radar",
+ "label": "Roland Search radar",
"shortLabel": "Roland Radar",
"filename": "",
- "type": "SAM"
+ "type": "SAM Search radar"
},
"snr s-125 tr": {
"name": "snr s-125 tr",
"coalition": "Red",
"era": "Early Cold War",
- "label": "SA-3 snr s-125 tr",
+ "label": "SA-3 Low Blow",
"shortLabel": "snr s-125 tr",
"range": "Medium",
"filename": "",
- "type": "SAM"
+ "type": "SAM Track radar"
},
"house1arm": {
"name": "house1arm",
diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts
index fc5f13a8..efaa85ab 100644
--- a/client/src/units/unit.ts
+++ b/client/src/units/unit.ts
@@ -9,6 +9,8 @@ import { TargetMarker } from '../map/targetmarker';
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';
+import { groundUnitDatabase } from './groundunitdatabase';
+import { navyUnitDatabase } from './navyunitdatabase';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@@ -74,6 +76,7 @@ export class Unit extends CustomMarker {
#ammo: Ammo[] = [];
#contacts: Contact[] = [];
#activePath: LatLng[] = [];
+ #isLeader: boolean = false;
#selectable: boolean;
#selected: boolean = false;
@@ -126,6 +129,7 @@ export class Unit extends CustomMarker {
getAmmo() {return this.#ammo};
getContacts() {return this.#contacts};
getActivePath() {return this.#activePath};
+ getIsLeader() {return this.#isLeader};
static getConstructor(type: string) {
if (type === "GroundUnit") return GroundUnit;
@@ -162,6 +166,8 @@ export class Unit extends CustomMarker {
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
});
+
+ getMap().on("zoomend", () => {this.#onZoom();})
}
getCategory() {
@@ -212,8 +218,9 @@ export class Unit extends CustomMarker {
case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break;
case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break;
case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break;
- case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); break;
+ case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", {detail: this})); break;
case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break;
+ case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); break;
}
}
@@ -223,25 +230,20 @@ export class Unit extends CustomMarker {
if (updateMarker)
this.#updateMarker();
- document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
+ if (this.getSelected() || getMap().getCenterUnit() === this)
+ document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
}
drawLines() {
- // TODO dont delete the polylines of the detected units
- this.#clearContacts();
- if (this.getSelected()) {
- this.#drawPath();
- this.#drawContacts();
- this.#drawTarget();
- }
- else {
- this.#clearPath();
- this.#clearTarget();
- }
+ this.#drawPath();
+ this.#drawContacts();
+ this.#drawTarget();
}
getData() {
return {
+ category: this.getCategory(),
+ ID: this.ID,
alive: this.#alive,
human: this.#human,
controlled: this.#controlled,
@@ -277,7 +279,8 @@ export class Unit extends CustomMarker {
generalSettings: this.#generalSettings,
ammo: this.#ammo,
contacts: this.#contacts,
- activePath: this.#activePath
+ activePath: this.#activePath,
+ isLeader: this.#isLeader
}
}
@@ -315,7 +318,7 @@ export class Unit extends CustomMarker {
/* Only alive units can be selected. Some units are not selectable (weapons) */
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) {
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
this.#updateMarker();
@@ -326,6 +329,15 @@ export class Unit extends CustomMarker {
this.#clearPath();
this.#clearTarget();
}
+
+ this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
+ if (getMap().getZoom() < 13) {
+ if (this.#isLeader)
+ this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
+ else
+ this.#updateMarker();
+ }
+
}
}
@@ -376,6 +388,10 @@ export class Unit extends CustomMarker {
return true;
}
+ getType() {
+ return "";
+ }
+
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
@@ -478,21 +494,16 @@ export class Unit extends CustomMarker {
/********************** Visibility *************************/
updateVisibility() {
- var hidden = false;
const hiddenUnits = getUnitsManager().getHiddenTypes();
- if (this.#human && hiddenUnits.includes("human"))
- hidden = true;
- if (this.#controlled == false && hiddenUnits.includes("dcs"))
- hidden = true;
- if (hiddenUnits.includes(this.getMarkerCategory()))
- hidden = true;
- if (hiddenUnits.includes(this.#coalition))
- hidden = true;
- if (getUnitsManager().getCommandMode() === HIDE_ALL)
- hidden = true;
- if (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0) {
- hidden = true;
- }
+ var hidden = ((this.#human && hiddenUnits.includes("human")) ||
+ (this.#controlled == false && hiddenUnits.includes("dcs")) ||
+ (hiddenUnits.includes(this.getMarkerCategory())) ||
+ (hiddenUnits.includes(this.#coalition)) ||
+ (getUnitsManager().getCommandMode() === HIDE_ALL) ||
+ (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0) ||
+ (!this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13)) &&
+ !(this.getSelected());
+
this.setHidden(hidden || !this.#alive);
}
@@ -718,7 +729,7 @@ export class Unit extends CustomMarker {
}
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
if (this.getCategory() == "Aircraft") {
- options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR
+ options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR
}
}
@@ -730,10 +741,13 @@ export class Unit extends CustomMarker {
}
if ((selectedUnits.length === 0 && this.getCategory() == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) {
- if (selectedUnits.concat([this]).every((unit: Unit) => { return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"]) }))
+ if (selectedUnits.concat([this]).every((unit: Unit) => { return ["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"].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." };
+
if (Object.keys(options).length > 0) {
getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
@@ -750,6 +764,8 @@ export class Unit extends CustomMarker {
getUnitsManager().selectedUnitsAttackUnit(this.ID);
else if (action === "refuel")
getUnitsManager().selectedUnitsRefuel();
+ else if (action === "group")
+ getUnitsManager().selectedUnitsCreateGroup();
else if (action === "follow")
this.#showFollowOptions(e);
else if (action === "bomb")
@@ -758,6 +774,7 @@ export class Unit extends CustomMarker {
getMap().setState(CARPET_BOMBING);
else if (action === "fire-at-area")
getMap().setState(FIRE_AT_AREA);
+
}
#showFollowOptions(e: any) {
@@ -949,10 +966,11 @@ export class Unit extends CustomMarker {
}
#drawContacts() {
+ this.#clearContacts();
for (let index in this.#contacts) {
var contactData = this.#contacts[index];
var contact = getUnitsManager().getUnitByID(contactData.ID)
- if (contact != null) {
+ if (contact != null && contact.getAlive()) {
var startLatLng = new LatLng(this.#position.lat, this.#position.lng);
var endLatLng: LatLng;
if (contactData.detectionMethod === RWR) {
@@ -1017,6 +1035,10 @@ export class Unit extends CustomMarker {
if (getMap().hasLayer(this.#targetPositionPolyline))
this.#targetPositionPolyline.removeFrom(getMap());
}
+
+ #onZoom() {
+ this.#updateMarker();
+ }
}
export class AirUnit extends Unit {
@@ -1079,6 +1101,11 @@ export class GroundUnit extends Unit {
getCategory() {
return "GroundUnit";
}
+
+ getType() {
+ var blueprint = groundUnitDatabase.getByName(this.getName());
+ return blueprint?.type? blueprint.type: "";
+ }
}
export class NavyUnit extends Unit {
@@ -1108,6 +1135,11 @@ export class NavyUnit extends Unit {
getCategory() {
return "NavyUnit";
}
+
+ getType() {
+ var blueprint = navyUnitDatabase.getByName(this.getName());
+ return blueprint?.type? blueprint.type: "";
+ }
}
export class Weapon extends Unit {
@@ -1115,21 +1147,6 @@ export class Weapon extends Unit {
super(ID);
this.setSelectable(false);
}
-
- getIconOptions() {
- return {
- showState: false,
- showVvi: false,
- showHotgroup: false,
- showUnitIcon: true,
- showShortLabel: false,
- showFuel: false,
- showAmmo: false,
- showSummary: false,
- showCallsign: false,
- rotateToHeading: true
- };
- }
}
export class Missile extends Weapon {
@@ -1142,7 +1159,25 @@ export class Missile extends Weapon {
}
getMarkerCategory() {
- return "missile";
+ if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC))
+ return "missile";
+ else
+ return "aircraft";
+ }
+
+ getIconOptions() {
+ return {
+ showState: false,
+ showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
+ showHotgroup: false,
+ showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
+ showShortLabel: false,
+ showFuel: false,
+ showAmmo: false,
+ showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
+ showCallsign: false,
+ rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)
+ };
}
}
@@ -1156,6 +1191,24 @@ export class Bomb extends Weapon {
}
getMarkerCategory() {
- return "bomb";
+ if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC))
+ return "bomb";
+ else
+ return "aircraft";
+ }
+
+ getIconOptions() {
+ return {
+ showState: false,
+ showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
+ showHotgroup: false,
+ showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
+ showShortLabel: false,
+ showFuel: false,
+ showAmmo: false,
+ showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
+ showCallsign: false,
+ rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)
+ };
}
}
diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts
index 0041aa0d..6288918e 100644
--- a/client/src/units/unitsmanager.ts
+++ b/client/src/units/unitsmanager.ts
@@ -1,7 +1,7 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getHotgroupPanel, getInfoPopup, getMap } from "..";
import { Unit } from "./unit";
-import { cloneUnit, setLastUpdateTime, spawnAircrafts, spawnGroundUnits } from "../server/server";
+import { cloneUnit, deleteUnit, spawnAircrafts, spawnGroundUnits } 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";
@@ -12,11 +12,12 @@ import { citiesDatabase } from "./citiesdatabase";
export class UnitsManager {
#units: { [ID: number]: Unit };
- #copiedUnits: Unit[];
+ #copiedUnits: any[];
#selectionEventDisabled: boolean = false;
#pasteDisabled: boolean = false;
#hiddenTypes: string[] = [];
#commandMode: string = HIDE_ALL;
+ #requestDetectionUpdate: boolean = false;
constructor() {
this.#units = {};
@@ -31,6 +32,7 @@ export class UnitsManager {
document.addEventListener('keyup', (event) => this.#onKeyUp(event));
document.addEventListener('exportToFile', () => this.exportToFile());
document.addEventListener('importFromFile', () => this.importFromFile());
+ document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true});
}
getSelectableAircraft() {
@@ -91,17 +93,21 @@ 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));
+ if (this.#requestDetectionUpdate) {
+ for (let ID in this.#units) {
+ var unit = this.#units[ID];
+ if (!unit.belongsToCommandedCoalition())
+ unit.setDetectionMethods(this.getUnitDetectedMethods(unit));
+ }
+ this.#requestDetectionUpdate = false;
}
- setLastUpdateTime(updateTime);
-
for (let ID in this.#units) {
- this.#units[ID].drawLines();
+ if (this.#units[ID].getSelected())
+ this.#units[ID].drawLines();
};
+
+ return updateTime;
}
setHiddenType(key: string, value: boolean) {
@@ -188,36 +194,59 @@ export class UnitsManager {
}
getSelectedUnitsTypes() {
- if (this.getSelectedUnits().length == 0)
+ const selectedUnits = this.getSelectedUnits();
+ if (selectedUnits.length == 0)
return [];
- return this.getSelectedUnits().map((unit: Unit) => {
- return unit.constructor.name
+ return selectedUnits.map((unit: Unit) => {
+ return unit.getCategory();
})?.filter((value: any, index: any, array: string[]) => {
return array.indexOf(value) === index;
});
};
+ /* Gets the value of a variable from the selected units. If all the units have the same value, returns the value, else returns undefined */
getSelectedUnitsVariable(variableGetter: CallableFunction) {
- if (this.getSelectedUnits().length == 0)
+ const selectedUnits = this.getSelectedUnits();
+ if (selectedUnits.length == 0)
return undefined;
- return this.getSelectedUnits().map((unit: Unit) => {
+ return selectedUnits.map((unit: Unit) => {
return variableGetter(unit);
})?.reduce((a: any, b: any) => {
- return a == b ? a : undefined
+ return a === b ? a : undefined
});
};
-
getSelectedUnitsCoalition() {
- if (this.getSelectedUnits().length == 0)
+ const selectedUnits = this.getSelectedUnits();
+ if (selectedUnits.length == 0)
return undefined;
- return this.getSelectedUnits().map((unit: Unit) => {
+ return selectedUnits.map((unit: Unit) => {
return unit.getCoalition()
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
+ getByType(type: string) {
+ Object.values(this.getUnits()).filter((unit: Unit) => {
+ return unit.getType() === type;
+ })
+ }
+
+ getUnitDetectedMethods(unit: Unit) {
+ var detectionMethods: number[] = [];
+ for (let idx in this.#units) {
+ if (this.#units[idx].getAlive() && this.#units[idx].getIsLeader() && 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;
+ }
+
/*********************** Actions on selected units ************************/
selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
@@ -227,7 +256,7 @@ export class UnitsManager {
if (mantainRelativePosition)
unitDestinations = this.selectedUnitsComputeGroupDestination(latlng, rotation);
else
- selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng });
+ selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng; });
for (let idx in selectedUnits) {
const unit = selectedUnits[idx];
@@ -515,36 +544,66 @@ 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 (this.#units[idx].getAlive() && contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod))
- detectionMethods.push(contact.detectionMethod);
- });
- }
+ // TODO add undo group
+ selectedUnitsCreateGroup() {
+ var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: false });
+ var units = [];
+ var coalition = "neutral";
+ for (let idx in selectedUnits) {
+ var unit = selectedUnits[idx];
+ coalition = unit.getCoalition();
+ deleteUnit(unit.ID, false, true);
+ units.push({unitType: unit.getName(), location: unit.getPosition()});
}
- return detectionMethods;
+ const category = this.getSelectedUnitsTypes()[0];
+ this.spawnUnit(category, units, coalition, true);
}
/***********************************************/
copyUnits() {
- this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
- this.#showActionMessage(this.#copiedUnits, `copied`);
+ this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => {return unit.getData()}))); /* Can be applied to humans too */
+ getInfoPopup().setText(`${this.#copiedUnits.length} units copied`);
}
pasteUnits() {
if (!this.#pasteDisabled) {
+ /* Compute the position of the center of the copied units */
+ var nUnits = this.#copiedUnits.length;
+ var avgLat = 0;
+ var avgLng = 0;
for (let idx in this.#copiedUnits) {
var unit = this.#copiedUnits[idx];
- //getMap().addTemporaryMarker(getMap().getMouseCoordinates());
- cloneUnit(unit.ID, getMap().getMouseCoordinates());
- this.#showActionMessage(this.#copiedUnits, `pasted`);
+ avgLat += unit.position.lat / nUnits;
+ avgLng += unit.position.lng / nUnits;
}
- this.#pasteDisabled = true;
- window.setTimeout(() => this.#pasteDisabled = false, 250);
+
+ /* Organize the copied units in groups */
+ var groups: {[key: string]: any} = {};
+ this.#copiedUnits.forEach((unit: any) => {
+ if (!(unit.groupName in groups))
+ groups[unit.groupName] = [];
+ groups[unit.groupName].push(unit);
+ });
+
+ for (let groupName in groups) {
+ /* Paste the units as groups. Only for ground and navy units because of loadouts, TODO: find a better solution so it works for them too*/
+ if (!["Aircraft", "Helicopter"].includes(groups[groupName][0].category)) {
+ var units = groups[groupName].map((unit: any) => {
+ var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
+ getMap().addTemporaryMarker(position, unit.name, unit.coalition);
+ return {unitType: unit.name, location: position};
+ });
+ this.spawnUnit(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
+ }
+ else {
+ groups[groupName].forEach((unit: any) => {
+ var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
+ getMap().addTemporaryMarker(position, unit.name, unit.coalition);
+ cloneUnit(unit.ID, position);
+ });
+ }
+ }
+ getInfoPopup().setText(`${this.#copiedUnits.length - 1} units pasted`);
}
}
@@ -581,7 +640,6 @@ export class UnitsManager {
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(data);
else
@@ -609,7 +667,7 @@ export class UnitsManager {
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}});
+ var units = groups[groupName].filter((unit: any) => {return unit.alive}).map((unit: any) => {return {unitType: unit.name, location: unit.position}});
spawnGroundUnits(units, groups[groupName][0].coalition, true);
}
}
@@ -629,8 +687,11 @@ export class UnitsManager {
/***********************************************/
#onKeyUp(event: KeyboardEvent) {
- if (!keyEventWasInInput(event) && event.key === "Delete" ) {
- this.selectedUnitsDelete();
+ if (!keyEventWasInInput(event)) {
+ if (event.key === "Delete")
+ this.selectedUnitsDelete();
+ else if (event.key === "a" && event.ctrlKey)
+ Object.values(this.getUnits()).filter((unit: Unit) => {return !unit.getHidden()}).forEach((unit: Unit) => unit.setSelected(true));
}
}
@@ -661,7 +722,7 @@ export class UnitsManager {
document.dispatchEvent(new CustomEvent("unitsDeselection", { detail: this.getSelectedUnits() }));
}
- #showActionMessage(units: Unit[], message: string) {
+ #showActionMessage(units: any[], message: string) {
if (units.length == 1)
getInfoPopup().setText(`${units[0].getUnitName()} ${message}`);
else if (units.length > 1)
diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs
index 9bb8e91b..873216b2 100644
--- a/client/views/other/contextmenus.ejs
+++ b/client/views/other/contextmenus.ejs
@@ -15,10 +15,10 @@