From 25e2c5043841711b2f42e02dce92ae0975548eb3 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Tue, 14 Nov 2023 17:43:24 +0100 Subject: [PATCH] Added unit marker to database and implemented better grouping --- client/@types/olympus/index.d.ts | 3 +- client/demo.js | 75 +- client/plugins/databasemanager/index.js | 45 +- .../databasemanager/src/grounduniteditor.ts | 1 + client/plugins/databasemanager/src/utils.ts | 4 +- client/plugins/databasemanager/style.css | 19 +- .../databases/units/groundunitdatabase.json | 717 ++++++++++++------ client/public/stylesheets/markers/units.css | 7 +- .../olympus/images/units/groundunit-aaa.svg | 4 +- .../olympus/images/units/groundunit-apc.svg | 9 + .../images/units/groundunit-artillery.svg | 2 + .../images/units/groundunit-infantry.svg | 6 + .../images/units/groundunit-tactical.svg | 2 + .../olympus/images/units/groundunit-tank.svg | 9 + .../olympus/images/units/groundunit-truck.svg | 6 + .../{groundunit-other.svg => groundunit.svg} | 0 client/src/constants/constants.ts | 4 +- client/src/interfaces.ts | 1 + client/src/map/map.ts | 6 + client/src/unit/unit.ts | 288 ++++--- 20 files changed, 785 insertions(+), 423 deletions(-) create mode 100644 client/public/themes/olympus/images/units/groundunit-apc.svg create mode 100644 client/public/themes/olympus/images/units/groundunit-artillery.svg create mode 100644 client/public/themes/olympus/images/units/groundunit-infantry.svg create mode 100644 client/public/themes/olympus/images/units/groundunit-tactical.svg create mode 100644 client/public/themes/olympus/images/units/groundunit-tank.svg create mode 100644 client/public/themes/olympus/images/units/groundunit-truck.svg rename client/public/themes/olympus/images/units/{groundunit-other.svg => groundunit.svg} (100%) diff --git a/client/@types/olympus/index.d.ts b/client/@types/olympus/index.d.ts index c5666bb1..6d698502 100644 --- a/client/@types/olympus/index.d.ts +++ b/client/@types/olympus/index.d.ts @@ -590,6 +590,7 @@ declare module "interfaces" { canAAA?: boolean; indirectFire?: boolean; markerFile?: string; + unitWhenGrouped?: string; } export interface UnitSpawnOptions { roleType: string; @@ -773,7 +774,7 @@ declare module "other/utils" { ranges?: string[]; eras?: string[]; }): UnitBlueprint | null; - export function getMarkerCategoryByName(name: string): "aircraft" | "helicopter" | "groundunit-sam" | "groundunit-other" | "groundunit-sam-radar" | "groundunit-sam-launcher" | "groundunit-ewr"; + export function getMarkerCategoryByName(name: string): "aircraft" | "helicopter" | "groundunit-other" | "navyunit" | "groundunit"; export function getUnitDatabaseByCategory(category: string): import("unit/databases/aircraftdatabase").AircraftDatabase | import("unit/databases/helicopterdatabase").HelicopterDatabase | import("unit/databases/groundunitdatabase").GroundUnitDatabase | import("unit/databases/navyunitdatabase").NavyUnitDatabase | null; export function base64ToBytes(base64: string): ArrayBufferLike; export function enumToState(state: number): string; diff --git a/client/demo.js b/client/demo.js index 751fab2d..9e560bee 100644 --- a/client/demo.js +++ b/client/demo.js @@ -10,7 +10,7 @@ const navyUnitDatabase = require('./public/databases/units/navyUnitDatabase.json const DEMO_UNIT_DATA = {} const DEMO_WEAPONS_DATA = { - ["1001"]:{ category: "Missile", alive: true, coalition: 2, name: "", position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 45 * Math.PI / 180 }, + /*["1001"]:{ category: "Missile", alive: true, coalition: 2, name: "", position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 45 * Math.PI / 180 }, */ } class DemoDataGenerator { @@ -52,6 +52,10 @@ class DemoDataGenerator { isLeader: true } + /* + + UNCOMMENT TO TEST ALL UNITS + var databases = Object.assign({}, aircraftDatabase, helicopterDatabase, groundUnitDatabase, navyUnitDatabase); var t = Object.keys(databases).length; var l = Math.floor(Math.sqrt(t)); @@ -60,28 +64,57 @@ class DemoDataGenerator { let idx = 1; console.log(l) for (let name in databases) { - DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData)); - DEMO_UNIT_DATA[idx].name = name; - DEMO_UNIT_DATA[idx].groupName = `Group-${idx}`; - DEMO_UNIT_DATA[idx].position.lat += latIdx / 5; - DEMO_UNIT_DATA[idx].position.lng += lngIdx / 5; - - latIdx += 1; - if (latIdx === l) { - latIdx = 0; - lngIdx += 1; - } + if (databases[name].enabled) { + DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData)); + DEMO_UNIT_DATA[idx].name = name; + DEMO_UNIT_DATA[idx].groupName = `Group-${idx}`; + DEMO_UNIT_DATA[idx].position.lat += latIdx / 5; + DEMO_UNIT_DATA[idx].position.lng += lngIdx / 5; + + latIdx += 1; + if (latIdx === l) { + latIdx = 0; + lngIdx += 1; + } - if (name in aircraftDatabase) - DEMO_UNIT_DATA[idx].category = "Aircraft"; - else if (name in helicopterDatabase) - DEMO_UNIT_DATA[idx].category = "Helicopter"; - else if (name in groundUnitDatabase) - DEMO_UNIT_DATA[idx].category = "GroundUnit"; - else if (name in navyUnitDatabase) - DEMO_UNIT_DATA[idx].category = "NavyUnit"; - idx += 1; + if (name in aircraftDatabase) + DEMO_UNIT_DATA[idx].category = "Aircraft"; + else if (name in helicopterDatabase) + DEMO_UNIT_DATA[idx].category = "Helicopter"; + else if (name in groundUnitDatabase) + DEMO_UNIT_DATA[idx].category = "GroundUnit"; + else if (name in navyUnitDatabase) + DEMO_UNIT_DATA[idx].category = "NavyUnit"; + + idx += 1; + } } + */ + + let idx = 1; + DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData)); + DEMO_UNIT_DATA[idx].name = "S_75M_Volhov"; + DEMO_UNIT_DATA[idx].groupName = `Group`; + DEMO_UNIT_DATA[idx].position.lat += idx / 100; + DEMO_UNIT_DATA[idx].category = "GroundUnit"; + DEMO_UNIT_DATA[idx].isLeader = true; + + idx += 1; + DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData)); + DEMO_UNIT_DATA[idx].name = "SNR_75V"; + DEMO_UNIT_DATA[idx].groupName = `Group`; + DEMO_UNIT_DATA[idx].position.lat += idx / 100; + DEMO_UNIT_DATA[idx].category = "GroundUnit"; + DEMO_UNIT_DATA[idx].isLeader = false; + + idx += 1; + DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData)); + DEMO_UNIT_DATA[idx].name = "Ural-4320 APA-5D"; + DEMO_UNIT_DATA[idx].groupName = `Group`; + DEMO_UNIT_DATA[idx].position.lat += idx / 100; + DEMO_UNIT_DATA[idx].category = "GroundUnit"; + DEMO_UNIT_DATA[idx].isLeader = false; + this.startTime = Date.now(); } diff --git a/client/plugins/databasemanager/index.js b/client/plugins/databasemanager/index.js index 5c5781b5..2e282c1a 100644 --- a/client/plugins/databasemanager/index.js +++ b/client/plugins/databasemanager/index.js @@ -508,7 +508,7 @@ class GroundUnitEditor extends uniteditor_1.UnitEditor { * @param blueprint The blueprint to edit */ setBlueprint(blueprint) { - var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v; + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w; __classPrivateFieldSet(this, _GroundUnitEditor_blueprint, blueprint, "f"); if (__classPrivateFieldGet(this, _GroundUnitEditor_blueprint, "f") !== null) { this.contentDiv2.replaceChildren(); @@ -519,28 +519,29 @@ class GroundUnitEditor extends uniteditor_1.UnitEditor { (0, utils_1.addStringInput)(this.contentDiv2, "Label", blueprint.label, "text", (value) => { blueprint.label = value; }); (0, utils_1.addStringInput)(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value) => { blueprint.shortLabel = value; }); (0, utils_1.addStringInput)(this.contentDiv2, "Type", (_a = blueprint.type) !== null && _a !== void 0 ? _a : "", "text", (value) => { blueprint.type = value; }); + (0, utils_1.addStringInput)(this.contentDiv2, "Unit when grouped", (_b = blueprint.unitWhenGrouped) !== null && _b !== void 0 ? _b : "", "text", (value) => { blueprint.unitWhenGrouped = value; }); (0, utils_1.addDropdownInput)(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value) => { blueprint.coalition = value; }); (0, utils_1.addDropdownInput)(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value) => { blueprint.era = value; }); //addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; }); - (0, utils_1.addStringInput)(this.contentDiv2, "Cost", (_b = String(blueprint.cost)) !== null && _b !== void 0 ? _b : "", "number", (value) => { blueprint.cost = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Acquisition range [m]", (_c = String(blueprint.acquisitionRange)) !== null && _c !== void 0 ? _c : "", "number", (value) => { blueprint.acquisitionRange = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Engagement range [m]", (_d = String(blueprint.engagementRange)) !== null && _d !== void 0 ? _d : "", "number", (value) => { blueprint.engagementRange = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Targeting range [m]", (_e = String(blueprint.targetingRange)) !== null && _e !== void 0 ? _e : "", "number", (value) => { blueprint.targetingRange = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Aim method range [m]", (_f = String(blueprint.aimMethodRange)) !== null && _f !== void 0 ? _f : "", "number", (value) => { blueprint.aimMethodRange = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Barrel height [m]", (_g = String(blueprint.barrelHeight)) !== null && _g !== void 0 ? _g : "", "number", (value) => { blueprint.barrelHeight = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Muzzle velocity [m/s]", (_h = String(blueprint.muzzleVelocity)) !== null && _h !== void 0 ? _h : "", "number", (value) => { blueprint.muzzleVelocity = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Aim time [s]", (_j = String(blueprint.aimTime)) !== null && _j !== void 0 ? _j : "", "number", (value) => { blueprint.aimTime = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Shots to fire", (_k = String(blueprint.shotsToFire)) !== null && _k !== void 0 ? _k : "", "number", (value) => { blueprint.shotsToFire = Math.round(parseFloat(value)); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Shots base interval [s]", (_l = String(blueprint.shotsBaseInterval)) !== null && _l !== void 0 ? _l : "", "number", (value) => { blueprint.shotsBaseInterval = Math.round(parseFloat(value)); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Shots base scatter [°]", (_m = String(blueprint.shotsBaseScatter)) !== null && _m !== void 0 ? _m : "", "number", (value) => { blueprint.shotsBaseScatter = Math.round(parseFloat(value)); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Alertness time constant [s]", (_o = String(blueprint.alertnessTimeConstant)) !== null && _o !== void 0 ? _o : "", "number", (value) => { blueprint.alertnessTimeConstant = Math.round(parseFloat(value)); }); - (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can target point", (_p = blueprint.canTargetPoint) !== null && _p !== void 0 ? _p : false, (value) => { blueprint.canTargetPoint = value; }); - (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can rearm", (_q = blueprint.canRearm) !== null && _q !== void 0 ? _q : false, (value) => { blueprint.canRearm = value; }); - (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can operate as AAA", (_r = blueprint.canAAA) !== null && _r !== void 0 ? _r : false, (value) => { blueprint.canAAA = value; }); - (0, utils_1.addCheckboxInput)(this.contentDiv2, "Indirect fire (e.g. mortar)", (_s = blueprint.indirectFire) !== null && _s !== void 0 ? _s : false, (value) => { blueprint.indirectFire = value; }); - (0, utils_1.addStringInput)(this.contentDiv2, "Description", (_t = blueprint.description) !== null && _t !== void 0 ? _t : "", "text", (value) => { blueprint.description = value; }); - (0, utils_1.addStringInput)(this.contentDiv2, "Tags", (_u = blueprint.tags) !== null && _u !== void 0 ? _u : "", "text", (value) => { blueprint.tags = value; }); - (0, utils_1.addStringInput)(this.contentDiv2, "Marker file", (_v = blueprint.markerFile) !== null && _v !== void 0 ? _v : "", "text", (value) => { blueprint.markerFile = value; }); + (0, utils_1.addStringInput)(this.contentDiv2, "Cost", (_c = String(blueprint.cost)) !== null && _c !== void 0 ? _c : "", "number", (value) => { blueprint.cost = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Acquisition range [m]", (_d = String(blueprint.acquisitionRange)) !== null && _d !== void 0 ? _d : "", "number", (value) => { blueprint.acquisitionRange = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Engagement range [m]", (_e = String(blueprint.engagementRange)) !== null && _e !== void 0 ? _e : "", "number", (value) => { blueprint.engagementRange = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Targeting range [m]", (_f = String(blueprint.targetingRange)) !== null && _f !== void 0 ? _f : "", "number", (value) => { blueprint.targetingRange = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Aim method range [m]", (_g = String(blueprint.aimMethodRange)) !== null && _g !== void 0 ? _g : "", "number", (value) => { blueprint.aimMethodRange = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Barrel height [m]", (_h = String(blueprint.barrelHeight)) !== null && _h !== void 0 ? _h : "", "number", (value) => { blueprint.barrelHeight = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Muzzle velocity [m/s]", (_j = String(blueprint.muzzleVelocity)) !== null && _j !== void 0 ? _j : "", "number", (value) => { blueprint.muzzleVelocity = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Aim time [s]", (_k = String(blueprint.aimTime)) !== null && _k !== void 0 ? _k : "", "number", (value) => { blueprint.aimTime = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Shots to fire", (_l = String(blueprint.shotsToFire)) !== null && _l !== void 0 ? _l : "", "number", (value) => { blueprint.shotsToFire = Math.round(parseFloat(value)); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Shots base interval [s]", (_m = String(blueprint.shotsBaseInterval)) !== null && _m !== void 0 ? _m : "", "number", (value) => { blueprint.shotsBaseInterval = Math.round(parseFloat(value)); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Shots base scatter [°]", (_o = String(blueprint.shotsBaseScatter)) !== null && _o !== void 0 ? _o : "", "number", (value) => { blueprint.shotsBaseScatter = Math.round(parseFloat(value)); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Alertness time constant [s]", (_p = String(blueprint.alertnessTimeConstant)) !== null && _p !== void 0 ? _p : "", "number", (value) => { blueprint.alertnessTimeConstant = Math.round(parseFloat(value)); }); + (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can target point", (_q = blueprint.canTargetPoint) !== null && _q !== void 0 ? _q : false, (value) => { blueprint.canTargetPoint = value; }); + (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can rearm", (_r = blueprint.canRearm) !== null && _r !== void 0 ? _r : false, (value) => { blueprint.canRearm = value; }); + (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can operate as AAA", (_s = blueprint.canAAA) !== null && _s !== void 0 ? _s : false, (value) => { blueprint.canAAA = value; }); + (0, utils_1.addCheckboxInput)(this.contentDiv2, "Indirect fire (e.g. mortar)", (_t = blueprint.indirectFire) !== null && _t !== void 0 ? _t : false, (value) => { blueprint.indirectFire = value; }); + (0, utils_1.addStringInput)(this.contentDiv2, "Description", (_u = blueprint.description) !== null && _u !== void 0 ? _u : "", "text", (value) => { blueprint.description = value; }); + (0, utils_1.addStringInput)(this.contentDiv2, "Tags", (_v = blueprint.tags) !== null && _v !== void 0 ? _v : "", "text", (value) => { blueprint.tags = value; }); + (0, utils_1.addStringInput)(this.contentDiv2, "Marker file", (_w = blueprint.markerFile) !== null && _w !== void 0 ? _w : "", "text", (value) => { blueprint.markerFile = value; }); } } /** Add a new empty blueprint @@ -998,8 +999,8 @@ function addBlueprintsScroll(div, database, filter, callback) { if (addKey) { var rowDiv = document.createElement("div"); scrollDiv.appendChild(rowDiv); - let text = document.createElement("label"); - text.textContent = key; + let text = document.createElement("div"); + text.innerHTML = `
${key}
${blueprints[key].label}
`; text.onclick = () => { callback(key); const collection = document.getElementsByClassName("blueprint-selected"); diff --git a/client/plugins/databasemanager/src/grounduniteditor.ts b/client/plugins/databasemanager/src/grounduniteditor.ts index ef5c61ae..41d68345 100644 --- a/client/plugins/databasemanager/src/grounduniteditor.ts +++ b/client/plugins/databasemanager/src/grounduniteditor.ts @@ -30,6 +30,7 @@ export class GroundUnitEditor extends UnitEditor { addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; }); addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; }); addStringInput(this.contentDiv2, "Type", blueprint.type?? "", "text", (value: string) => {blueprint.type = value; }); + addStringInput(this.contentDiv2, "Unit when grouped", blueprint.unitWhenGrouped?? "", "text", (value: string) => {blueprint.unitWhenGrouped = value; }); addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; }); addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; }); //addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; }); diff --git a/client/plugins/databasemanager/src/utils.ts b/client/plugins/databasemanager/src/utils.ts index 18639612..ba166dec 100644 --- a/client/plugins/databasemanager/src/utils.ts +++ b/client/plugins/databasemanager/src/utils.ts @@ -198,8 +198,8 @@ export function addBlueprintsScroll(div: HTMLElement, database: {blueprints: {[k var rowDiv = document.createElement("div"); scrollDiv.appendChild(rowDiv); - let text = document.createElement("label"); - text.textContent = key; + let text = document.createElement("div"); + text.innerHTML = `
${key}
${blueprints[key].label}
`; text.onclick = () => { callback(key); const collection = document.getElementsByClassName("blueprint-selected"); diff --git a/client/plugins/databasemanager/style.css b/client/plugins/databasemanager/style.css index 0dfd2647..f8c80213 100644 --- a/client/plugins/databasemanager/style.css +++ b/client/plugins/databasemanager/style.css @@ -73,7 +73,7 @@ } .dm-content-container:nth-of-type(1) { - width: 300px; + width: 400px; } .dm-content-container:nth-of-type(2) { @@ -153,12 +153,29 @@ justify-content: space-between; } +.dm-scroll-container>div>div { + display: flex; + align-items: center; + justify-content: space-between; +} + .dm-scroll-container>div>button { height: 20px; width: 20px; padding: 0px; } +.dm-scroll-container>div>div>div:nth-child(1) { + width: fit-content; +} + +.dm-scroll-container>div>div>div:nth-child(2) { + overflow: hidden; + text-wrap: nowrap; + text-overflow: ellipsis; + font-weight: normal; +} + .input-row { width: 100%; display: flex; diff --git a/client/public/databases/units/groundunitdatabase.json b/client/public/databases/units/groundunitdatabase.json index ca48fbd4..5c980d7a 100644 --- a/client/public/databases/units/groundunitdatabase.json +++ b/client/public/databases/units/groundunitdatabase.json @@ -26,7 +26,7 @@ "name": "2B11 mortar", "coalition": "red", "era": "Late Cold War", - "label": "2B11 mortar (120mm)", + "label": "2B11 mortar", "shortLabel": "2B11 mortar", "filename": "", "type": "Artillery", @@ -62,14 +62,16 @@ "barrelHeight": 1, "muzzleVelocity": 325, "aimTime": 5, - "shotsToFire": 100 + "shotsToFire": 100, + "markerFile": "groundunit-artillery", + "tags": "120mm" }, "2S6 Tunguska": { "name": "2S6 Tunguska", "coalition": "red", "era": "Late Cold War", "label": "SA-19 Tunguska", - "shortLabel": "SA-19", + "shortLabel": "19", "range": "Short", "filename": "", "type": "SAM Site", @@ -123,7 +125,8 @@ "aimTime": 5, "shotsToFire": 10, "cost": null, - "tags": "Optical, Radar, CA" + "tags": "Optical, Radar, CA", + "markerFile": "groundunit-sam" }, "55G6 EWR": { "name": "55G6 EWR", @@ -152,7 +155,7 @@ "name": "5p73 s-125 ln", "coalition": "red", "era": "Mid Cold War", - "label": "SA-3", + "label": "SA-3 Launcher", "shortLabel": "5p73 s-125 ln", "range": "Medium", "filename": "", @@ -202,7 +205,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Launcher" + "tags": "", + "markerFile": "groundunit-sam-launcher" }, "AAV7": { "name": "AAV7", @@ -245,7 +249,8 @@ "muzzleVelocity": 900, "aimTime": 10, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "ATMZ-5": { "name": "ATMZ-5", @@ -268,7 +273,8 @@ "abilities": "Refuel", "canTargetPoint": false, "canRearm": false, - "tags": "Fuel Truck" + "tags": "Fuel Truck", + "markerFile": "groundunit-truck" }, "ATZ-10": { "name": "ATZ-10", @@ -291,7 +297,8 @@ "abilities": "Refuel", "canTargetPoint": false, "canRearm": false, - "tags": "Fuel Truck" + "tags": "Fuel Truck", + "markerFile": "groundunit-truck" }, "BMD-1": { "name": "BMD-1", @@ -354,7 +361,8 @@ "canRearm": false, "shotsToFire": 100, "aimTime": 5, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "BMP-1": { "name": "BMP-1", @@ -461,7 +469,8 @@ "muzzleVelocity": 665, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "BMP-2": { "name": "BMP-2", @@ -552,7 +561,8 @@ "canRearm": false, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "BMP-3": { "name": "BMP-3", @@ -611,7 +621,8 @@ "muzzleVelocity": 1080, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "BRDM-2": { "name": "BRDM-2", @@ -670,7 +681,8 @@ "barrelHeight": 2.25, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "BTR-80": { "name": "BTR-80", @@ -761,7 +773,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "BTR_D": { "name": "BTR_D", @@ -816,7 +829,8 @@ "abilities": "Combined arms, Amphibious, Transport", "canTargetPoint": false, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "Bunker": { "name": "Bunker", @@ -863,7 +877,8 @@ "muzzleVelocity": 800, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tank" }, "Cobra": { "name": "Cobra", @@ -884,7 +899,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "Dog Ear radar": { "name": "Dog Ear radar", @@ -955,7 +971,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Search Radar" + "tags": "Search Radar", + "markerFile": "groundunit-sam-radar" }, "GAZ-3307": { "name": "GAZ-3307", @@ -977,7 +994,8 @@ "description": "Civilian truck, single axle, wheeled", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "GAZ-3308": { "name": "GAZ-3308", @@ -999,7 +1017,8 @@ "description": "Military truck, single axle, canvas covered cargo bay. wheeled", "abilities": "Rearm", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "GAZ-66": { "name": "GAZ-66", @@ -1053,7 +1072,8 @@ "description": "Military truck, single axle, open cargo bay. wheeled", "abilities": "", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "Gepard": { "name": "Gepard", @@ -1097,7 +1117,8 @@ "aimTime": 5, "shotsToFire": 100, "cost": 15000000, - "tags": "Radar, CA" + "tags": "Radar, CA", + "markerFile": "groundunit-aaa" }, "Grad-URAL": { "name": "Grad-URAL", @@ -1113,7 +1134,8 @@ "description": "Military truck, single axle, open cargo bay. wheeled", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "HEMTT TFFT": { "name": "HEMTT TFFT", @@ -1135,14 +1157,15 @@ "description": "Military truck, 2 axle, firefigther. wheeled", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Hawk SAM Battery": { "name": "Hawk SAM Battery", "coalition": "blue", "era": "Early Cold War", "label": "Hawk SAM Battery", - "shortLabel": "Hawk SAM Battery", + "shortLabel": "HA", "range": "Medium", "filename": "", "type": "SAM Site", @@ -1153,7 +1176,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-sam" }, "Hawk cwar": { "name": "Hawk cwar", @@ -1184,7 +1208,8 @@ "description": "Hawk site Aquisition Radar", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "Hawk ln": { "name": "Hawk ln", @@ -1888,7 +1913,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-sam-radar" }, "Hawk tr": { "name": "Hawk tr", @@ -2224,7 +2250,8 @@ "description": "Hawk site track Radar. Medium sized trailer", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "Hummer": { "name": "Hummer", @@ -2279,7 +2306,8 @@ "abilities": "Combined arms, Transport", "canTargetPoint": false, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "IKARUS Bus": { "name": "IKARUS Bus", @@ -2295,14 +2323,15 @@ "description": "Civilian Bus. Yellow. Bendy bus", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Igla manpad INS": { "name": "Igla manpad INS", "coalition": "red", "era": "Late Cold War", "label": "SA-18", - "shortLabel": "SA-18 Igla", + "shortLabel": "18", "range": "Short", "filename": "", "type": "SAM Site", @@ -2331,7 +2360,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "IR, CA, MANPADS" + "tags": "IR, CA, MANPADS", + "markerFile": "groundunit-sam" }, "Infantry AK": { "name": "Infantry AK", @@ -2352,7 +2382,8 @@ "canRearm": false, "aimTime": 5, "shotsToFire": 100, - "tags": "Russian type 1" + "tags": "Russian type 1", + "markerFile": "groundunit-infantry" }, "KAMAZ Truck": { "name": "KAMAZ Truck", @@ -2406,7 +2437,8 @@ "description": "Military truck, 2 axle, wheeled", "abilities": "", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "Kub 1S91 str": { "name": "Kub 1S91 str", @@ -2445,7 +2477,8 @@ "description": "SA-6/Kub search and track Radar, tracked.", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "Kub 2P25 ln": { "name": "Kub 2P25 ln", @@ -2544,7 +2577,8 @@ "muzzleVelocity": 1100, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "LAZ Bus": { "name": "LAZ Bus", @@ -2560,7 +2594,8 @@ "description": "Civilian bus. Single Axle. Wheeled", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Leclerc": { "name": "Leclerc", @@ -2582,7 +2617,8 @@ "description": "Main battle tank. Tracked. Modern and heavily armoured.", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Leopard-2": { "name": "Leopard-2", @@ -2748,7 +2784,8 @@ "description": "Main battle tank. Tracked. Modern and heavily armoured.", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Leopard1A3": { "name": "Leopard1A3", @@ -2802,7 +2839,8 @@ "description": "Main battle tank. Tracked. Heavily armoured.", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "M 818": { "name": "M 818", @@ -2836,7 +2874,8 @@ "description": "???", "abilities": "", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "M-1 Abrams": { "name": "M-1 Abrams", @@ -2875,7 +2914,8 @@ "abilities": "Combined arms,", "canTargetPoint": true, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tank" }, "M-109": { "name": "M-109", @@ -2930,7 +2970,8 @@ "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "155mm, CA" + "tags": "155mm, CA", + "markerFile": "groundunit-artillery" }, "M-113": { "name": "M-113", @@ -3033,7 +3074,8 @@ "muzzleVelocity": 950, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "M-2 Bradley": { "name": "M-2 Bradley", @@ -3076,7 +3118,8 @@ "muzzleVelocity": 1000, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "M-60": { "name": "M-60", @@ -3114,7 +3157,8 @@ "description": "Main battle tank. Tracked. Heavily armoured.", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "M1043 HMMWV Armament": { "name": "M1043 HMMWV Armament", @@ -3169,7 +3213,8 @@ "abilities": "Combined arms, Transport", "canTargetPoint": true, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "M1045 HMMWV TOW": { "name": "M1045 HMMWV TOW", @@ -3224,14 +3269,15 @@ "abilities": "Combined arms, Transport", "canTargetPoint": true, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "M1097 Avenger": { "name": "M1097 Avenger", "coalition": "blue", "era": "Modern", "label": "M1097 Avenger", - "shortLabel": "M1097 Avenger", + "shortLabel": "97", "filename": "", "type": "SAM Site", "enabled": true, @@ -3241,7 +3287,8 @@ "abilities": "Combined arms,", "canTargetPoint": true, "canRearm": false, - "tags": "IR, CA" + "tags": "IR, CA", + "markerFile": "groundunit-sam" }, "M1126 Stryker ICV": { "name": "M1126 Stryker ICV", @@ -3262,7 +3309,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "M1128 Stryker MGS": { "name": "M1128 Stryker MGS", @@ -3283,7 +3331,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "M1134 Stryker ATGM": { "name": "M1134 Stryker ATGM", @@ -3304,14 +3353,15 @@ "muzzleVelocity": 900, "barrelHeight": 2.8, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "M48 Chaparral": { "name": "M48 Chaparral", "coalition": "blue", "era": "Mid Cold War", "label": "M48 Chaparral", - "shortLabel": "M48 Chaparral", + "shortLabel": "48", "filename": "", "type": "SAM Site", "enabled": true, @@ -3375,14 +3425,15 @@ "abilities": "Combined arms,", "canTargetPoint": false, "canRearm": false, - "tags": "IR, CA" + "tags": "IR, CA", + "markerFile": "groundunit-sam" }, "M6 Linebacker": { "name": "M6 Linebacker", "coalition": "blue", "era": "Late Cold War", "label": "M6 Linebacker", - "shortLabel": "M6 Linebacker", + "shortLabel": "M6", "filename": "", "type": "SAM Site", "enabled": true, @@ -3414,7 +3465,8 @@ "abilities": "Combined arms,", "canTargetPoint": false, "canRearm": false, - "tags": "IR, CA" + "tags": "IR, CA", + "markerFile": "groundunit-sam" }, "M978 HEMTT Tanker": { "name": "M978 HEMTT Tanker", @@ -3452,7 +3504,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "MAZ-6303": { "name": "MAZ-6303", @@ -3490,7 +3543,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "MCV-80": { "name": "MCV-80", @@ -3533,7 +3587,8 @@ "muzzleVelocity": 1100, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "MLRS": { "name": "MLRS", @@ -3588,7 +3643,8 @@ "abilities": "Combined arms, Indirect Fire", "canTargetPoint": true, "canRearm": false, - "tags": "270mm, MLRS, CA" + "tags": "270mm, MLRS, CA", + "markerFile": "groundunit-artillery" }, "MTLB": { "name": "MTLB", @@ -3647,7 +3703,8 @@ "muzzleVelocity": 800, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "Marder": { "name": "Marder", @@ -3690,7 +3747,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "Osa 9A33 ln": { "name": "Osa 9A33 ln", @@ -3751,7 +3809,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "Russian Para" + "tags": "Russian Para", + "markerFile": "groundunit-infantry" }, "Paratrooper RPG-16": { "name": "Paratrooper RPG-16", @@ -3772,7 +3831,8 @@ "muzzleVelocity": 295, "aimTime": 5, "shotsToFire": 100, - "tags": "Russian Para" + "tags": "Russian Para", + "markerFile": "groundunit-infantry" }, "Patriot AMG": { "name": "Patriot AMG", @@ -4046,7 +4106,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "Predator GCS": { "name": "Predator GCS", @@ -4070,7 +4131,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Predator TrojanSpirit": { "name": "Predator TrojanSpirit", @@ -4086,7 +4148,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "RLS_19J6": { "name": "RLS_19J6", @@ -4121,7 +4184,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "RPC_5N62V": { "name": "RPC_5N62V", @@ -4211,7 +4275,7 @@ "coalition": "blue", "era": "Late Cold War", "label": "Roland ADS", - "shortLabel": "Roland ADS", + "shortLabel": "RO", "filename": "", "type": "SAM Site", "enabled": true, @@ -4227,7 +4291,8 @@ "abilities": "Combined arms,", "canTargetPoint": false, "canRearm": false, - "tags": "Radar, Optical, CA" + "tags": "Radar, Optical, CA", + "markerFile": "groundunit-sam" }, "Roland Radar": { "name": "Roland Radar", @@ -4249,7 +4314,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "S-200_Launcher": { "name": "S-200_Launcher", @@ -4372,7 +4438,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "S-300PS 40B6MD sr": { "name": "S-300PS 40B6MD sr", @@ -4411,7 +4478,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "S-300PS 54K6 cp": { "name": "S-300PS 54K6 cp", @@ -4571,14 +4639,15 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "SA-10 SAM Battery": { "name": "SA-10 SAM Battery", "coalition": "red", "era": "Late Cold War", "label": "SA-10 SAM Battery", - "shortLabel": "SA-10", + "shortLabel": "10", "range": "Long", "filename": "", "type": "SAM Site", @@ -4589,7 +4658,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-sam" }, "SA-11 Buk CC 9S470M1": { "name": "SA-11 Buk CC 9S470M1", @@ -4762,7 +4832,7 @@ "coalition": "red", "era": "Late Cold War", "label": "SA-11 SAM Battery", - "shortLabel": "SA-11 SAM Battery", + "shortLabel": "11", "range": "Medium", "filename": "", "type": "SAM Site", @@ -4773,14 +4843,15 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-sam" }, "SA-18 Igla manpad": { "name": "SA-18 Igla manpad", "coalition": "red", "era": "Late Cold War", "label": "SA-18 Igla \"Grouse\" C2", - "shortLabel": "SA-18 Igla manpad", + "shortLabel": "18", "range": "Short", "filename": "", "type": "SAM Site", @@ -4798,7 +4869,7 @@ "coalition": "red", "era": "Late Cold War", "label": "SA-18 Igla \"Grouse\" C2", - "shortLabel": "SA-18 Igla \"Grouse\"", + "shortLabel": "18", "range": "Short", "filename": "", "type": "SAM Site", @@ -4816,7 +4887,7 @@ "coalition": "red", "era": "Early Cold War", "label": "SA-2 SAM Battery", - "shortLabel": "SA-2", + "shortLabel": "2", "range": "Long", "filename": "", "type": "SAM Site", @@ -4827,14 +4898,15 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-sam" }, "SA-3 SAM Battery": { "name": "SA-3 SAM Battery", "coalition": "red", "era": "Early Cold War", "label": "SA-3 SAM Battery", - "shortLabel": "SA-3", + "shortLabel": "3", "range": "Medium", "filename": "", "type": "SAM Site", @@ -4845,14 +4917,15 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-sam" }, "SA-5 SAM Battery": { "name": "SA-5 SAM Battery", "coalition": "red", "era": "Mid Cold War", "label": "SA-5 SAM Battery", - "shortLabel": "SA-5", + "shortLabel": "5", "range": "Long", "filename": "", "type": "SAM Site", @@ -4863,14 +4936,15 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-sam" }, "SA-6 SAM Battery": { "name": "SA-6 SAM Battery", "coalition": "red", "era": "Mid Cold War", "label": "SA-6 SAM Battery", - "shortLabel": "SA-6", + "shortLabel": "6", "range": "Medium", "filename": "", "type": "SAM Site", @@ -4881,7 +4955,8 @@ "engagementRange": "", "canTargetPoint": false, "canRearm": false, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-sam" }, "SAU 2-C9": { "name": "SAU 2-C9", @@ -4936,7 +5011,8 @@ "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "120mm, CA" + "tags": "120mm, CA", + "markerFile": "groundunit-artillery" }, "SAU Akatsia": { "name": "SAU Akatsia", @@ -4991,7 +5067,8 @@ "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "152mm, CA" + "tags": "152mm, CA", + "markerFile": "groundunit-artillery" }, "SAU Gvozdika": { "name": "SAU Gvozdika", @@ -5046,7 +5123,8 @@ "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "122mm, CA" + "tags": "122mm, CA", + "markerFile": "groundunit-artillery" }, "SAU Msta": { "name": "SAU Msta", @@ -5101,7 +5179,8 @@ "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "152mm, CA" + "tags": "152mm, CA", + "markerFile": "groundunit-artillery" }, "SKP-11": { "name": "SKP-11", @@ -5123,7 +5202,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "SNR_75V": { "name": "SNR_75V", @@ -5161,7 +5241,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "S_75M_Volhov": { "name": "S_75M_Volhov", @@ -5200,7 +5281,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "markerFile": "groundunit-sam-launcher" + "markerFile": "groundunit-sam-launcher", + "unitWhenGrouped": "SA-2 SAM Battery" }, "Sandbox": { "name": "Sandbox", @@ -5255,7 +5337,8 @@ "abilities": "Combined arms,", "canTargetPoint": true, "canRearm": false, - "tags": "Rocket, CA" + "tags": "Rocket, CA", + "markerFile": "groundunit-artillery" }, "Soldier AK": { "name": "Soldier AK", @@ -5310,7 +5393,8 @@ "canRearm": false, "aimTime": 5, "shotsToFire": 100, - "tags": "Russian type 4" + "tags": "Russian type 4", + "markerFile": "groundunit-infantry" }, "Soldier M249": { "name": "Soldier M249", @@ -5365,7 +5449,8 @@ "aimTime": 5, "shotsToFire": 100, "barrelHeight": 0.2, - "tags": "US" + "tags": "US", + "markerFile": "groundunit-infantry" }, "Soldier M4 GRG": { "name": "Soldier M4 GRG", @@ -5420,7 +5505,8 @@ "muzzleVelocity": 910, "aimTime": 5, "shotsToFire": 100, - "tags": "Georgia" + "tags": "Georgia", + "markerFile": "groundunit-infantry" }, "Soldier M4": { "name": "Soldier M4", @@ -5475,7 +5561,8 @@ "muzzleVelocity": 910, "aimTime": 5, "shotsToFire": 100, - "tags": "US" + "tags": "US", + "markerFile": "groundunit-infantry" }, "Soldier RPG": { "name": "Soldier RPG", @@ -5530,7 +5617,8 @@ "muzzleVelocity": 295, "aimTime": 5, "shotsToFire": 100, - "tags": "Russian" + "tags": "Russian", + "markerFile": "groundunit-infantry" }, "Stinger comm dsr": { "name": "Stinger comm dsr", @@ -5570,7 +5658,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "MANPADS, IR" + "tags": "MANPADS, IR", + "markerFile": "groundunit-sam" }, "Stinger comm": { "name": "Stinger comm", @@ -5610,14 +5699,15 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "MANPADS, IR" + "tags": "MANPADS, IR", + "markerFile": "groundunit-sam" }, "Strela-1 9P31": { "name": "Strela-1 9P31", "coalition": "red", "era": "Mid Cold War", "label": "SA-9 SAM Battery", - "shortLabel": "SA-9 Strela 1", + "shortLabel": "9", "range": "Short", "filename": "", "type": "SAM Site", @@ -5666,14 +5756,15 @@ "abilities": "Combined arms, Amphibious", "canTargetPoint": false, "canRearm": false, - "tags": "IR, CA" + "tags": "IR, CA", + "markerFile": "groundunit-sam" }, "Strela-10M3": { "name": "Strela-10M3", "coalition": "red", "era": "Late Cold War", "label": "SA-13 SAM Battery", - "shortLabel": "SA-13 Strela 10", + "shortLabel": "13", "range": "Short", "filename": "", "type": "SAM Site", @@ -5722,7 +5813,8 @@ "abilities": "Combined arms, Amphibious", "canTargetPoint": false, "canRearm": false, - "tags": "Optical, Radar, CA" + "tags": "Optical, Radar, CA", + "markerFile": "groundunit-sam" }, "Suidae": { "name": "Suidae", @@ -5738,7 +5830,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "T-55": { "name": "T-55", @@ -5792,7 +5885,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "T-72B": { "name": "T-72B", @@ -5814,7 +5908,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "T-80UD": { "name": "T-80UD", @@ -5876,7 +5971,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "T-90": { "name": "T-90", @@ -5930,7 +6026,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "TPZ": { "name": "TPZ", @@ -5951,7 +6048,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "Tigr_233036": { "name": "Tigr_233036", @@ -5973,14 +6071,15 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Tor 9A331": { "name": "Tor 9A331", "coalition": "red", "era": "Late Cold War", "label": "SA-15 SAM Battery", - "shortLabel": "SA-15 Tor", + "shortLabel": "15", "range": "Medium", "filename": "", "type": "SAM Site", @@ -6045,7 +6144,8 @@ "abilities": "Combined arms,", "canTargetPoint": false, "canRearm": false, - "tags": "Radar, CA" + "tags": "Radar, CA", + "markerFile": "groundunit-sam" }, "Trolley bus": { "name": "Trolley bus", @@ -6061,7 +6161,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "UAZ-469": { "name": "UAZ-469", @@ -6127,7 +6228,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Uragan_BM-27": { "name": "Uragan_BM-27", @@ -6198,7 +6300,8 @@ "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "Rocket, CA" + "tags": "Rocket, CA", + "markerFile": "groundunit-artillery" }, "Ural ATsP-6": { "name": "Ural ATsP-6", @@ -6214,7 +6317,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Ural-375 PBU": { "name": "Ural-375 PBU", @@ -6236,7 +6340,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Ural-375 ZU-23 Insurgent": { "name": "Ural-375 ZU-23 Insurgent", @@ -6264,7 +6369,8 @@ "muzzleVelocity": 1000, "barrelHeight": 3, "cost": 90000, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "Ural-375 ZU-23": { "name": "Ural-375 ZU-23", @@ -6292,7 +6398,8 @@ "muzzleVelocity": 1000, "aimTime": 8, "shotsToFire": 1000, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "Ural-375": { "name": "Ural-375", @@ -6314,7 +6421,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "Ural-4320 APA-5D": { "name": "Ural-4320 APA-5D", @@ -6336,7 +6444,8 @@ "description": "Lightly armoured", "abilities": "", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "Ural-4320-31": { "name": "Ural-4320-31", @@ -6358,7 +6467,8 @@ "abilities": "", "description": "", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "Ural-4320T": { "name": "Ural-4320T", @@ -6380,7 +6490,8 @@ "abilities": "", "description": "", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "VAZ Car": { "name": "VAZ Car", @@ -6396,7 +6507,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Vulcan": { "name": "Vulcan", @@ -6472,7 +6584,8 @@ "muzzleVelocity": 900, "aimTime": 10, "shotsToFire": 100, - "tags": "Radar, CA" + "tags": "Radar, CA", + "markerFile": "groundunit-aaa" }, "ZIL-131 KUNG": { "name": "ZIL-131 KUNG", @@ -6526,7 +6639,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "ZIL-4331": { "name": "ZIL-4331", @@ -6580,7 +6694,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "ZSU-23-4 Shilka": { "name": "ZSU-23-4 Shilka", @@ -6672,7 +6787,8 @@ "aimTime": 9, "shotsToFire": 100, "cost": 2500000, - "tags": "Radar, CA" + "tags": "Radar, CA", + "markerFile": "groundunit-aaa" }, "ZU-23 Closed Insurgent": { "name": "ZU-23 Closed Insurgent", @@ -6700,7 +6816,8 @@ "aimTime": 9, "shotsToFire": 100, "cost": 50000, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "ZU-23 Emplacement Closed": { "name": "ZU-23 Emplacement Closed", @@ -6744,7 +6861,8 @@ "muzzleVelocity": 1000, "barrelHeight": 1.5, "cost": 50000, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "ZU-23 Emplacement": { "name": "ZU-23 Emplacement", @@ -6788,7 +6906,8 @@ "muzzleVelocity": 1000, "barrelHeight": 1.5, "cost": 50000, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "ZU-23 Insurgent": { "name": "ZU-23 Insurgent", @@ -6816,7 +6935,8 @@ "muzzleVelocity": 1000, "barrelHeight": 1.5, "cost": 50000, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "ZiL-131 APA-80": { "name": "ZiL-131 APA-80", @@ -6838,7 +6958,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "house1arm": { "name": "house1arm", @@ -6972,7 +7093,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "snr s-125 tr": { "name": "snr s-125 tr", @@ -7027,7 +7149,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "SpGH_Dana": { "name": "SpGH_Dana", @@ -7043,7 +7166,8 @@ "description": "SpGH Dana 77. Wheeled. Self propelled 152mm howitzer. 1km min range, 18km max.", "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-artillery" }, "Grad_FDDM": { "name": "Grad_FDDM", @@ -7064,7 +7188,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "Arty Command/Recon, CA" + "tags": "Arty Command/Recon, CA", + "markerFile": "groundunit-artillery" }, "Infantry AK Ins": { "name": "Infantry AK Ins", @@ -7085,7 +7210,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "Insurgent" + "tags": "Insurgent", + "markerFile": "groundunit-infantry" }, "MLRS FDDM": { "name": "MLRS FDDM", @@ -7106,7 +7232,8 @@ "aimTime": 5, "shotsToFire": 100, "barrelHeight": 2.49, - "tags": "Arty Command/Recon, CA" + "tags": "Arty Command/Recon, CA", + "markerFile": "groundunit-artillery" }, "Infantry AK ver2": { "name": "Infantry AK ver2", @@ -7127,7 +7254,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "Russian type 2" + "tags": "Russian type 2", + "markerFile": "groundunit-infantry" }, "Infantry AK ver3": { "name": "Infantry AK ver3", @@ -7148,7 +7276,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "Russian type 3" + "tags": "Russian type 3", + "markerFile": "groundunit-infantry" }, "Smerch_HE": { "name": "Smerch_HE", @@ -7165,14 +7294,15 @@ "abilities": "Combined Arms, Indirect Fire", "canTargetPoint": true, "canRearm": false, - "tags": "300mm, MLRS, CA" + "tags": "300mm, MLRS, CA", + "markerFile": "groundunit-artillery" }, "Soldier stinger": { "name": "Soldier stinger", "coalition": "", "era": "", "label": "Stinger", - "shortLabel": "Stinger", + "shortLabel": "ST", "type": "SAM Site", "enabled": true, "liveries": {}, @@ -7182,14 +7312,15 @@ "abilities": "Combined arms,", "canTargetPoint": false, "canRearm": false, - "tags": "IR, CA, MANPADS" + "tags": "IR, CA, MANPADS", + "markerFile": "groundunit-sam" }, "SA-18 Igla comm": { "name": "SA-18 Igla comm", "coalition": "", "era": "", "label": "SA-18 Igla \"Grouse\" C2", - "shortLabel": "SA-18 Igla \"Grouse\" C2", + "shortLabel": "18", "type": "SAM Site", "enabled": false, "liveries": {}, @@ -7206,7 +7337,7 @@ "coalition": "", "era": "", "label": "SA-18 Igla \"Grouse\" C2", - "shortLabel": "SA-18 Igla \"Grouse\"", + "shortLabel": "18", "type": "SAM Site", "enabled": false, "liveries": {}, @@ -7248,7 +7379,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "LiAZ Bus": { "name": "LiAZ Bus", @@ -7264,7 +7396,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "KrAZ6322": { "name": "KrAZ6322", @@ -7280,7 +7413,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": true + "canRearm": true, + "markerFile": "groundunit-truck" }, "JTAC": { "name": "JTAC", @@ -7296,7 +7430,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-infantry" }, "Electric locomotive": { "name": "Electric locomotive", @@ -7448,7 +7583,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "LARC-V": { "name": "LARC-V", @@ -7464,7 +7600,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "KS-19": { "name": "KS-19", @@ -7485,7 +7622,8 @@ "aimTime": 25, "shotsToFire": 100, "barrelHeight": 5, - "cost": 8000 + "cost": 8000, + "markerFile": "groundunit-aaa" }, "SON_9": { "name": "SON_9", @@ -7503,7 +7641,8 @@ "canTargetPoint": false, "canRearm": false, "cost": 750000, - "tags": "Radar" + "tags": "Radar", + "markerFile": "groundunit-aaa" }, "Scud_B": { "name": "Scud_B", @@ -7520,7 +7659,8 @@ "abilities": "", "canTargetPoint": true, "canRearm": false, - "tags": "Missile" + "tags": "Missile", + "markerFile": "groundunit-artillery" }, "HL_DSHK": { "name": "HL_DSHK", @@ -7537,7 +7677,8 @@ "abilities": "Combined arms,", "canTargetPoint": true, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "HL_KORD": { "name": "HL_KORD", @@ -7554,7 +7695,8 @@ "abilities": "Combined arms,", "canTargetPoint": true, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "tt_DSHK": { "name": "tt_DSHK", @@ -7571,7 +7713,8 @@ "abilities": "Combined arms,", "canTargetPoint": true, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "tt_KORD": { "name": "tt_KORD", @@ -7588,7 +7731,8 @@ "abilities": "Combined arms,", "canTargetPoint": true, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "HL_ZU-23": { "name": "HL_ZU-23", @@ -7610,7 +7754,8 @@ "muzzleVelocity": 1000, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "tt_ZU-23": { "name": "tt_ZU-23", @@ -7632,7 +7777,8 @@ "muzzleVelocity": 1000, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "HL_B8M1": { "name": "HL_B8M1", @@ -7649,7 +7795,8 @@ "abilities": "Combined Arms, Indirect Fire.", "canTargetPoint": true, "canRearm": false, - "tags": "Rocket, CA" + "tags": "Rocket, CA", + "markerFile": "groundunit-artillery" }, "tt_B8M1": { "name": "tt_B8M1", @@ -7666,7 +7813,8 @@ "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "80mm, CA" + "tags": "80mm, CA", + "markerFile": "groundunit-artillery" }, "NASAMS_Radar_MPQ64F1": { "name": "NASAMS_Radar_MPQ64F1", @@ -7682,7 +7830,8 @@ "description": "", "abilities": "", "canTargetPoint": "no", - "canRearm": "no" + "canRearm": "no", + "markerFile": "groundunit-sam-radar" }, "NASAMS_Command_Post": { "name": "NASAMS_Command_Post", @@ -7746,7 +7895,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "M2A1_halftrack": { "name": "M2A1_halftrack", @@ -7767,7 +7917,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "FPS-117 Dome": { "name": "FPS-117 Dome", @@ -7826,7 +7977,7 @@ "name": "RD_75", "coalition": "", "era": "", - "label": "SAM SA-2 S-75 RD-75 Amazonka RF", + "label": "SA-2 S-75 RD-75 Amazonka RF", "shortLabel": "SAM SA-2 S-75 RD-75 Amazonka RF", "type": "SAM Site Parts", "enabled": true, @@ -7858,7 +8009,8 @@ "aimTime": 11, "shotsToFire": 100, "cost": 750000, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "S-60_Type59_Artillery": { "name": "S-60_Type59_Artillery", @@ -7880,7 +8032,8 @@ "shotsToFire": 100, "barrelHeight": 2, "cost": 500000, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "generator_5i57": { "name": "generator_5i57", @@ -7896,7 +8049,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "T-72B3": { "name": "T-72B3", @@ -7912,7 +8066,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "PT_76": { "name": "PT_76", @@ -7928,7 +8083,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "BTR-82A": { "name": "BTR-82A", @@ -7949,7 +8105,8 @@ "muzzleVelocity": 900, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "ATZ-5": { "name": "ATZ-5", @@ -7966,7 +8123,8 @@ "abilities": "Combined arms, Refuel", "canTargetPoint": false, "canRearm": false, - "tags": "Fuel Truck, CA" + "tags": "Fuel Truck, CA", + "markerFile": "groundunit-truck" }, "AA8": { "name": "AA8", @@ -7983,7 +8141,8 @@ "abilities": "Combined Arms", "canTargetPoint": false, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-truck" }, "TZ-22_KrAZ": { "name": "TZ-22_KrAZ", @@ -8000,7 +8159,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "KrAZ-258B1" + "tags": "KrAZ-258B1", + "markerFile": "groundunit-truck" }, "ATZ-60_Maz": { "name": "ATZ-60_Maz", @@ -8017,7 +8177,8 @@ "abilities": "Combined arms", "canTargetPoint": false, "canRearm": false, - "tags": "Cab only, CA" + "tags": "Cab only, CA", + "markerFile": "groundunit-truck" }, "ZIL-135": { "name": "ZIL-135", @@ -8026,14 +8187,15 @@ "label": "Truck ZIL-135", "shortLabel": "Truck ZIL-135", "type": "Unarmed", - "enabled": true, + "enabled": false, "liveries": {}, "acquisitionRange": 0, "engagementRange": 0, "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "S_75_ZIL": { "name": "S_75_ZIL", @@ -8050,7 +8212,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "ZIL-131" + "tags": "ZIL-131", + "markerFile": "groundunit-truck" }, "rapier_fsa_launcher": { "name": "rapier_fsa_launcher", @@ -8098,7 +8261,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "bofors40": { "name": "bofors40", @@ -8120,7 +8284,8 @@ "aimTime": 8, "shotsToFire": 100, "cost": null, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-aaa" }, "Chieftain_mk3": { "name": "Chieftain_mk3", @@ -8141,7 +8306,8 @@ "muzzleVelocity": 800, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tank" }, "Bedford_MWD": { "name": "Bedford_MWD", @@ -8158,7 +8324,8 @@ "abilities": "Combined arms, Rearm", "canTargetPoint": false, "canRearm": true, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-truck" }, "Land_Rover_101_FC": { "name": "Land_Rover_101_FC", @@ -8174,7 +8341,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Land_Rover_109_S3": { "name": "Land_Rover_109_S3", @@ -8190,7 +8358,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "hy_launcher": { "name": "hy_launcher", @@ -8207,7 +8376,8 @@ "abilities": "", "canTargetPoint": true, "canRearm": false, - "tags": "Missile Launcher" + "tags": "Missile Launcher", + "markerFile": "groundunit-artillery" }, "Silkworm_SR": { "name": "Silkworm_SR", @@ -8224,7 +8394,8 @@ "abilities": "", "canTargetPoint": true, "canRearm": false, - "tags": "Missile Search Radar" + "tags": "Missile Search Radar", + "markerFile": "groundunit-artillery" }, "ES44AH": { "name": "ES44AH", @@ -8313,7 +8484,8 @@ "canRearm": false, "muzzleVelocity": 1000, "barrelHeight": 2.1, - "cost": 40000 + "cost": 40000, + "markerFile": "groundunit-aaa" }, "Pz_IV_H": { "name": "Pz_IV_H", @@ -8329,7 +8501,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Leopard-2A5": { "name": "Leopard-2A5", @@ -8345,7 +8518,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "leopard-2A4": { "name": "leopard-2A4", @@ -8361,7 +8535,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "leopard-2A4_trs": { "name": "leopard-2A4_trs", @@ -8377,7 +8552,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Sd_Kfz_251": { "name": "Sd_Kfz_251", @@ -8398,7 +8574,8 @@ "muzzleVelocity": 765, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "Blitz_36-6700A": { "name": "Blitz_36-6700A", @@ -8415,7 +8592,8 @@ "abilities": "Combined arms, Rearm", "canTargetPoint": false, "canRearm": true, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-truck" }, "T155_Firtina": { "name": "T155_Firtina", @@ -8432,7 +8610,8 @@ "abilities": "Combined arms, Indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "155mm, CA" + "tags": "155mm, CA", + "markerFile": "groundunit-artillery" }, "VAB_Mephisto": { "name": "VAB_Mephisto", @@ -8449,7 +8628,8 @@ "abilities": "", "canTargetPoint": true, "canRearm": false, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-tactical" }, "ZTZ96B": { "name": "ZTZ96B", @@ -8469,7 +8649,8 @@ "barrelHeight": 2.2, "muzzleVelocity": 1100, "aimTime": 5, - "shotsToFire": 100 + "shotsToFire": 100, + "markerFile": "groundunit-tank" }, "ZBD04A": { "name": "ZBD04A", @@ -8490,7 +8671,8 @@ "muzzleVelocity": 1000, "aimTime": 5, "shotsToFire": 100, - "tags": "CA" + "tags": "CA", + "markerFile": "groundunit-apc" }, "HQ-7_LN_SP": { "name": "HQ-7_LN_SP", @@ -8538,7 +8720,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-sam-radar" }, "PLZ05": { "name": "PLZ05", @@ -8555,7 +8738,8 @@ "abilities": "Combined arms, indirect fire", "canTargetPoint": true, "canRearm": false, - "tags": "155mm, CA" + "tags": "155mm, CA", + "markerFile": "groundunit-artillery" }, "TYPE-59": { "name": "TYPE-59", @@ -8571,7 +8755,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Kubelwagen_82": { "name": "Kubelwagen_82", @@ -8587,7 +8772,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Sd_Kfz_2": { "name": "Sd_Kfz_2", @@ -8603,7 +8789,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Sd_Kfz_7": { "name": "Sd_Kfz_7", @@ -8619,7 +8806,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Horch_901_typ_40_kfz_21": { "name": "Horch_901_typ_40_kfz_21", @@ -8635,7 +8823,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "Tiger_I": { "name": "Tiger_I", @@ -8651,7 +8840,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Tiger_II_H": { "name": "Tiger_II_H", @@ -8667,7 +8857,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Pz_V_Panther_G": { "name": "Pz_V_Panther_G", @@ -8684,7 +8875,8 @@ "abilities": "", "canTargetPoint": true, "canRearm": false, - "tags": "Pz V" + "tags": "Pz V", + "markerFile": "groundunit-tank" }, "Jagdpanther_G1": { "name": "Jagdpanther_G1", @@ -8700,7 +8892,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "JagdPz_IV": { "name": "JagdPz_IV", @@ -8716,7 +8909,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Stug_IV": { "name": "Stug_IV", @@ -8732,7 +8926,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "SturmPzIV": { "name": "SturmPzIV", @@ -8748,7 +8943,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Wespe124": { "name": "Wespe124", @@ -8764,7 +8960,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-artillery" }, "Sd_Kfz_234_2_Puma": { "name": "Sd_Kfz_234_2_Puma", @@ -8796,7 +8993,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-aaa" }, "Flakscheinwerfer_37": { "name": "Flakscheinwerfer_37", @@ -8812,7 +9010,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-aaa" }, "Maschinensatz_33": { "name": "Maschinensatz_33", @@ -8828,7 +9027,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-aaa" }, "soldier_mauser98": { "name": "soldier_mauser98", @@ -8844,7 +9044,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-infantry" }, "SK_C_28_naval_gun": { "name": "SK_C_28_naval_gun", @@ -8860,7 +9061,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-artillery" }, "fire_control": { "name": "fire_control", @@ -8892,7 +9094,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Elefant_SdKfz_184": { "name": "Elefant_SdKfz_184", @@ -8908,7 +9111,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "v1_launcher": { "name": "v1_launcher", @@ -8972,7 +9176,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-artillery" }, "LeFH_18-40-105": { "name": "LeFH_18-40-105", @@ -8988,7 +9193,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-artillery" }, "Cromwell_IV": { "name": "Cromwell_IV", @@ -9004,7 +9210,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "M4A4_Sherman_FF": { "name": "M4A4_Sherman_FF", @@ -9020,7 +9227,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "soldier_wwii_br_01": { "name": "soldier_wwii_br_01", @@ -9036,7 +9244,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-infantry" }, "Centaur_IV": { "name": "Centaur_IV", @@ -9052,7 +9261,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "Churchill_VII": { "name": "Churchill_VII", @@ -9072,7 +9282,8 @@ "barrelHeight": 2, "muzzleVelocity": 800, "aimTime": 5, - "shotsToFire": 100 + "shotsToFire": 100, + "markerFile": "groundunit-tank" }, "Daimler_AC": { "name": "Daimler_AC", @@ -9120,7 +9331,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-aaa" }, "Allies_Director": { "name": "Allies_Director", @@ -9137,7 +9349,8 @@ "abilities": "", "canTargetPoint": false, "canRearm": false, - "tags": "DRT" + "tags": "DRT", + "markerFile": "groundunit-truck" }, "CCKW_353": { "name": "CCKW_353", @@ -9154,7 +9367,8 @@ "abilities": "Rearm,", "canTargetPoint": false, "canRearm": true, - "tags": "Rearm" + "tags": "Rearm", + "markerFile": "groundunit-truck" }, "Willys_MB": { "name": "Willys_MB", @@ -9170,7 +9384,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "M12_GMC": { "name": "M12_GMC", @@ -9186,7 +9401,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-artillery" }, "M30_CC": { "name": "M30_CC", @@ -9202,7 +9418,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "soldier_wwii_us": { "name": "soldier_wwii_us", @@ -9218,7 +9435,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-infantry" }, "M10_GMC": { "name": "M10_GMC", @@ -9234,7 +9452,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-tank" }, "M8_Greyhound": { "name": "M8_Greyhound", @@ -9266,7 +9485,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-artillery" }, "M4_Tractor": { "name": "M4_Tractor", @@ -9282,7 +9502,8 @@ "description": "", "abilities": "", "canTargetPoint": false, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-truck" }, "M45_Quadmount": { "name": "M45_Quadmount", @@ -9298,7 +9519,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-aaa" }, "M1_37mm": { "name": "M1_37mm", @@ -9314,7 +9536,8 @@ "description": "", "abilities": "", "canTargetPoint": true, - "canRearm": false + "canRearm": false, + "markerFile": "groundunit-aaa" }, "DR_50Ton_Flat_Wagon": { "name": "DR_50Ton_Flat_Wagon", diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 09f02d65..7038f26f 100644 --- a/client/public/stylesheets/markers/units.css +++ b/client/public/stylesheets/markers/units.css @@ -97,6 +97,10 @@ position: absolute; } +[data-object|="unit-groundunit"] .unit-short-label { + transform: translateY(7px); +} + /*** Health indicator ***/ [data-object|="unit"] .unit-health { background: white; @@ -277,7 +281,8 @@ background-image: url("/resources/theme/images/states/idle.svg"); } -[data-object*="groundunit"][data-state="idle"] .unit-state { +[data-object*="groundunit"][data-state="idle"] .unit-state, +[data-object*="navyunit"][data-state="idle"] .unit-state { background-image: url(""); /* To avoid clutter, dont show the idle state for non flying units */ } diff --git a/client/public/themes/olympus/images/units/groundunit-aaa.svg b/client/public/themes/olympus/images/units/groundunit-aaa.svg index 64a99efd..1174f3bb 100644 --- a/client/public/themes/olympus/images/units/groundunit-aaa.svg +++ b/client/public/themes/olympus/images/units/groundunit-aaa.svg @@ -1,6 +1,8 @@ + A - A + A + A diff --git a/client/public/themes/olympus/images/units/groundunit-apc.svg b/client/public/themes/olympus/images/units/groundunit-apc.svg new file mode 100644 index 00000000..bb70f4be --- /dev/null +++ b/client/public/themes/olympus/images/units/groundunit-apc.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/public/themes/olympus/images/units/groundunit-artillery.svg b/client/public/themes/olympus/images/units/groundunit-artillery.svg new file mode 100644 index 00000000..66fb468d --- /dev/null +++ b/client/public/themes/olympus/images/units/groundunit-artillery.svg @@ -0,0 +1,2 @@ + + diff --git a/client/public/themes/olympus/images/units/groundunit-infantry.svg b/client/public/themes/olympus/images/units/groundunit-infantry.svg new file mode 100644 index 00000000..459b7de0 --- /dev/null +++ b/client/public/themes/olympus/images/units/groundunit-infantry.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/public/themes/olympus/images/units/groundunit-tactical.svg b/client/public/themes/olympus/images/units/groundunit-tactical.svg new file mode 100644 index 00000000..95292f22 --- /dev/null +++ b/client/public/themes/olympus/images/units/groundunit-tactical.svg @@ -0,0 +1,2 @@ + + diff --git a/client/public/themes/olympus/images/units/groundunit-tank.svg b/client/public/themes/olympus/images/units/groundunit-tank.svg new file mode 100644 index 00000000..48ae29d1 --- /dev/null +++ b/client/public/themes/olympus/images/units/groundunit-tank.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/public/themes/olympus/images/units/groundunit-truck.svg b/client/public/themes/olympus/images/units/groundunit-truck.svg new file mode 100644 index 00000000..9152ad3c --- /dev/null +++ b/client/public/themes/olympus/images/units/groundunit-truck.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/public/themes/olympus/images/units/groundunit-other.svg b/client/public/themes/olympus/images/units/groundunit.svg similarity index 100% rename from client/public/themes/olympus/images/units/groundunit-other.svg rename to client/public/themes/olympus/images/units/groundunit.svg diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index fe1a07fc..7ec229c5 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -273,4 +273,6 @@ export const MGRS_PRECISION_10M = 5; export const MGRS_PRECISION_1M = 6; export const DELETE_CYCLE_TIME = 0.05; -export const DELETE_SLOW_THRESHOLD = 50; \ No newline at end of file +export const DELETE_SLOW_THRESHOLD = 50; + +export const GROUPING_ZOOM_TRANSITION = 13; \ No newline at end of file diff --git a/client/src/interfaces.ts b/client/src/interfaces.ts index f4251bf6..c12e5c5f 100644 --- a/client/src/interfaces.ts +++ b/client/src/interfaces.ts @@ -231,6 +231,7 @@ export interface UnitBlueprint { canAAA?: boolean; indirectFire?: boolean; markerFile?: string; + unitWhenGrouped?: string; } export interface UnitSpawnOptions { diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 7f4d641d..b176e137 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -68,6 +68,7 @@ export class Map extends L.Map { #temporaryMarkers: TemporaryUnitMarker[] = []; #selecting: boolean = false; #isZooming: boolean = false; + #previousZoom: number = 0; #destinationGroupRotation: number = 0; #computeDestinationRotation: boolean = false; @@ -501,6 +502,10 @@ export class Map extends L.Map { return this.#visibilityOptions; } + getPreviousZoom() { + return this.#previousZoom; + } + /* Event handlers */ #onClick(e: any) { if (!this.#preventLeftClick) { @@ -701,6 +706,7 @@ export class Map extends L.Map { } #onZoomStart(e: any) { + this.#previousZoom = this.getZoom(); if (this.#centerUnit != null) this.#panToUnit(this.#centerUnit); this.#isZooming = true; diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 237d919e..9b84b072 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -5,7 +5,7 @@ import { CustomMarker } from '../map/markers/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './databases/unitdatabase'; import { TargetMarker } from '../map/markers/targetmarker'; -import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING } from '../constants/constants'; +import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUPING_ZOOM_TRANSITION } from '../constants/constants'; import { DataExtractor } from '../server/dataextractor'; import { groundUnitDatabase } from './databases/groundunitdatabase'; import { navyUnitDatabase } from './databases/navyunitdatabase'; @@ -21,12 +21,11 @@ var pathIcon = new Icon({ /** * Unit class which controls unit behaviour - * - * Just about everything is a unit - even missiles! */ -export class Unit extends CustomMarker { +export abstract class Unit extends CustomMarker { ID: number; + /* Data controlled directly by the backend. No setters are provided to avoid misalignments */ #alive: boolean = false; #human: boolean = false; #controlled: boolean = false; @@ -90,6 +89,7 @@ export class Unit extends CustomMarker { #shotsIntensity: number = 2; #health: number = 100; + /* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */ #selectable: boolean; #selected: boolean = false; #hidden: boolean = false; @@ -107,6 +107,7 @@ export class Unit extends CustomMarker { #hotgroup: number | null = null; #detectionMethods: number[] = []; + /* Getters for backend driven data */ getAlive() { return this.#alive }; getHuman() { return this.#human }; getControlled() { return this.#controlled }; @@ -171,6 +172,7 @@ export class Unit extends CustomMarker { this.#engagementCircle = new RangeCircle(this.getPosition(), { radius: 0, weight: 4, opacity: 1, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false }); this.#acquisitionCircle = new RangeCircle(this.getPosition(), { radius: 0, weight: 2, opacity: 1, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false }); + /* Leaflet events listeners */ this.on('click', (e) => this.#onClick(e)); this.on('dblclick', (e) => this.#onDoubleClick(e)); this.on('contextmenu', (e) => this.#onContextMenu(e)); @@ -184,7 +186,7 @@ export class Unit extends CustomMarker { this.setHighlighted(false); document.dispatchEvent(new CustomEvent("unitMouseout", { detail: this })); }); - getApp().getMap().on("zoomend", () => { this.#onZoom(); }) + getApp().getMap().on("zoomend", (e: any) => { this.#onZoom(e); }) /* Deselect units if they are hidden */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { @@ -195,6 +197,7 @@ export class Unit extends CustomMarker { window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300); }); + /* Update the marker when the visibility options change */ document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => { this.#updateMarker(); @@ -209,13 +212,33 @@ export class Unit extends CustomMarker { }); } - getCategory() { - // Overloaded by child classes - return ""; - } + /********************** Abstract methods *************************/ + /** Get the unit category string + * + * @returns string The unit category + */ + abstract getCategory(): string; + + /** Get the icon options + * Used to configure how the marker appears on the map + * + * @returns ObjectIconOptions + */ + abstract getIconOptions(): ObjectIconOptions; + + /** Get the actions that this unit can perform + * + * @returns Object containing the available actions + */ + abstract getActions(): {[key: string]: { text: string, tooltip: string, type: string}}; /********************** Unit data *************************/ + /** This function is called by the units manager to update all the data coming from the backend. It reads the binary raw data using a DataExtractor + * + * @param dataExtractor The DataExtractor object pointing to the binary buffer which contains the raw data coming from the backend + */ setData(dataExtractor: DataExtractor) { + /* This variable controls if the marker must be updated. This is not always true since not all variables have an effect on the marker*/ var updateMarker = !getApp().getMap().hasLayer(this); var datumIndex = 0; @@ -226,7 +249,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: let newCoalition = enumToCoalition(dataExtractor.extractUInt8()); updateMarker = true; if (newCoalition != this.#coalition) this.#clearRanges(); this.#coalition = newCoalition; break; + case DataIndexes.coalition: let newCoalition = enumToCoalition(dataExtractor.extractUInt8()); updateMarker = true; if (newCoalition != this.#coalition) this.#clearRanges(); this.#coalition = newCoalition; break; // If the coalition has changed, redraw the range circles to update the colour case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break; case DataIndexes.name: this.#name = dataExtractor.extractString(); break; case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break; @@ -269,25 +292,18 @@ export class Unit extends CustomMarker { } } - /* Dead units can't be selected */ + /* Dead and hidden units can't be selected */ this.setSelected(this.getSelected() && this.#alive && !this.getHidden()) + /* Update the marker if required */ if (updateMarker) this.#updateMarker(); + /* If the unit is selected or if the view is centered on this unit, sent the update signal so that other elements like the UnitControlPanel can be updated. */ if (this.getSelected() || getApp().getMap().getCenterUnit() === this) document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); } - drawLines() { - /* Leaflet does not like it when you change coordinates when the map is zooming */ - if (!getApp().getMap().isZooming()) { - this.#drawPath(); - this.#drawContacts(); - this.#drawTarget(); - } - } - /** Get unit data collated into an object * * @returns object populated by unit information which can also be retrieved using getters @@ -358,28 +374,6 @@ export class Unit extends CustomMarker { return getUnitDatabaseByCategory(this.getMarkerCategory()); } - /** Get the icon options - * Used to configure how the marker appears on the map - * - * @returns ObjectIconOptions - */ - getIconOptions(): ObjectIconOptions { - // Default values, overloaded by child classes if needed - return { - showState: false, - showVvi: false, - showHealth: true, - showHotgroup: false, - showUnitIcon: true, - showShortLabel: false, - showFuel: false, - showAmmo: false, - showSummary: true, - showCallsign: true, - rotateToHeading: false - } - } - /** Set the unit as alive or dead * * @param newAlive (boolean) true = alive, false = dead @@ -395,16 +389,11 @@ export class Unit extends CustomMarker { * @param selected (boolean) */ setSelected(selected: boolean) { - /* Only alive units can be selected. Some units are not selectable (weapons) */ - if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected && this.belongsToCommandedCoalition()) { + /* Only alive units can be selected that belong to the commanded coalition can be selected */ + if ((this.#alive || !selected) && this.belongsToCommandedCoalition() && this.getSelected() != selected) { this.#selected = selected; - /* Circles don't like to be updated when the map is zooming */ - if (!getApp().getMap().isZooming()) - this.#drawRanges(); - else - this.once("zoomend", () => { this.#drawRanges(); }) - + /* If selected, update the marker to show the selected effects, else clear all the drawings that are only shown for selected units. */ if (selected) { this.#updateMarker(); } @@ -414,21 +403,27 @@ export class Unit extends CustomMarker { this.#clearTarget(); } - this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected); - if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < 13) { - if (this.#isLeader) + /* When the group leader is selected, if grouping is active, all the other group members are also selected */ + if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION) { + if (this.#isLeader) { + /* Redraw the marker in case the leader unit was replaced by a group marker, like for SAM Sites */ + this.#redrawMarker(); this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected)); - else + } + else { this.#updateMarker(); + } } - // Trigger events after all (de-)selecting has been done + /* Activate the selection effects on the marker */ + this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected); + + /* Trigger events after all (de-)selecting has been done */ if (selected) { document.dispatchEvent(new CustomEvent("unitSelection", { detail: this })); } else { document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this })); } - } } @@ -440,22 +435,6 @@ export class Unit extends CustomMarker { return this.#selected; } - /** Set whether this unit is selectable - * - * @param selectable (boolean) - */ - setSelectable(selectable: boolean) { - this.#selectable = selectable; - } - - /** Get whether this unit is selectable - * - * @returns boolean - */ - getSelectable() { - return this.#selectable; - } - /** Set the number of the hotgroup to which the unit belongs * * @param hotgroup (number) @@ -478,9 +457,9 @@ export class Unit extends CustomMarker { * @param highlighted (boolean) */ setHighlighted(highlighted: boolean) { - if (this.getSelectable() && this.#highlighted != highlighted) { - this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); + if (this.#highlighted != highlighted) { this.#highlighted = highlighted; + this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); this.getGroupMembers().forEach((unit: Unit) => unit.setHighlighted(highlighted)); } } @@ -517,10 +496,25 @@ export class Unit extends CustomMarker { return this.getDatabase()?.getSpawnPointsByName(this.getName()); } + getDatabaseEntry() { + return this.getDatabase()?.getByName(this.#name); + } + + drawLines() { + /* Leaflet does not like it when you change coordinates when the map is zooming */ + if (!getApp().getMap().isZooming()) { + this.#drawPath(); + this.#drawContacts(); + this.#drawTarget(); + } + } + + checkRedraw() { + return false; + } + /********************** Icon *************************/ createIcon(): void { - const databaseEntry = this.getDatabase()?.getByName(this.#name); - /* Set the icon */ var icon = new DivIcon({ className: 'leaflet-unit-icon', @@ -529,6 +523,7 @@ export class Unit extends CustomMarker { }); this.setIcon(icon); + /* Create the base element */ var el = document.createElement("div"); el.classList.add("unit"); el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); @@ -536,8 +531,8 @@ export class Unit extends CustomMarker { var iconOptions = this.getIconOptions(); - // Generate and append elements depending on active options - // Velocity vector + /* Generate and append elements depending on active options */ + /* Velocity vector */ if (iconOptions.showVvi) { var vvi = document.createElement("div"); vvi.classList.add("unit-vvi"); @@ -545,7 +540,7 @@ export class Unit extends CustomMarker { el.append(vvi); } - // Hotgroup indicator + /* Hotgroup indicator */ if (iconOptions.showHotgroup) { var hotgroup = document.createElement("div"); hotgroup.classList.add("unit-hotgroup"); @@ -555,42 +550,42 @@ export class Unit extends CustomMarker { el.append(hotgroup); } - // Main icon + /* Main icon */ if (iconOptions.showUnitIcon) { var unitIcon = document.createElement("div"); unitIcon.classList.add("unit-icon"); var img = document.createElement("img"); - var marker; /* If a unit does not belong to the commanded coalition or it is not visually detected, show it with the generic aircraft square */ - if (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))) - marker = databaseEntry?.markerFile ?? this.getMarkerCategory(); + var marker; + if (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))) + marker = this.getDatabaseEntry()?.markerFile ?? this.getMarkerCategory(); else marker = "aircraft"; - img.src = `/resources/theme/images/units/${marker}.svg`; img.onload = () => SVGInjector(img); unitIcon.appendChild(img); + unitIcon.toggleAttribute("data-rotate-to-heading", iconOptions.rotateToHeading); el.append(unitIcon); } - // State icon + /* State icon */ if (iconOptions.showState) { var state = document.createElement("div"); state.classList.add("unit-state"); el.appendChild(state); } - // Short label + /* Short label */ if (iconOptions.showShortLabel) { var shortLabel = document.createElement("div"); shortLabel.classList.add("unit-short-label"); - shortLabel.innerText = databaseEntry?.shortLabel || ""; + shortLabel.innerText = this.getDatabaseEntry()?.shortLabel || ""; el.append(shortLabel); } - // Fuel indicator + /* Fuel indicator */ if (iconOptions.showFuel) { var fuelIndicator = document.createElement("div"); fuelIndicator.classList.add("unit-fuel"); @@ -600,7 +595,7 @@ export class Unit extends CustomMarker { el.append(fuelIndicator); } - // Health indicator + /* Health indicator */ if (iconOptions.showHealth) { var healthIndicator = document.createElement("div"); healthIndicator.classList.add("unit-health"); @@ -610,7 +605,7 @@ export class Unit extends CustomMarker { el.append(healthIndicator); } - // Ammo indicator + /* Ammo indicator */ if (iconOptions.showAmmo) { var ammoIndicator = document.createElement("div"); ammoIndicator.classList.add("unit-ammo"); @@ -619,7 +614,7 @@ export class Unit extends CustomMarker { el.append(ammoIndicator); } - // Unit summary + /* Unit summary */ if (iconOptions.showSummary) { var summary = document.createElement("div"); summary.classList.add("unit-summary"); @@ -637,25 +632,29 @@ export class Unit extends CustomMarker { } this.getElement()?.appendChild(el); - - /* Circles don't like to be updated when the map is zooming */ - if (!getApp().getMap().isZooming()) - this.#drawRanges(); - else - this.once("zoomend", () => { this.#drawRanges(); }) } /********************** Visibility *************************/ updateVisibility() { - const hiddenUnits = getApp().getMap().getHiddenTypes(); - var hidden = ((this.#human && hiddenUnits.includes("human")) || - (this.#controlled == false && hiddenUnits.includes("dcs")) || - (hiddenUnits.includes(this.getMarkerCategory())) || - (hiddenUnits.includes(this.#coalition)) || + const hiddenTypes = getApp().getMap().getHiddenTypes(); + var hidden = ( + /* Hide the unit if it is a human and humans are hidden */ + (this.#human && hiddenTypes.includes("human")) || + /* Hide the unit if it is DCS controlled and DCS controlled units are hidden */ + (this.#controlled == false && hiddenTypes.includes("dcs")) || + /* Hide the unit if this specific category is hidden */ + (hiddenTypes.includes(this.getMarkerCategory())) || + /* Hide the unit if this coalition is hidden */ + (hiddenTypes.includes(this.#coalition)) || + /* Hide the unit if it does not belong to the commanded coalition and it is not detected by a method that can pinpoint its location (RWR does not count) */ (!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) || - (getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) && - !(this.getSelected()); + /* Hide the unit if grouping is activated, the unit is not the group leader, it is not selected, and the zoom is higher than the grouping threshold */ + (getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && + (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) && + !(this.getSelected() + ); + /* Force dead units to be hidden */ this.setHidden(hidden || !this.#alive); } @@ -675,6 +674,7 @@ export class Unit extends CustomMarker { getApp().getMap().removeLayer(this); } + /* Draw the range circles if the unit is not hidden */ if (!this.getHidden()) { /* Circles don't like to be updated when the map is zooming */ if (!getApp().getMap().isZooming()) @@ -714,7 +714,7 @@ export class Unit extends CustomMarker { if (typeof (roles) === "string") roles = [roles]; - var loadouts = this.getDatabase()?.getByName(this.#name)?.loadouts; + var loadouts = this.getDatabaseEntry()?.loadouts; if (loadouts) { return loadouts.some((loadout: LoadoutBlueprint) => { return (roles as string[]).some((role: string) => { return loadout.roles.includes(role) }); @@ -728,11 +728,11 @@ export class Unit extends CustomMarker { } canTargetPoint() { - return this.getDatabase()?.getByName(this.#name)?.canTargetPoint === true; + return this.getDatabaseEntry()?.canTargetPoint === true; } canRearm() { - return this.getDatabase()?.getByName(this.#name)?.canRearm === true; + return this.getDatabaseEntry()?.canRearm === true; } canLandAtPoint() { @@ -740,11 +740,11 @@ export class Unit extends CustomMarker { } canAAA() { - return this.getDatabase()?.getByName(this.#name)?.canAAA === true; + return this.getDatabaseEntry()?.canAAA === true; } indirectFire() { - return this.getDatabase()?.getByName(this.#name)?.indirectFire === true; + return this.getDatabaseEntry()?.indirectFire === true; } isTanker() { @@ -933,11 +933,6 @@ export class Unit extends CustomMarker { } /***********************************************/ - getActions(): { [key: string]: { text: string, tooltip: string, type: string } } { - /* To be implemented by child classes */ // TODO make Unit an abstract class - return {}; - } - executeAction(e: any, action: string) { if (action === "center-map") getApp().getMap().centerOnUnit(this.ID); @@ -963,18 +958,15 @@ export class Unit extends CustomMarker { /***********************************************/ #onClick(e: any) { - - // Exit if we were waiting for a doubleclick + /* Exit if we were waiting for a doubleclick */ if (this.#waitingForDoubleClick) { return; } - // We'll wait for a doubleclick + /* We'll wait for a doubleclick */ this.#waitingForDoubleClick = true; - this.#doubleClickTimer = window.setTimeout(() => { - - // Still waiting so no doubleclick; do the click action + /* Still waiting so no doubleclick; do the click action */ if (this.#waitingForDoubleClick) { if (getApp().getMap().getState() === IDLE || getApp().getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) { if (!e.originalEvent.ctrlKey) @@ -984,17 +976,17 @@ export class Unit extends CustomMarker { } } - // No longer waiting for a doubleclick + /* No longer waiting for a doubleclick */ this.#waitingForDoubleClick = false; }, 200); } #onDoubleClick(e: any) { - // Let single clicks work again + /* Let single clicks work again */ this.#waitingForDoubleClick = false; clearTimeout(this.#doubleClickTimer); - // Select all matching units in the viewport + /* Select all matching units in the viewport */ const unitsManager = getApp().getUnitsManager(); Object.values(unitsManager.getUnits()).forEach((unit: Unit) => { if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport()) @@ -1226,6 +1218,11 @@ export class Unit extends CustomMarker { if (hotgroupEl) hotgroupEl.innerText = String(this.#hotgroup); } + + /* If the unit is a leader of a SAM Site, show the group as a SAM with the appropriate short label */ + if (this.#isLeader) { + + } } /* Set vertical offset for altitude stacking */ @@ -1234,6 +1231,11 @@ export class Unit extends CustomMarker { } } + #redrawMarker() { + this.removeFrom(getApp().getMap()); + this.#updateMarker(); + } + #drawPath() { if (this.#activePath != undefined && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) { var points = []; @@ -1446,12 +1448,21 @@ export class Unit extends CustomMarker { this.#targetPositionPolyline.removeFrom(getApp().getMap()); } - #onZoom() { + #onZoom(e: any) { + if (this.checkRedraw()) { + this.#redrawMarker(); + + /* If the marker has been redrawn, reapply selection */ + if (this.getSelected()) { + this.setSelected(false); + this.setSelected(true); + } + } this.#updateMarker(); } } -export class AirUnit extends Unit { +export abstract class AirUnit extends Unit { getIconOptions() { var belongsToCommandedCoalition = this.belongsToCommandedCoalition(); return { @@ -1534,7 +1545,7 @@ export class GroundUnit extends Unit { showHealth: true, showHotgroup: belongsToCommandedCoalition, showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showShortLabel: false, + showShortLabel: this.getDatabaseEntry()?.type === "SAM Site", showFuel: false, showAmmo: false, showSummary: false, @@ -1584,6 +1595,31 @@ export class GroundUnit extends Unit { var blueprint = groundUnitDatabase.getByName(this.getName()); return blueprint?.type ? blueprint.type : ""; } + + /* When a unit is a leader of a group, the map is zoomed out and grouping when zoomed out is enabled, check if the unit should be shown as a specific group. This is used to show a SAM battery instead of the group leader */ + getDatabaseEntry() { + let unitWhenGrouped = null; + if (!this.getSelected() && this.getIsLeader() && getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION) { + unitWhenGrouped = this.getDatabase()?.getByName(this.getName())?.unitWhenGrouped ?? null; + let member = this.getGroupMembers().reduce((prev: Unit | null, unit: Unit, index: number) => { + if (unit.getDatabaseEntry()?.unitWhenGrouped != undefined) + return unit + return prev; + }, null); + unitWhenGrouped == member !== null ? member?.getDatabaseEntry()?.unitWhenGrouped : unitWhenGrouped; + } + if (unitWhenGrouped !== null) + return this.getDatabase()?.getByName(unitWhenGrouped); + else + return this.getDatabase()?.getByName(this.getName()); + } + + /* When we zoom past the grouping limit, grouping is enabled and the unit is a leader, we redraw the unit to apply any possible grouped marker */ + checkRedraw(): boolean { + return (this.getIsLeader() && getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && + (getApp().getMap().getZoom() >= GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() < GROUPING_ZOOM_TRANSITION || + getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() >= GROUPING_ZOOM_TRANSITION)) + } } export class NavyUnit extends Unit {