diff --git a/client/@types/olympus/index.d.ts b/client/@types/olympus/index.d.ts index 8418600c..226f5f6b 100644 --- a/client/@types/olympus/index.d.ts +++ b/client/@types/olympus/index.d.ts @@ -241,6 +241,7 @@ declare module "constants/constants" { contacts = 36, activePath = 37, isLeader = 38, + operateAs = 39, endOfData = 255 } } @@ -512,6 +513,7 @@ declare module "interfaces" { contacts: Contact[]; activePath: LatLng[]; isLeader: boolean; + operateAs: string; } export interface LoadoutItemBlueprint { name: string; @@ -532,7 +534,7 @@ declare module "interfaces" { label: string; shortLabel: string; type?: string; - range?: string; + rangeType?: string; loadouts?: LoadoutBlueprint[]; filename?: string; liveries?: { @@ -544,6 +546,14 @@ declare module "interfaces" { cost?: number; barrelHeight?: number; muzzleVelocity?: number; + aimTime?: number; + shotsToFire?: number; + description?: string; + abilities?: string; + acquisitionRange?: number; + engagementRange?: number; + refuelsFrom?: string; + refuelingType?: string; } export interface UnitSpawnOptions { roleType: string; @@ -721,6 +731,7 @@ declare module "other/utils" { export function enumToReactionToThreat(reactionToThreat: number): string; export function enumToEmissioNCountermeasure(emissionCountermeasure: number): string; export function enumToCoalition(coalitionID: number): "" | "blue" | "red" | "neutral"; + export function coalitionToEnum(coalition: string): 0 | 1 | 2; export function convertDateAndTimeToDate(dateAndTime: DateAndTime): Date; export function createCheckboxOption(value: string, text: string, checked?: boolean, callback?: CallableFunction): HTMLElement; export function getCheckboxOptions(dropdown: Dropdown): { @@ -752,6 +763,7 @@ declare module "controls/unitspawnmenu" { import { UnitSpawnOptions } from "interfaces"; export class UnitSpawnMenu { #private; + spawnOptions: UnitSpawnOptions; constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean); getContainer(): HTMLElement; reset(): void; @@ -1023,6 +1035,7 @@ declare module "unit/unit" { getContacts(): Contact[]; getActivePath(): LatLng[]; getIsLeader(): boolean; + getOperateAs(): string; static getConstructor(type: string): typeof GroundUnit | undefined; constructor(ID: number); getCategory(): string; @@ -1078,6 +1091,7 @@ declare module "unit/unit" { setEmissionsCountermeasures(emissionCountermeasure: string): void; setOnOff(onOff: boolean): void; setFollowRoads(followRoads: boolean): void; + setOperateAs(operateAs: string): void; delete(explosion: boolean, immediate: boolean): void; refuel(): void; setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings): void; @@ -1086,8 +1100,20 @@ declare module "unit/unit" { bombBuilding(latlng: LatLng): void; fireAtArea(latlng: LatLng): void; simulateFireFight(latlng: LatLng, targetGroundElevation: number | null): void; + scenicAAA(): void; + missOnPurpose(): void; + /***********************************************/ + getActions(): { + [key: string]: { + text: string; + tooltip: string; + type: string; + }; + }; + executeAction(e: any, action: string): void; /***********************************************/ onAdd(map: Map): this; + getActionOptions(): {}; } export class AirUnit extends Unit { getIconOptions(): { @@ -1102,6 +1128,13 @@ declare module "unit/unit" { showCallsign: boolean; rotateToHeading: boolean; }; + getActions(): { + [key: string]: { + text: string; + tooltip: string; + type: string; + }; + }; } export class Aircraft extends AirUnit { constructor(ID: number); @@ -1125,6 +1158,13 @@ declare module "unit/unit" { showCallsign: boolean; rotateToHeading: boolean; }; + getActions(): { + [key: string]: { + text: string; + tooltip: string; + type: string; + }; + }; getCategory(): string; getType(): string; } @@ -1142,6 +1182,13 @@ declare module "unit/unit" { showCallsign: boolean; rotateToHeading: boolean; }; + getActions(): { + [key: string]: { + text: string; + tooltip: string; + type: string; + }; + }; getMarkerCategory(): string; getCategory(): string; getType(): string; @@ -1310,6 +1357,14 @@ declare module "popups/popup" { declare module "map/touchboxselect" { export var TouchBoxSelect: (new (...args: any[]) => any) & typeof import("leaflet").Class; } +declare module "map/markers/destinationpreviewHandle" { + import { LatLng } from "leaflet"; + import { CustomMarker } from "map/markers/custommarker"; + export class DestinationPreviewHandle extends CustomMarker { + constructor(latlng: LatLng); + createIcon(): void; + } +} declare module "map/map" { import * as L from "leaflet"; import { MapContextMenu } from "contextmenus/mapcontextmenu"; @@ -1414,7 +1469,9 @@ declare module "panels/connectionstatuspanel" { import { Panel } from "panels/panel"; export class ConnectionStatusPanel extends Panel { constructor(ID: string); - update(connected: boolean): void; + showDisconnected(): void; + showConnected(): void; + showServerPaused(): void; } } declare module "panels/hotgrouppanel" { @@ -1615,7 +1672,7 @@ declare module "unit/unitsmanager" { * * @param hotgroup The hotgroup number */ - selectUnitsByHotgroup(hotgroup: number): void; + selectUnitsByHotgroup(hotgroup: number, deselectAllUnits?: boolean): void; /** Get all the currently selected units * * @param options Selection options @@ -1728,6 +1785,11 @@ declare module "unit/unitsmanager" { * @param followRoads If true, units will follow roads */ selectedUnitsSetFollowRoads(followRoads: boolean): void; + /** Instruct selected units to operate as a certain coalition + * + * @param operateAsBool If true, units will operate as blue + */ + selectedUnitsSetOperateAs(operateAsBool: boolean): void; /** Instruct units to attack a specific unit * * @param ID ID of the unit to attack @@ -1768,6 +1830,14 @@ declare module "unit/unitsmanager" { * @param latlng Location to fire at */ selectedUnitsSimulateFireFight(latlng: LatLng): void; + /** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming + * + */ + selectedUnitsScenicAAA(): void; + /** Instruct units to enter into miss on purpose mode. Units will aim to the nearest enemy unit but not precisely. + * + */ + selectedUnitsMissOnPurpose(): void; /*********************** Control operations on selected units ************************/ /** See getUnitsCategories for more info * @@ -1948,12 +2018,15 @@ declare module "server/servermanager" { setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback?: CallableFunction): void; setOnOff(ID: number, onOff: boolean, callback?: CallableFunction): void; setFollowRoads(ID: number, followRoads: boolean, callback?: CallableFunction): void; + setOperateAs(ID: number, operateAs: number, callback?: CallableFunction): void; refuel(ID: number, callback?: CallableFunction): void; bombPoint(ID: number, latlng: LatLng, callback?: CallableFunction): void; carpetBomb(ID: number, latlng: LatLng, callback?: CallableFunction): void; bombBuilding(ID: number, latlng: LatLng, callback?: CallableFunction): void; fireAtArea(ID: number, latlng: LatLng, callback?: CallableFunction): void; simulateFireFight(ID: number, latlng: LatLng, altitude: number, callback?: CallableFunction): void; + scenicAAA(ID: number, coalition: string, callback?: CallableFunction): void; + missOnPurpose(ID: number, coalition: string, callback?: CallableFunction): void; setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback?: CallableFunction): void; setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: { blue: number; @@ -1967,14 +2040,14 @@ declare module "server/servermanager" { getConnected(): boolean; setPaused(newPaused: boolean): void; getPaused(): boolean; + getServerIsPaused(): boolean; } } declare module "panels/unitlistpanel" { - import { OlympusApp } from "olympusapp"; import { Panel } from "panels/panel"; export class UnitListPanel extends Panel { #private; - constructor(olympusApp: OlympusApp, panelElement: string, contentElement: string); + constructor(panelElement: string, contentElement: string); doUpdate(): void; getContentElement(): HTMLElement; startUpdates(): void; diff --git a/client/demo.js b/client/demo.js index 17321e79..1e2288d0 100644 --- a/client/demo.js +++ b/client/demo.js @@ -48,7 +48,7 @@ const DEMO_UNIT_DATA = { ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], contacts: [{ID: 1, detectionMethod: 16}], activePath: [ ] - }, ["4"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "Gepard", unitName: "Cool guy 2-1", groupName: "Cool group 4", state: 1, task: "Being cool", + }, ["4"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 0, country: 0, name: "Gepard", unitName: "Cool guy 2-1", groupName: "Cool group 4", state: 1, task: "Being cool", hasTask: false, position: { lat: 37.2, lng: -116.1, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0, formationOffset: { x: 0, y: 0, z: 0 }, @@ -63,8 +63,9 @@ const DEMO_UNIT_DATA = { ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ], contacts: [{ID: 1001, detectionMethod: 16}], activePath: [ ], - isLeader: true - }, ["5"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "S_75M_Volhov", unitName: "Cool guy 2-2", groupName: "Cool group 4", state: 1, task: "Being cool", + isLeader: true, + operateAs: 2 + }, ["5"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 0, country: 0, name: "Gepard", unitName: "Cool guy 2-2", groupName: "Cool group 4", state: 1, task: "Being cool", hasTask: false, position: { lat: 37.21, lng: -116.1, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0, formationOffset: { x: 0, y: 0, z: 0 }, @@ -79,7 +80,8 @@ const DEMO_UNIT_DATA = { ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], contacts: [], activePath: [ ], - isLeader: false + isLeader: false, + operateAs: 2 }, ["6"]:{ category: "Aircraft", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "FA-18C_hornet", unitName: "Bad boi 1-2", groupName: "Bad group 1", state: 1, task: "Being bad", hasTask: false, position: { lat: 36.8, lng: -116, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, @@ -187,6 +189,7 @@ class DemoDataGenerator { array = this.appendContacts(array, unit.contacts, 36); array = this.appendActivePath(array, unit.activePath, 37); array = this.appendUint8(array, unit.isLeader, 38); + array = this.appendUint8(array, unit.operateAs, 39); array = this.concat(array, this.uint8ToByteArray(255)); } res.end(Buffer.from(array, 'binary')); diff --git a/client/plugins/databasemanager/src/airuniteditor.ts b/client/plugins/databasemanager/src/airuniteditor.ts index b96e8275..c4ab6f2f 100644 --- a/client/plugins/databasemanager/src/airuniteditor.ts +++ b/client/plugins/databasemanager/src/airuniteditor.ts @@ -44,6 +44,8 @@ export class AirUnitEditor extends UnitEditor { addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"]); addStringInput(this.contentDiv2, "Filename", blueprint.filename ?? "", "text", (value: string) => { blueprint.filename = value; }); addStringInput(this.contentDiv2, "Cost", String(blueprint.cost) ?? "", "number", (value: string) => { blueprint.cost = parseFloat(value); }); + addStringInput(this.contentDiv2, "Rufels from", String(blueprint.refuelsFrom) ?? "", "text", (value: string) => { blueprint.refuelsFrom = value; }); + addStringInput(this.contentDiv2, "Refueling type", String(blueprint.refuelingType) ?? "", "text", (value: string) => { blueprint.refuelingType = value; }); /* Add a scrollable list of loadouts that the user can edit */ var title = document.createElement("label"); diff --git a/client/plugins/databasemanager/src/databasemanagerplugin.ts b/client/plugins/databasemanager/src/databasemanagerplugin.ts index 253edc5a..a842acb2 100644 --- a/client/plugins/databasemanager/src/databasemanagerplugin.ts +++ b/client/plugins/databasemanager/src/databasemanagerplugin.ts @@ -157,7 +157,7 @@ export class DatabaseManagerPlugin implements OlympusPlugin { */ initialize(app: any) { this.#app = app; - + /* Load the databases and initialize the editors */ this.#loadDatabases(); @@ -173,9 +173,10 @@ export class DatabaseManagerPlugin implements OlympusPlugin { toolbar.getMainDropdown().setOptionsElements(arr); mainButton.onclick = () => { toolbar.getMainDropdown().close(); - this.toggle(); + if (this.#app?.getMissionManager().getCommandModeOptions().commandMode === "Game master") + this.toggle(); } - + return true; } diff --git a/client/plugins/databasemanager/src/grounduniteditor.ts b/client/plugins/databasemanager/src/grounduniteditor.ts index 53842e31..5f6ea6f2 100644 --- a/client/plugins/databasemanager/src/grounduniteditor.ts +++ b/client/plugins/databasemanager/src/grounduniteditor.ts @@ -34,8 +34,12 @@ export class GroundUnitEditor extends UnitEditor { addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"]); //addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; }); addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); }); + addStringInput(this.contentDiv2, "Acquisition range [NM]", String(blueprint.acquisitionRange)?? "", "number", (value: string) => {blueprint.acquisitionRange = parseFloat(value); }); + addStringInput(this.contentDiv2, "Engagement range [NM]", String(blueprint.engagementRange)?? "", "number", (value: string) => {blueprint.engagementRange = parseFloat(value); }); addStringInput(this.contentDiv2, "Barrel height [m]", String(blueprint.barrelHeight)?? "", "number", (value: string) => {blueprint.barrelHeight = parseFloat(value); }); addStringInput(this.contentDiv2, "Muzzle velocity [m/s]", String(blueprint.muzzleVelocity)?? "", "number", (value: string) => {blueprint.muzzleVelocity = parseFloat(value); }); + addStringInput(this.contentDiv2, "Aim time [s]", String(blueprint.aimTime)?? "", "number", (value: string) => {blueprint.aimTime = parseFloat(value); }); + addStringInput(this.contentDiv2, "Burst quantity", String(blueprint.shotsToFire)?? "", "number", (value: string) => {blueprint.shotsToFire = Math.round(parseFloat(value)); }); } } diff --git a/client/plugins/databasemanager/style.css b/client/plugins/databasemanager/style.css index 8ac5be31..3c1a6b24 100644 --- a/client/plugins/databasemanager/style.css +++ b/client/plugins/databasemanager/style.css @@ -9,15 +9,16 @@ z-index: 9999999; } -@media (orientation: landscape) { +@media (min-width: 1200px) { .dm-container { flex-direction: row; } } -@media (orientation: portrait) { +@media (max-width: 1200px) { .dm-container { flex-direction: column; + overflow-y: auto; } } @@ -56,14 +57,6 @@ border-radius: 0px 5px 5px 5px; } -.dm-container>div:nth-child(2) { - width: 500px; -} - -.dm-container>div:nth-child(3) { - flex: 1; -} - .dm-content-container { position: relative; margin: 10px; @@ -72,19 +65,40 @@ row-gap: 5px; } -@media (orientation: landscape) { +@media (min-width: 1200px) { .dm-content-container { height: calc(100% - 20px); - min-width: 200px; - width: fit-content; + } + + .dm-content-container:nth-of-type(1) { + width: 200px; + } + + .dm-content-container:nth-of-type(2) { + width: 500px; + } + + .dm-content-container:nth-of-type(3) { + flex: 1; } } -@media (orientation: portrait) { +@media (max-width: 1200px) { .dm-content-container { - width: 100% - calc(20px); + width: calc(100% - 20px); + } + + .dm-content-container:nth-of-type(1) { height: 30%; } + + .dm-content-container:nth-of-type(2) { + height: 50%; + } + + .dm-content-container:nth-of-type(3) { + flex: 1; + } } .dm-content-container>label { @@ -95,7 +109,7 @@ .dm-scroll-container { display: flex; flex-direction: column; - overflow-y: auto; + overflow-y: scroll; max-height: 100%; color: black; font-weight: bold; @@ -113,7 +127,6 @@ height: 100%; width: calc(100% - 25px); padding: 2px; - text-wrap: wrap; word-wrap: break-word; } @@ -141,6 +154,15 @@ flex-direction: row; } +@media (max-width: 1200px) { + .dm-content-container label { + width: 100%; + } + .input-row { + width: 50%; + } +} + .input-row>dt { width: 250px; } diff --git a/client/public/databases/units/groundunitdatabase.json b/client/public/databases/units/groundunitdatabase.json index 5f099c6d..c83e7534 100644 --- a/client/public/databases/units/groundunitdatabase.json +++ b/client/public/databases/units/groundunitdatabase.json @@ -681,16 +681,6 @@ } } }, - "Boman": { - "name": "Boman", - "coalition": "blue", - "era": "Late Cold War", - "label": "Grad Fire Direction Manager", - "shortLabel": "Boman", - "filename": "", - "type": "Reconnaissance", - "enabled": true - }, "Bunker": { "name": "Bunker", "coalition": "", @@ -4160,17 +4150,6 @@ "type": "SAM Site", "enabled": true }, - "SA-8 Osa LD 9T217": { - "name": "SA-8 Osa LD 9T217", - "coalition": "red", - "era": "Late Cold War", - "label": "SA-8 Osa LD 9T217", - "shortLabel": "SA-8 Osa LD 9T217", - "range": "Short", - "filename": "", - "type": "SAM", - "enabled": true - }, "SAU 2-C9": { "name": "SAU 2-C9", "coalition": "red", @@ -4773,93 +4752,6 @@ } } }, - "Stinger manpad GRG": { - "name": "Stinger manpad GRG", - "coalition": "blue", - "era": "Late Cold War", - "label": "Stinger manpad GRG", - "shortLabel": "Stinger manpad GRG", - "range": "Short", - "filename": "", - "type": "MANPADS", - "enabled": true, - "liveries": { - "grc_spring": { - "name": "GRC_Spring", - "countries": "All" - }, - "grc_summer": { - "name": "GRC_Summer", - "countries": "All" - }, - "grc_autumn": { - "name": "GRC_Autumn", - "countries": "All" - }, - "grc_winter": { - "name": "GRC_Winter", - "countries": "All" - } - } - }, - "Stinger manpad dsr": { - "name": "Stinger manpad dsr", - "coalition": "blue", - "era": "Late Cold War", - "label": "Stinger manpad dsr", - "shortLabel": "Stinger manpad dsr", - "range": "Short", - "filename": "", - "type": "MANPADS", - "enabled": true, - "liveries": { - "grc_spring": { - "name": "GRC_Spring", - "countries": "All" - }, - "grc_summer": { - "name": "GRC_Summer", - "countries": "All" - }, - "grc_autumn": { - "name": "GRC_Autumn", - "countries": "All" - }, - "grc_winter": { - "name": "GRC_Winter", - "countries": "All" - } - } - }, - "Stinger manpad": { - "name": "Stinger manpad", - "coalition": "blue", - "era": "Late Cold War", - "label": "Stinger manpad", - "shortLabel": "Stinger manpad", - "range": "Short", - "filename": "", - "type": "MANPADS", - "enabled": true, - "liveries": { - "grc_spring": { - "name": "GRC_Spring", - "countries": "All" - }, - "grc_summer": { - "name": "GRC_Summer", - "countries": "All" - }, - "grc_autumn": { - "name": "GRC_Autumn", - "countries": "All" - }, - "grc_winter": { - "name": "GRC_Winter", - "countries": "All" - } - } - }, "Strela-1 9P31": { "name": "Strela-1 9P31", "coalition": "red", @@ -5991,5 +5883,1445 @@ "countries": "All" } } + }, + "SpGH_Dana": { + "name": "SpGH_Dana", + "coalition": "", + "era": "", + "label": "SPH Dana vz77 152mm", + "shortLabel": "SPH Dana vz77 152mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "Grad_FDDM": { + "name": "Grad_FDDM", + "coalition": "", + "era": "", + "label": "Grad MRL FDDM (FC)", + "shortLabel": "Grad MRL FDDM (FC)", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "Infantry AK Ins": { + "name": "Infantry AK Ins", + "coalition": "", + "era": "", + "label": "Insurgent AK-74", + "shortLabel": "Insurgent AK-74", + "type": "Infantry", + "enabled": true, + "liveries": {} + }, + "MLRS FDDM": { + "name": "MLRS FDDM", + "coalition": "", + "era": "", + "label": "MRLS FDDM (FC)", + "shortLabel": "MRLS FDDM (FC)", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "Infantry AK ver2": { + "name": "Infantry AK ver2", + "coalition": "", + "era": "", + "label": "Infantry AK-74 Rus ver2", + "shortLabel": "Infantry AK-74 Rus ver2", + "type": "Infantry", + "enabled": true, + "liveries": {} + }, + "Infantry AK ver3": { + "name": "Infantry AK ver3", + "coalition": "", + "era": "", + "label": "Infantry AK-74 Rus ver3", + "shortLabel": "Infantry AK-74 Rus ver3", + "type": "Infantry", + "enabled": true, + "liveries": {} + }, + "Smerch_HE": { + "name": "Smerch_HE", + "coalition": "", + "era": "", + "label": "MLRS 9A52 Smerch HE 300mm", + "shortLabel": "MLRS 9A52 Smerch HE 300mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "Soldier stinger": { + "name": "Soldier stinger", + "coalition": "", + "era": "", + "label": "MANPADS Stinger", + "shortLabel": "MANPADS Stinger", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "SA-18 Igla comm": { + "name": "SA-18 Igla comm", + "coalition": "", + "era": "", + "label": "MANPADS SA-18 Igla \"Grouse\" C2", + "shortLabel": "MANPADS SA-18 Igla \"Grouse\" C2", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "SA-18 Igla-S comm": { + "name": "SA-18 Igla-S comm", + "coalition": "", + "era": "", + "label": "MANPADS SA-18 Igla-S \"Grouse\" C2", + "shortLabel": "MANPADS SA-18 Igla-S \"Grouse\" C2", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "TACAN_beacon": { + "name": "TACAN_beacon", + "coalition": "", + "era": "", + "label": "Beacon TACAN Portable TTS 3030", + "shortLabel": "Beacon TACAN Portable TTS 3030", + "type": "Fortification", + "enabled": true, + "liveries": {} + }, + "Merkava_Mk4": { + "name": "Merkava_Mk4", + "coalition": "", + "era": "", + "label": "MBT Merkava IV", + "shortLabel": "MBT Merkava IV", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "LiAZ Bus": { + "name": "LiAZ Bus", + "coalition": "", + "era": "", + "label": "Bus LiAZ-677", + "shortLabel": "Bus LiAZ-677", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "KrAZ6322": { + "name": "KrAZ6322", + "coalition": "", + "era": "", + "label": "Truck KrAZ-6322 6x6", + "shortLabel": "Truck KrAZ-6322 6x6", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "JTAC": { + "name": "JTAC", + "coalition": "", + "era": "", + "label": "JTAC", + "shortLabel": "JTAC", + "type": "Infantry", + "enabled": true, + "liveries": {} + }, + "Infantry Animated": { + "name": "Infantry Animated", + "coalition": "", + "era": "", + "label": "Infantry", + "shortLabel": "Infantry", + "type": "Infantry", + "enabled": true, + "liveries": {} + }, + "Electric locomotive": { + "name": "Electric locomotive", + "coalition": "", + "era": "", + "label": "Loco VL80 Electric", + "shortLabel": "Loco VL80 Electric", + "type": "Locomotive", + "enabled": true, + "liveries": {} + }, + "Locomotive": { + "name": "Locomotive", + "coalition": "", + "era": "", + "label": "Loco CHME3T", + "shortLabel": "Loco CHME3T", + "type": "Locomotive", + "enabled": true, + "liveries": {} + }, + "Coach cargo": { + "name": "Coach cargo", + "coalition": "", + "era": "", + "label": "Freight Van", + "shortLabel": "Freight Van", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "Coach cargo open": { + "name": "Coach cargo open", + "coalition": "", + "era": "", + "label": "Open Wagon", + "shortLabel": "Open Wagon", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "Coach a tank blue": { + "name": "Coach a tank blue", + "coalition": "", + "era": "", + "label": "Tank Car blue", + "shortLabel": "Tank Car blue", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "Coach a tank yellow": { + "name": "Coach a tank yellow", + "coalition": "", + "era": "", + "label": "Tank Car yellow", + "shortLabel": "Tank Car yellow", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "Coach a passenger": { + "name": "Coach a passenger", + "coalition": "", + "era": "", + "label": "Passenger Car", + "shortLabel": "Passenger Car", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "Coach a platform": { + "name": "Coach a platform", + "coalition": "", + "era": "", + "label": "Coach Platform", + "shortLabel": "Coach Platform", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "tacr2a": { + "name": "tacr2a", + "coalition": "", + "era": "", + "label": "RAF Rescue", + "shortLabel": "RAF Rescue", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "LARC-V": { + "name": "LARC-V", + "coalition": "", + "era": "", + "label": "LARC-V", + "shortLabel": "LARC-V", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "KS-19": { + "name": "KS-19", + "coalition": "", + "era": "", + "label": "AAA KS-19 100mm", + "shortLabel": "AAA KS-19 100mm", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "SON_9": { + "name": "SON_9", + "coalition": "", + "era": "", + "label": "AAA Fire Can SON-9", + "shortLabel": "AAA Fire Can SON-9", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "Scud_B": { + "name": "Scud_B", + "coalition": "", + "era": "", + "label": "SSM SS-1C Scud-B", + "shortLabel": "SSM SS-1C Scud-B", + "type": "MissilesSS", + "enabled": true, + "liveries": {} + }, + "HL_DSHK": { + "name": "HL_DSHK", + "coalition": "", + "era": "", + "label": "Scout HL with DSHK 12.7mm", + "shortLabel": "Scout HL with DSHK 12.7mm", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "HL_KORD": { + "name": "HL_KORD", + "coalition": "", + "era": "", + "label": "Scout HL with KORD 12.7mm", + "shortLabel": "Scout HL with KORD 12.7mm", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "tt_DSHK": { + "name": "tt_DSHK", + "coalition": "", + "era": "", + "label": "Scout LC with DSHK 12.7mm", + "shortLabel": "Scout LC with DSHK 12.7mm", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "tt_KORD": { + "name": "tt_KORD", + "coalition": "", + "era": "", + "label": "Scout LC with KORD 12.7mm", + "shortLabel": "Scout LC with KORD 12.7mm", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "HL_ZU-23": { + "name": "HL_ZU-23", + "coalition": "", + "era": "", + "label": "SPAAA HL with ZU-23", + "shortLabel": "SPAAA HL with ZU-23", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "tt_ZU-23": { + "name": "tt_ZU-23", + "coalition": "", + "era": "", + "label": "SPAAA LC with ZU-23", + "shortLabel": "SPAAA LC with ZU-23", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "HL_B8M1": { + "name": "HL_B8M1", + "coalition": "", + "era": "", + "label": "MLRS HL with B8M1 80mm", + "shortLabel": "MLRS HL with B8M1 80mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "tt_B8M1": { + "name": "tt_B8M1", + "coalition": "", + "era": "", + "label": "MLRS LC with B8M1 80mm", + "shortLabel": "MLRS LC with B8M1 80mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "NASAMS_Radar_MPQ64F1": { + "name": "NASAMS_Radar_MPQ64F1", + "coalition": "", + "era": "", + "label": "SAM NASAMS SR MPQ64F1", + "shortLabel": "SAM NASAMS SR MPQ64F1", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "NASAMS_Command_Post": { + "name": "NASAMS_Command_Post", + "coalition": "", + "era": "", + "label": "SAM NASAMS C2", + "shortLabel": "SAM NASAMS C2", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "NASAMS_LN_B": { + "name": "NASAMS_LN_B", + "coalition": "", + "era": "", + "label": "SAM NASAMS LN AIM-120B", + "shortLabel": "SAM NASAMS LN AIM-120B", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "NASAMS_LN_C": { + "name": "NASAMS_LN_C", + "coalition": "", + "era": "", + "label": "SAM NASAMS LN AIM-120C", + "shortLabel": "SAM NASAMS LN AIM-120C", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "M4_Sherman": { + "name": "M4_Sherman", + "coalition": "", + "era": "", + "label": "Tk M4 Sherman", + "shortLabel": "Tk M4 Sherman", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "M2A1_halftrack": { + "name": "M2A1_halftrack", + "coalition": "", + "era": "", + "label": "APC M2A1 Halftrack", + "shortLabel": "APC M2A1 Halftrack", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "FPS-117 Dome": { + "name": "FPS-117 Dome", + "coalition": "", + "era": "", + "label": "EWR AN/FPS-117 Radar (domed)", + "shortLabel": "EWR AN/FPS-117 Radar (domed)", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "FPS-117 ECS": { + "name": "FPS-117 ECS", + "coalition": "", + "era": "", + "label": "EWR AN/FPS-117 ECS", + "shortLabel": "EWR AN/FPS-117 ECS", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "FPS-117": { + "name": "FPS-117", + "coalition": "", + "era": "", + "label": "EWR AN/FPS-117 Radar", + "shortLabel": "EWR AN/FPS-117 Radar", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "RD_75": { + "name": "RD_75", + "coalition": "", + "era": "", + "label": "SAM SA-2 S-75 RD-75 Amazonka RF", + "shortLabel": "SAM SA-2 S-75 RD-75 Amazonka RF", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "ZSU_57_2": { + "name": "ZSU_57_2", + "coalition": "", + "era": "", + "label": "SPAAA ZSU-57-2", + "shortLabel": "SPAAA ZSU-57-2", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "S-60_Type59_Artillery": { + "name": "S-60_Type59_Artillery", + "coalition": "", + "era": "", + "label": "AAA S-60 57mm", + "shortLabel": "AAA S-60 57mm", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "generator_5i57": { + "name": "generator_5i57", + "coalition": "", + "era": "", + "label": "Diesel Power Station 5I57A", + "shortLabel": "Diesel Power Station 5I57A", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "T-72B3": { + "name": "T-72B3", + "coalition": "", + "era": "", + "label": "MBT T-72B3", + "shortLabel": "MBT T-72B3", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "PT_76": { + "name": "PT_76", + "coalition": "", + "era": "", + "label": "LT PT-76", + "shortLabel": "LT PT-76", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "BTR-82A": { + "name": "BTR-82A", + "coalition": "", + "era": "", + "label": "IFV BTR-82A", + "shortLabel": "IFV BTR-82A", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "ATZ-5": { + "name": "ATZ-5", + "coalition": "", + "era": "", + "label": "Refueler ATZ-5", + "shortLabel": "Refueler ATZ-5", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "AA8": { + "name": "AA8", + "coalition": "", + "era": "", + "label": "Firefighter Vehicle AA-7.2/60", + "shortLabel": "Firefighter Vehicle AA-7.2/60", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "TZ-22_KrAZ": { + "name": "TZ-22_KrAZ", + "coalition": "", + "era": "", + "label": "Refueler TZ-22 Tractor (KrAZ-258B1)", + "shortLabel": "Refueler TZ-22 Tractor (KrAZ-258B1)", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "ATZ-60_Maz": { + "name": "ATZ-60_Maz", + "coalition": "", + "era": "", + "label": "Refueler ATZ-60 Tractor (MAZ-7410)", + "shortLabel": "Refueler ATZ-60 Tractor (MAZ-7410)", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "ZIL-135": { + "name": "ZIL-135", + "coalition": "", + "era": "", + "label": "Truck ZIL-135", + "shortLabel": "Truck ZIL-135", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "S_75_ZIL": { + "name": "S_75_ZIL", + "coalition": "", + "era": "", + "label": "S-75 Tractor (ZIL-131)", + "shortLabel": "S-75 Tractor (ZIL-131)", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "rapier_fsa_launcher": { + "name": "rapier_fsa_launcher", + "coalition": "", + "era": "", + "label": "SAM Rapier LN", + "shortLabel": "SAM Rapier LN", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "rapier_fsa_optical_tracker_unit": { + "name": "rapier_fsa_optical_tracker_unit", + "coalition": "", + "era": "", + "label": "SAM Rapier Tracker", + "shortLabel": "SAM Rapier Tracker", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "rapier_fsa_blindfire_radar": { + "name": "rapier_fsa_blindfire_radar", + "coalition": "", + "era": "", + "label": "SAM Rapier Blindfire TR", + "shortLabel": "SAM Rapier Blindfire TR", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "bofors40": { + "name": "bofors40", + "coalition": "", + "era": "", + "label": "AAA Bofors 40mm", + "shortLabel": "AAA Bofors 40mm", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "Chieftain_mk3": { + "name": "Chieftain_mk3", + "coalition": "", + "era": "", + "label": "MBT Chieftain Mk.3", + "shortLabel": "MBT Chieftain Mk.3", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Bedford_MWD": { + "name": "Bedford_MWD", + "coalition": "", + "era": "", + "label": "Truck Bedford", + "shortLabel": "Truck Bedford", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "Land_Rover_101_FC": { + "name": "Land_Rover_101_FC", + "coalition": "", + "era": "", + "label": "Truck Land Rover 101 FC", + "shortLabel": "Truck Land Rover 101 FC", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "Land_Rover_109_S3": { + "name": "Land_Rover_109_S3", + "coalition": "", + "era": "", + "label": "LUV Land Rover 109", + "shortLabel": "LUV Land Rover 109", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "hy_launcher": { + "name": "hy_launcher", + "coalition": "", + "era": "", + "label": "AShM SS-N-2 Silkworm", + "shortLabel": "AShM SS-N-2 Silkworm", + "type": "MissilesSS", + "enabled": true, + "liveries": {} + }, + "Silkworm_SR": { + "name": "Silkworm_SR", + "coalition": "", + "era": "", + "label": "AShM Silkworm SR", + "shortLabel": "AShM Silkworm SR", + "type": "MissilesSS", + "enabled": true, + "liveries": {} + }, + "ES44AH": { + "name": "ES44AH", + "coalition": "", + "era": "", + "label": "Loco ES44AH", + "shortLabel": "Loco ES44AH", + "type": "Locomotive", + "enabled": true, + "liveries": {} + }, + "Boxcartrinity": { + "name": "Boxcartrinity", + "coalition": "", + "era": "", + "label": "Flatcar", + "shortLabel": "Flatcar", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "Tankcartrinity": { + "name": "Tankcartrinity", + "coalition": "", + "era": "", + "label": "Tank Cartrinity", + "shortLabel": "Tank Cartrinity", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "Wellcarnsc": { + "name": "Wellcarnsc", + "coalition": "", + "era": "", + "label": "Well Car", + "shortLabel": "Well Car", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "flak18": { + "name": "flak18", + "coalition": "", + "era": "", + "label": "AAA 8,8cm Flak 18", + "shortLabel": "AAA 8,8cm Flak 18", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "Pz_IV_H": { + "name": "Pz_IV_H", + "coalition": "", + "era": "", + "label": "Tk PzIV H", + "shortLabel": "Tk PzIV H", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Leopard-2A5": { + "name": "Leopard-2A5", + "coalition": "", + "era": "", + "label": "MBT Leopard-2A5", + "shortLabel": "MBT Leopard-2A5", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "leopard-2A4": { + "name": "leopard-2A4", + "coalition": "", + "era": "", + "label": "MBT Leopard-2A4", + "shortLabel": "MBT Leopard-2A4", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "leopard-2A4_trs": { + "name": "leopard-2A4_trs", + "coalition": "", + "era": "", + "label": "MBT Leopard-2A4 Trs", + "shortLabel": "MBT Leopard-2A4 Trs", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Sd_Kfz_251": { + "name": "Sd_Kfz_251", + "coalition": "", + "era": "", + "label": "APC Sd.Kfz.251 Halftrack", + "shortLabel": "APC Sd.Kfz.251 Halftrack", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Blitz_36-6700A": { + "name": "Blitz_36-6700A", + "coalition": "", + "era": "", + "label": "Truck Opel Blitz", + "shortLabel": "Truck Opel Blitz", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "T155_Firtina": { + "name": "T155_Firtina", + "coalition": "", + "era": "", + "label": "SPH T155 Firtina 155mm", + "shortLabel": "SPH T155 Firtina 155mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "VAB_Mephisto": { + "name": "VAB_Mephisto", + "coalition": "", + "era": "", + "label": "ATGM VAB Mephisto", + "shortLabel": "ATGM VAB Mephisto", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "ZTZ96B": { + "name": "ZTZ96B", + "coalition": "", + "era": "", + "label": "ZTZ-96B", + "shortLabel": "ZTZ-96B", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "ZBD04A": { + "name": "ZBD04A", + "coalition": "", + "era": "", + "label": "ZBD-04A", + "shortLabel": "ZBD-04A", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "HQ-7_LN_SP": { + "name": "HQ-7_LN_SP", + "coalition": "", + "era": "", + "label": "HQ-7 Self-Propelled LN", + "shortLabel": "HQ-7 Self-Propelled LN", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "HQ-7_LN_EO": { + "name": "HQ-7_LN_EO", + "coalition": "", + "era": "", + "label": "HQ-7 LN Electro-Optics", + "shortLabel": "HQ-7 LN Electro-Optics", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "HQ-7_STR_SP": { + "name": "HQ-7_STR_SP", + "coalition": "", + "era": "", + "label": "HQ-7 Self-Propelled STR", + "shortLabel": "HQ-7 Self-Propelled STR", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "PLZ05": { + "name": "PLZ05", + "coalition": "", + "era": "", + "label": "PLZ-05", + "shortLabel": "PLZ-05", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "TYPE-59": { + "name": "TYPE-59", + "coalition": "", + "era": "", + "label": "MT Type 59", + "shortLabel": "MT Type 59", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Kubelwagen_82": { + "name": "Kubelwagen_82", + "coalition": "", + "era": "", + "label": "LUV Kubelwagen Jeep", + "shortLabel": "LUV Kubelwagen Jeep", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "Sd_Kfz_2": { + "name": "Sd_Kfz_2", + "coalition": "", + "era": "", + "label": "LUV Kettenrad", + "shortLabel": "LUV Kettenrad", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "Sd_Kfz_7": { + "name": "Sd_Kfz_7", + "coalition": "", + "era": "", + "label": "Tractor Sd.Kfz.7 Art'y Tractor", + "shortLabel": "Tractor Sd.Kfz.7 Art'y Tractor", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "Horch_901_typ_40_kfz_21": { + "name": "Horch_901_typ_40_kfz_21", + "coalition": "", + "era": "", + "label": "LUV Horch 901 Staff Car", + "shortLabel": "LUV Horch 901 Staff Car", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "Tiger_I": { + "name": "Tiger_I", + "coalition": "", + "era": "", + "label": "Tk Tiger 1", + "shortLabel": "Tk Tiger 1", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Tiger_II_H": { + "name": "Tiger_II_H", + "coalition": "", + "era": "", + "label": "Tk Tiger II", + "shortLabel": "Tk Tiger II", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Pz_V_Panther_G": { + "name": "Pz_V_Panther_G", + "coalition": "", + "era": "", + "label": "Tk Panther G (Pz V)", + "shortLabel": "Tk Panther G (Pz V)", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Jagdpanther_G1": { + "name": "Jagdpanther_G1", + "coalition": "", + "era": "", + "label": "SPG Jagdpanther TD", + "shortLabel": "SPG Jagdpanther TD", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "JagdPz_IV": { + "name": "JagdPz_IV", + "coalition": "", + "era": "", + "label": "SPG Jagdpanzer IV TD", + "shortLabel": "SPG Jagdpanzer IV TD", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Stug_IV": { + "name": "Stug_IV", + "coalition": "", + "era": "", + "label": "SPG StuG IV AG", + "shortLabel": "SPG StuG IV AG", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "SturmPzIV": { + "name": "SturmPzIV", + "coalition": "", + "era": "", + "label": "SPG Brummbaer AG", + "shortLabel": "SPG Brummbaer AG", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Wespe124": { + "name": "Wespe124", + "coalition": "", + "era": "", + "label": "SPH Sd.Kfz.124 Wespe 105mm", + "shortLabel": "SPH Sd.Kfz.124 Wespe 105mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "Sd_Kfz_234_2_Puma": { + "name": "Sd_Kfz_234_2_Puma", + "coalition": "", + "era": "", + "label": "Scout Puma AC", + "shortLabel": "Scout Puma AC", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "flak30": { + "name": "flak30", + "coalition": "", + "era": "", + "label": "AAA Flak 38 20mm", + "shortLabel": "AAA Flak 38 20mm", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "flak36": { + "name": "flak36", + "coalition": "", + "era": "", + "label": "AAA 8,8cm Flak 36", + "shortLabel": "AAA 8,8cm Flak 36", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "flak37": { + "name": "flak37", + "coalition": "", + "era": "", + "label": "AAA 8,8cm Flak 37", + "shortLabel": "AAA 8,8cm Flak 37", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "flak38": { + "name": "flak38", + "coalition": "", + "era": "", + "label": "AAA Flak-Vierling 38 Quad 20mm", + "shortLabel": "AAA Flak-Vierling 38 Quad 20mm", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "KDO_Mod40": { + "name": "KDO_Mod40", + "coalition": "", + "era": "", + "label": "AAA Kdo.G.40", + "shortLabel": "AAA Kdo.G.40", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "Flakscheinwerfer_37": { + "name": "Flakscheinwerfer_37", + "coalition": "", + "era": "", + "label": "SL Flakscheinwerfer 37", + "shortLabel": "SL Flakscheinwerfer 37", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "Maschinensatz_33": { + "name": "Maschinensatz_33", + "coalition": "", + "era": "", + "label": "Maschinensatz 33 Gen", + "shortLabel": "Maschinensatz 33 Gen", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "soldier_mauser98": { + "name": "soldier_mauser98", + "coalition": "", + "era": "", + "label": "Infantry Mauser 98", + "shortLabel": "Infantry Mauser 98", + "type": "Infantry", + "enabled": true, + "liveries": {} + }, + "SK_C_28_naval_gun": { + "name": "SK_C_28_naval_gun", + "coalition": "", + "era": "", + "label": "Gun 15cm SK C/28 Naval in Bunker", + "shortLabel": "Gun 15cm SK C/28 Naval in Bunker", + "type": "Fortification", + "enabled": true, + "liveries": {} + }, + "fire_control": { + "name": "fire_control", + "coalition": "", + "era": "", + "label": "Bunker with Fire Control Center", + "shortLabel": "Bunker with Fire Control Center", + "type": "Fortification", + "enabled": true, + "liveries": {} + }, + "Stug_III": { + "name": "Stug_III", + "coalition": "", + "era": "", + "label": "SPG StuG III G AG", + "shortLabel": "SPG StuG III G AG", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Elefant_SdKfz_184": { + "name": "Elefant_SdKfz_184", + "coalition": "", + "era": "", + "label": "SPG Elefant TD", + "shortLabel": "SPG Elefant TD", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "flak41": { + "name": "flak41", + "coalition": "", + "era": "", + "label": "AAA 8,8cm Flak 41", + "shortLabel": "AAA 8,8cm Flak 41", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "v1_launcher": { + "name": "v1_launcher", + "coalition": "", + "era": "", + "label": "V-1 Launch Ramp", + "shortLabel": "V-1 Launch Ramp", + "type": "MissilesSS", + "enabled": true, + "liveries": {} + }, + "FuMG-401": { + "name": "FuMG-401", + "coalition": "", + "era": "", + "label": "EWR FuMG-401 Freya LZ", + "shortLabel": "EWR FuMG-401 Freya LZ", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "FuSe-65": { + "name": "FuSe-65", + "coalition": "", + "era": "", + "label": "EWR FuSe-65 Würzburg-Riese", + "shortLabel": "EWR FuSe-65 Würzburg-Riese", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "Pak40": { + "name": "Pak40", + "coalition": "", + "era": "", + "label": "FH Pak 40 75mm", + "shortLabel": "FH Pak 40 75mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "LeFH_18-40-105": { + "name": "LeFH_18-40-105", + "coalition": "", + "era": "", + "label": "FH LeFH-18 105mm", + "shortLabel": "FH LeFH-18 105mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "Cromwell_IV": { + "name": "Cromwell_IV", + "coalition": "", + "era": "", + "label": "Tk Cromwell IV", + "shortLabel": "Tk Cromwell IV", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "M4A4_Sherman_FF": { + "name": "M4A4_Sherman_FF", + "coalition": "", + "era": "", + "label": "Tk M4A4 Sherman Firefly", + "shortLabel": "Tk M4A4 Sherman Firefly", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "soldier_wwii_br_01": { + "name": "soldier_wwii_br_01", + "coalition": "", + "era": "", + "label": "Infantry SMLE No.4 Mk-1", + "shortLabel": "Infantry SMLE No.4 Mk-1", + "type": "Infantry", + "enabled": true, + "liveries": {} + }, + "Centaur_IV": { + "name": "Centaur_IV", + "coalition": "", + "era": "", + "label": "Tk Centaur IV CS", + "shortLabel": "Tk Centaur IV CS", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Churchill_VII": { + "name": "Churchill_VII", + "coalition": "", + "era": "", + "label": "Tk Churchill VII", + "shortLabel": "Tk Churchill VII", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Daimler_AC": { + "name": "Daimler_AC", + "coalition": "", + "era": "", + "label": "Car Daimler Armored", + "shortLabel": "Car Daimler Armored", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "Tetrarch": { + "name": "Tetrarch", + "coalition": "", + "era": "", + "label": "Tk Tetrach", + "shortLabel": "Tk Tetrach", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "QF_37_AA": { + "name": "QF_37_AA", + "coalition": "", + "era": "", + "label": "AAA QF 3.7\"", + "shortLabel": "AAA QF 3.7\"", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "Allies_Director": { + "name": "Allies_Director", + "coalition": "", + "era": "", + "label": "Allies Rangefinder (DRT)", + "shortLabel": "Allies Rangefinder (DRT)", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "CCKW_353": { + "name": "CCKW_353", + "coalition": "", + "era": "", + "label": "Truck GMC \"Jimmy\" 6x6", + "shortLabel": "Truck GMC \"Jimmy\" 6x6", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "Willys_MB": { + "name": "Willys_MB", + "coalition": "", + "era": "", + "label": "Car Willys Jeep", + "shortLabel": "Car Willys Jeep", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "M12_GMC": { + "name": "M12_GMC", + "coalition": "", + "era": "", + "label": "SPH M12 GMC 155mm", + "shortLabel": "SPH M12 GMC 155mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "M30_CC": { + "name": "M30_CC", + "coalition": "", + "era": "", + "label": "Ammo M30 Cargo Carrier", + "shortLabel": "Ammo M30 Cargo Carrier", + "type": "Unarmed", + "enabled": true, + "liveries": {} + }, + "soldier_wwii_us": { + "name": "soldier_wwii_us", + "coalition": "", + "era": "", + "label": "Infantry M1 Garand", + "shortLabel": "Infantry M1 Garand", + "type": "Infantry", + "enabled": true, + "liveries": {} + }, + "M10_GMC": { + "name": "M10_GMC", + "coalition": "", + "era": "", + "label": "SPG M10 GMC TD", + "shortLabel": "SPG M10 GMC TD", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "M8_Greyhound": { + "name": "M8_Greyhound", + "coalition": "", + "era": "", + "label": "Scout M8 Greyhound AC", + "shortLabel": "Scout M8 Greyhound AC", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "M2A1-105": { + "name": "M2A1-105", + "coalition": "", + "era": "", + "label": "FH M2A1 105mm", + "shortLabel": "FH M2A1 105mm", + "type": "Artillery", + "enabled": true, + "liveries": {} + }, + "M4_Tractor": { + "name": "M4_Tractor", + "coalition": "", + "era": "", + "label": "Tractor M4 High Speed", + "shortLabel": "Tractor M4 High Speed", + "type": "Armor", + "enabled": true, + "liveries": {} + }, + "M45_Quadmount": { + "name": "M45_Quadmount", + "coalition": "", + "era": "", + "label": "AAA M45 Quadmount HB 12.7mm", + "shortLabel": "AAA M45 Quadmount HB 12.7mm", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "M1_37mm": { + "name": "M1_37mm", + "coalition": "", + "era": "", + "label": "AAA M1 37mm", + "shortLabel": "AAA M1 37mm", + "type": "AirDefence", + "enabled": true, + "liveries": {} + }, + "DR_50Ton_Flat_Wagon": { + "name": "DR_50Ton_Flat_Wagon", + "coalition": "", + "era": "", + "label": "DR 50-ton flat wagon", + "shortLabel": "DR 50-ton flat wagon", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "DRG_Class_86": { + "name": "DRG_Class_86", + "coalition": "", + "era": "", + "label": "Loco DRG Class 86", + "shortLabel": "Loco DRG Class 86", + "type": "Locomotive", + "enabled": true, + "liveries": {} + }, + "German_covered_wagon_G10": { + "name": "German_covered_wagon_G10", + "coalition": "", + "era": "", + "label": "Wagon G10 (Germany)", + "shortLabel": "Wagon G10 (Germany)", + "type": "Carriage", + "enabled": true, + "liveries": {} + }, + "German_tank_wagon": { + "name": "German_tank_wagon", + "coalition": "", + "era": "", + "label": "Tank Car (Germany)", + "shortLabel": "Tank Car (Germany)", + "type": "Carriage", + "enabled": true, + "liveries": {} } } \ No newline at end of file diff --git a/client/public/databases/units/helicopterdatabase.json b/client/public/databases/units/helicopterdatabase.json index e4f87c17..962f5e98 100644 --- a/client/public/databases/units/helicopterdatabase.json +++ b/client/public/databases/units/helicopterdatabase.json @@ -3485,7 +3485,24 @@ "shortLabel": "S60", "loadouts": [ { - "items": [], + "items": [ + { + "name": "", + "quantity": 1 + }, + { + "name": "", + "quantity": 1 + }, + { + "name": "", + "quantity": 1 + }, + { + "name": "", + "quantity": 1 + } + ], "enabled": true, "code": "", "name": "Empty loadout", @@ -3498,6 +3515,30 @@ { "name": "AGM-119B Penguin ASM", "quantity": 1 + }, + { + "name": "", + "quantity": 1 + }, + { + "name": "", + "quantity": 1 + }, + { + "name": "", + "quantity": 1 + }, + { + "name": "", + "quantity": 1 + }, + { + "name": "", + "quantity": 1 + }, + { + "name": "", + "quantity": 1 } ], "enabled": true, @@ -3506,6 +3547,20 @@ "roles": [ "Antiship Strike" ] + }, + { + "name": "asd", + "code": "", + "fuel": 1, + "items": [], + "roles": [] + }, + { + "name": "asd", + "code": "", + "fuel": 1, + "items": [], + "roles": [] } ], "filename": "uh-60.png", diff --git a/client/public/databases/units/navyunitdatabase.json b/client/public/databases/units/navyunitdatabase.json index 4ffc29bd..7c6866c4 100644 --- a/client/public/databases/units/navyunitdatabase.json +++ b/client/public/databases/units/navyunitdatabase.json @@ -10,17 +10,6 @@ "filename": "", "enabled": true }, - "Boat Armed Hi-Speed": { - "name": "Boat Armed Hi-Speed", - "coalition": "", - "type": "Fast Attack Craft", - "era": "Mid Cold War", - "label": "Boat Armed Hi-Speed", - "shortLabel": "Boat Armed Hi-Speed", - "range": "", - "filename": "", - "enabled": true - }, "CVN_71": { "name": "CVN_71", "coalition": "blue", @@ -98,17 +87,6 @@ "filename": "", "enabled": true }, - "CV_59": { - "name": "CV_59", - "coalition": "blue", - "type": "Aircraft Carrier", - "era": "Early Cold War", - "label": "CV-59 Forrestal", - "shortLabel": "CV-59", - "range": "Short", - "filename": "", - "enabled": true - }, "CastleClass_01": { "name": "CastleClass_01", "coalition": "blue", @@ -966,55 +944,6 @@ } } }, - "albatros": { - "name": "albatros", - "coalition": "red", - "type": "Aircraft Carrier", - "era": "Early Cold War", - "label": "Albatros (Grisha-5)", - "shortLabel": "Albatros", - "range": "", - "filename": "", - "enabled": true, - "liveries": { - "141": { - "name": "141", - "countries": "All" - }, - "142": { - "name": "142", - "countries": "All" - }, - "143": { - "name": "143", - "countries": "All" - }, - "144": { - "name": "144", - "countries": "All" - }, - "145": { - "name": "145", - "countries": "All" - }, - "146": { - "name": "146", - "countries": "All" - }, - "147": { - "name": "147", - "countries": "All" - }, - "148": { - "name": "148", - "countries": "All" - }, - "149": { - "name": "149", - "countries": "All" - } - } - }, "ara_vdm": { "name": "ara_vdm", "coalition": "", @@ -1026,53 +955,6 @@ "filename": "", "enabled": true }, - "barge-1": { - "name": "barge-1", - "coalition": "red", - "type": "Cargoship", - "era": "Late Cold War", - "label": "Dry cargo ship Ivanov", - "shortLabel": "Dry cargo ship Ivanov", - "range": "", - "filename": "", - "enabled": true - }, - "barge-2": { - "name": "barge-2", - "coalition": "red", - "type": "Cargoship", - "era": "Late Cold War", - "label": "Dry cargo ship Yakushev", - "shortLabel": "Dry cargo ship Yakushev", - "range": "", - "filename": "", - "enabled": true - }, - "elnya": { - "name": "elnya", - "coalition": "red", - "type": "Tanker", - "era": "Late Cold War", - "label": "Elnya tanker", - "shortLabel": "Elnya tanker", - "range": "", - "filename": "", - "enabled": true, - "liveries": { - "952": { - "name": "952", - "countries": "All" - }, - "953": { - "name": "953", - "countries": "All" - }, - "954": { - "name": "954", - "countries": "All" - } - } - }, "hms_invincible": { "name": "hms_invincible", "coalition": "blue", @@ -1084,39 +966,6 @@ "filename": "", "enabled": true }, - "kilo": { - "name": "kilo", - "coalition": "red", - "type": "Submarine", - "era": "Late Cold War", - "label": "Project 636 Varshavyanka Basic", - "shortLabel": "Varshavyanka Basic", - "range": "Medium", - "filename": "", - "enabled": true - }, - "kilo_636": { - "name": "kilo_636", - "coalition": "red", - "type": "Submarine", - "era": "Late Cold War", - "label": "Project 636 Varshavyanka Improved", - "shortLabel": "Varshavyanka Improved", - "range": "Medium", - "filename": "", - "enabled": true - }, - "kuznecow": { - "name": "kuznecow", - "coalition": "red", - "type": "Aircraft Carrier", - "era": "Late Cold War", - "label": "Admiral Kuznetsov", - "shortLabel": "Admiral Kuznetsov", - "range": "Medium", - "filename": "", - "enabled": true - }, "leander-gun-achilles": { "name": "leander-gun-achilles", "coalition": "blue", @@ -1172,72 +1021,38 @@ "filename": "", "enabled": true }, - "molniya": { - "name": "molniya", + "santafe": { + "name": "santafe", "coalition": "", - "type": "Fast Attack Craft", - "era": "Late Cold War", - "label": "Molniya (Tarantul-3)", - "shortLabel": "Molniya", - "range": "Short", - "filename": "", - "enabled": true, - "liveries": { - "952": { - "name": "952", - "countries": "All" - }, - "953": { - "name": "953", - "countries": "All" - }, - "954": { - "name": "954", - "countries": "All" - } - } - }, - "moscow": { - "name": "moscow", - "coalition": "red", - "type": "Cruiser", - "era": "Late Cold War", - "label": "Moscow", - "shortLabel": "Moscow", - "range": "Medium", - "filename": "", - "enabled": true, - "liveries": { - "default": { - "name": "default", - "countries": "All" - }, - "cow1": { - "name": "cow1", - "countries": "All" - }, - "cow3": { - "name": "cow3", - "countries": "All" - }, - "cow2": { - "name": "cow2", - "countries": "All" - } - } - }, - "neustrash": { - "name": "neustrash", - "coalition": "red", - "type": "Frigate", - "era": "Late Cold War", - "label": "Neustrashimy", - "shortLabel": "Neustrashimy", - "range": "Short", + "type": "Submarine", + "era": "Early Cold War", + "label": "ARA Santa Fe S-21", + "shortLabel": "ARA Santa", + "range": "", "filename": "", "enabled": true }, - "perry": { + "speedboat": { + "name": "speedboat", + "coalition": "", + "era": "", + "label": "Boat Armed Hi-speed", + "shortLabel": "Boat Armed Hi-speed", + "type": "Speedboat", + "enabled": true, + "liveries": {} + }, + "VINSON": { + "name": "VINSON", + "coalition": "", + "era": "", + "label": "CVN-70 Carl Vinson", + "shortLabel": "CVN-70 Carl Vinson", + "type": "Aircraft Carrier", + "enabled": true, + "liveries": {} + }, + "PERRY": { "name": "perry", "coalition": "blue", "type": "Frigate", @@ -1290,18 +1105,142 @@ } } }, - "piotr_velikiy": { - "name": "piotr_velikiy", + "ALBATROS": { + "name": "albatros", "coalition": "red", - "type": "Cruiser", + "type": "Frigade", + "era": "Early Cold War", + "label": "Albatros (Grisha-5)", + "shortLabel": "Albatros", + "range": "", + "filename": "", + "enabled": true, + "liveries": { + "141": { + "name": "141", + "countries": "All" + }, + "142": { + "name": "142", + "countries": "All" + }, + "143": { + "name": "143", + "countries": "All" + }, + "144": { + "name": "144", + "countries": "All" + }, + "145": { + "name": "145", + "countries": "All" + }, + "146": { + "name": "146", + "countries": "All" + }, + "147": { + "name": "147", + "countries": "All" + }, + "148": { + "name": "148", + "countries": "All" + }, + "149": { + "name": "149", + "countries": "All" + } + } + }, + "KUZNECOW": { + "name": "kuznecow", + "coalition": "red", + "type": "Aircraft Carrier", "era": "Late Cold War", - "label": "Pyotr Velikiy", - "shortLabel": "Pyotr Velikiy", + "label": "Admiral Kuznetsov", + "shortLabel": "Admiral Kuznetsov", "range": "Medium", "filename": "", "enabled": true }, - "rezky": { + "MOLNIYA": { + "name": "molniya", + "coalition": "", + "type": "Corvette", + "era": "Late Cold War", + "label": "Molniya (Tarantul-3)", + "shortLabel": "Molniya", + "range": "Short", + "filename": "", + "enabled": true, + "liveries": { + "952": { + "name": "952", + "countries": "All" + }, + "953": { + "name": "953", + "countries": "All" + }, + "954": { + "name": "954", + "countries": "All" + } + } + }, + "MOSCOW": { + "name": "moscow", + "coalition": "red", + "type": "Cruiser", + "era": "Late Cold War", + "label": "Moscow", + "shortLabel": "Moscow", + "range": "Medium", + "filename": "", + "enabled": true, + "liveries": { + "default": { + "name": "default", + "countries": "All" + }, + "cow1": { + "name": "cow1", + "countries": "All" + }, + "cow3": { + "name": "cow3", + "countries": "All" + }, + "cow2": { + "name": "cow2", + "countries": "All" + } + } + }, + "NEUSTRASH": { + "name": "neustrash", + "coalition": "red", + "type": "Frigate", + "era": "Late Cold War", + "label": "Neustrashimy", + "shortLabel": "Neustrashimy", + "range": "Short", + "filename": "", + "enabled": true + }, + "PIOTR": { + "name": "PIOTR", + "coalition": "", + "era": "", + "label": "Battlecruiser 1144.2 Pyotr Velikiy", + "shortLabel": "Battlecruiser 1144.2 Pyotr Velikiy", + "type": "Cruiser", + "enabled": true, + "liveries": {} + }, + "REZKY": { "name": "Rezky (Krivak-2)", "coalition": "red", "type": "Frigate", @@ -1312,18 +1251,52 @@ "filename": "", "enabled": true }, - "santafe": { - "name": "santafe", - "coalition": "", - "type": "Submarine", - "era": "Early Cold War", - "label": "ARA Santa Fe S-21", - "shortLabel": "ARA Santa", + "ELNYA": { + "name": "elnya", + "coalition": "red", + "type": "Tanker", + "era": "Late Cold War", + "label": "Elnya tanker", + "shortLabel": "Elnya tanker", "range": "", "filename": "", - "enabled": true + "enabled": true, + "liveries": { + "952": { + "name": "952", + "countries": "All" + }, + "953": { + "name": "953", + "countries": "All" + }, + "954": { + "name": "954", + "countries": "All" + } + } }, - "zwezdny": { + "Dry-cargo ship-2": { + "name": "Dry-cargo ship-2", + "coalition": "", + "era": "", + "label": "Cargo Ivanov", + "shortLabel": "Cargo Ivanov", + "type": "Cargo", + "enabled": true, + "liveries": {} + }, + "Dry-cargo ship-1": { + "name": "Dry-cargo ship-1", + "coalition": "", + "era": "", + "label": "Bulker Yakushev", + "shortLabel": "Bulker Yakushev", + "type": "Cargo", + "enabled": true, + "liveries": {} + }, + "ZWEZDNY": { "name": "zwezdny", "coalition": "", "type": "Civilian Boat", @@ -1333,5 +1306,96 @@ "range": "", "filename": "", "enabled": true + }, + "KILO": { + "name": "kilo", + "coalition": "red", + "type": "Submarine", + "era": "Late Cold War", + "label": "Project 636 Varshavyanka Basic", + "shortLabel": "Varshavyanka Basic", + "range": "Medium", + "filename": "", + "enabled": true + }, + "IMPROVED_KILO": { + "name": "IMPROVED_KILO", + "coalition": "", + "era": "", + "label": "SSK 636 Improved Kilo", + "shortLabel": "SSK 636 Improved Kilo", + "type": "Submarine", + "enabled": true, + "liveries": {} + }, + "SOM": { + "name": "SOM", + "coalition": "", + "era": "", + "label": "SSK 641B Tango", + "shortLabel": "SSK 641B Tango", + "type": "SOM", + "enabled": true, + "liveries": {} + }, + "Forrestal": { + "name": "Forrestal", + "coalition": "", + "era": "", + "label": "CV-59 Forrestal", + "shortLabel": "CV-59 Forrestal", + "type": "Forrestal", + "enabled": true, + "liveries": {} + }, + "LST_Mk2": { + "name": "LST_Mk2", + "coalition": "", + "era": "", + "label": "LST Mk.II", + "shortLabel": "LST Mk.II", + "type": "LST_Mk2", + "enabled": true, + "liveries": {} + }, + "USS_Samuel_Chase": { + "name": "USS_Samuel_Chase", + "coalition": "", + "era": "", + "label": "LS Samuel Chase", + "shortLabel": "LS Samuel Chase", + "type": "USS_Samuel_Chase", + "enabled": true, + "liveries": {} + }, + "Higgins_boat": { + "name": "Higgins_boat", + "coalition": "", + "era": "", + "label": "Boat LCVP Higgins", + "shortLabel": "Boat LCVP Higgins", + "type": "Higgins_boat", + "enabled": true, + "liveries": {} + }, + "Uboat_VIIC": { + "name": "Uboat_VIIC", + "coalition": "", + "era": "", + "label": "U-boat VIIC U-flak", + "shortLabel": "U-boat VIIC U-flak", + "type": "Uboat_VIIC", + "enabled": true, + "liveries": {} + }, + "Schnellboot_type_S130": { + "name": "Schnellboot_type_S130", + "coalition": "", + "era": "", + "label": "Boat Schnellboot type S130", + "shortLabel": "Boat Schnellboot type S130", + "type": "Schnellboot_type_S130", + "enabled": true, + "liveries": {} } } \ No newline at end of file diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index fe9e5837..e1feb19a 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -326,9 +326,9 @@ #unit-contextmenu div:before { display: inline-block; filter: invert(100%); - height: 16px; + height: 20px; margin-right: 15px; - width: 16px; + width: 20px; } .ol-select>.ol-select-options>div button.country-dropdown-element { @@ -377,6 +377,22 @@ content: url("/resources/theme/images/icons/follow.svg"); } +#scenic-aaa::before { + content: url("/resources/theme/images/icons/scenic.svg"); +} + +#miss-aaa::before { + content: url("/resources/theme/images/icons/miss.svg"); +} + +#group-ground::before { + content: url("/resources/theme/images/icons/group-ground.svg"); +} + +#group-navy::before { + content: url("/resources/theme/images/icons/group-navy.svg"); +} + #trail::before { content: url("/resources/theme/images/icons/trail.svg"); } diff --git a/client/public/stylesheets/panels/unitcontrol.css b/client/public/stylesheets/panels/unitcontrol.css index 0b74a2ff..819e2a68 100644 --- a/client/public/stylesheets/panels/unitcontrol.css +++ b/client/public/stylesheets/panels/unitcontrol.css @@ -203,6 +203,22 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { content: "NO"; } +#operate-as-switch[data-value="true"] .ol-switch-fill { + background-color: var(--accent-light-blue); +} + +#operate-as-switch[data-value="false"] .ol-switch-fill { + background-color: var(--primary-red); +} + +#operate-as-switch[data-value="true"]>.ol-switch-fill::before { + content: "BLUE" !important; +} + +#operate-as-switch[data-value="false"]>.ol-switch-fill::before { + content: "RED" !important; +} + #advanced-settings-div { column-gap: 5px; display: flex; @@ -225,6 +241,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { #unit-control-panel:not([data-show-emissions-countermeasures]) #emissions-countermeasures, #unit-control-panel:not([data-show-on-off]) #ai-on-off, #unit-control-panel:not([data-show-follow-roads]) #follow-roads, +#unit-control-panel:not([data-show-operate-as]) #operate-as, #unit-control-panel:not([data-show-advanced-settings-button]) #advanced-settings-button, #advanced-settings-dialog:not([data-show-settings]) #general-settings, #advanced-settings-dialog:not([data-show-tasking]) #tasking, diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index 70b77e82..638c90d1 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -678,6 +678,13 @@ nav.ol-panel> :last-child { stroke: white; } +#rapid-controls button:before { + display: inline-block; + filter: invert(100%); + height: 20px; + width: 20px; +} + /****************************************************************************************/ #splash-screen { border-radius: var(--border-radius-md); diff --git a/client/public/themes/olympus/images/icons/burst-solid.svg b/client/public/themes/olympus/images/icons/burst-solid.svg new file mode 100644 index 00000000..81719666 --- /dev/null +++ b/client/public/themes/olympus/images/icons/burst-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/icons/group-ground.svg b/client/public/themes/olympus/images/icons/group-ground.svg new file mode 100644 index 00000000..d47d8bc6 --- /dev/null +++ b/client/public/themes/olympus/images/icons/group-ground.svg @@ -0,0 +1,76 @@ + + diff --git a/client/public/themes/olympus/images/icons/group-navy.svg b/client/public/themes/olympus/images/icons/group-navy.svg new file mode 100644 index 00000000..c2457a0f --- /dev/null +++ b/client/public/themes/olympus/images/icons/group-navy.svg @@ -0,0 +1,64 @@ + + diff --git a/client/public/themes/olympus/images/icons/jet-fighter-up-solid.svg b/client/public/themes/olympus/images/icons/jet-fighter-up-solid.svg new file mode 100644 index 00000000..9df104e9 --- /dev/null +++ b/client/public/themes/olympus/images/icons/jet-fighter-up-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/icons/miss-blue.svg b/client/public/themes/olympus/images/icons/miss-blue.svg new file mode 100644 index 00000000..4f6ee87f --- /dev/null +++ b/client/public/themes/olympus/images/icons/miss-blue.svg @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/client/public/themes/olympus/images/icons/miss-red.svg b/client/public/themes/olympus/images/icons/miss-red.svg new file mode 100644 index 00000000..8bb47e46 --- /dev/null +++ b/client/public/themes/olympus/images/icons/miss-red.svg @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/client/public/themes/olympus/images/icons/miss.svg b/client/public/themes/olympus/images/icons/miss.svg new file mode 100644 index 00000000..0a88637e --- /dev/null +++ b/client/public/themes/olympus/images/icons/miss.svg @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/client/public/themes/olympus/images/icons/scenic-blue.svg b/client/public/themes/olympus/images/icons/scenic-blue.svg new file mode 100644 index 00000000..c19ddb60 --- /dev/null +++ b/client/public/themes/olympus/images/icons/scenic-blue.svg @@ -0,0 +1,49 @@ + + diff --git a/client/public/themes/olympus/images/icons/scenic-red.svg b/client/public/themes/olympus/images/icons/scenic-red.svg new file mode 100644 index 00000000..0fe03fd9 --- /dev/null +++ b/client/public/themes/olympus/images/icons/scenic-red.svg @@ -0,0 +1,49 @@ + + diff --git a/client/public/themes/olympus/images/icons/scenic.svg b/client/public/themes/olympus/images/icons/scenic.svg new file mode 100644 index 00000000..6321f72a --- /dev/null +++ b/client/public/themes/olympus/images/icons/scenic.svg @@ -0,0 +1,49 @@ + + diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index cd0b3225..e952cd0b 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -197,5 +197,6 @@ export enum DataIndexes { contacts, activePath, isLeader, + operateAs, endOfData = 255 }; \ No newline at end of file diff --git a/client/src/interfaces.ts b/client/src/interfaces.ts index 9c33664f..949c49fc 100644 --- a/client/src/interfaces.ts +++ b/client/src/interfaces.ts @@ -176,6 +176,7 @@ export interface UnitData { contacts: Contact[]; activePath: LatLng[]; isLeader: boolean; + operateAs: string; } export interface LoadoutItemBlueprint { @@ -199,13 +200,21 @@ export interface UnitBlueprint { label: string; shortLabel: string; type?: string; - range?: string; + rangeType?: string; loadouts?: LoadoutBlueprint[]; filename?: string; liveries?: { [key: string]: { name: string, countries: string[] } }; cost?: number; barrelHeight?: number; muzzleVelocity?: number; + aimTime?: number; + shotsToFire?: number; + description?: string; + abilities?: string; + acquisitionRange?: number; + engagementRange?: number; + refuelsFrom?: string; + refuelingType?: string; } export interface UnitSpawnOptions { diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index 58f799ba..655c5374 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -259,7 +259,7 @@ export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: {type?: if (options.ranges) { unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { //@ts-ignore - return unitBlueprint.range? options.ranges.includes(unitBlueprint.range): true; + return unitBlueprint.rangeType? options.ranges.includes(unitBlueprint.rangeType): true; }); } @@ -351,6 +351,16 @@ export function enumToCoalition(coalitionID: number) { return ""; } +export function coalitionToEnum(coalition: string) { + switch (coalition){ + case "neutral": return 0; + case "red": return 1; + case "blue": return 2; + } + return 0; +} + + export function convertDateAndTimeToDate(dateAndTime: DateAndTime) { const date = dateAndTime.date; const time = dateAndTime.time; diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 95475529..1ca60845 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -17,6 +17,7 @@ export class UnitControlPanel extends Panel { #speedTypeSwitch: Switch; #onOffSwitch: Switch; #followRoadsSwitch: Switch; + #operateAsSwitch: Switch; #TACANXYDropdown: Dropdown; #radioDecimalsDropdown: Dropdown; #radioCallsignDropdown: Dropdown; @@ -67,6 +68,11 @@ export class UnitControlPanel extends Panel { getApp().getUnitsManager().selectedUnitsSetFollowRoads(value); }); + /* Operate as */ + this.#operateAsSwitch = new Switch("operate-as-switch", (value: boolean) => { + getApp().getUnitsManager().selectedUnitsSetOperateAs(value); + }); + /* Advanced settings dialog */ this.#advancedSettingsDialog = document.querySelector("#advanced-settings-dialog"); @@ -80,21 +86,28 @@ export class UnitControlPanel extends Panel { /* Events and timer */ window.setInterval(() => {this.update();}, 25); - document.addEventListener("unitsSelection", (e: CustomEvent) => { this.show(); this.addButtons();}); - document.addEventListener("clearSelection", () => { this.hide() }); + document.addEventListener("unitsSelection", (e: CustomEvent) => { + this.show(); + this.addButtons(); + this.#updateRapidControls(); + }); + document.addEventListener("clearSelection", () => { + this.hide(); + this.#updateRapidControls(); + }); document.addEventListener("applyAdvancedSettings", () => {this.#applyAdvancedSettings();}) document.addEventListener("showAdvancedSettings", () => { this.#updateAdvancedSettingsDialog(getApp().getUnitsManager().getSelectedUnits()); this.#advancedSettingsDialog.classList.remove("hide"); }); - - this.hide(); - - // This is for when a ctrl-click happens on the map for deselection and we need to remove the selected unit from the panel + /* This is for when a ctrl-click happens on the map for deselection and we need to remove the selected unit from the panel */ document.addEventListener( "unitDeselection", ( ev:CustomEventInit ) => { this.getElement().querySelector( `button[data-unit-id="${ev.detail.ID}"]` )?.remove(); + this.#updateRapidControls(); }); + + this.hide(); } show() { @@ -157,6 +170,7 @@ export class UnitControlPanel extends Panel { element.toggleAttribute("data-show-emissions-countermeasures", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit"))); element.toggleAttribute("data-show-on-off", (this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")) && !(this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter"))); element.toggleAttribute("data-show-follow-roads", (this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit"))); + element.toggleAttribute("data-show-operate-as", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === "neutral"); element.toggleAttribute("data-show-advanced-settings-button", this.#units.length == 1); if (this.#selectedUnitsTypes.length == 1) { @@ -167,6 +181,7 @@ export class UnitControlPanel extends Panel { var desiredSpeedType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()}); var onOff = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()}); var followRoads = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()}); + var operateAs = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOperateAs()}); this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "ASL": undefined, false); this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "CAS": undefined, false); @@ -204,6 +219,60 @@ export class UnitControlPanel extends Panel { this.#onOffSwitch.setValue(onOff, false); this.#followRoadsSwitch.setValue(followRoads, false); + this.#operateAsSwitch.setValue(operateAs? operateAs === "blue": undefined, false); + } + } + } + + #updateRapidControls() { + var options: { [key: string]: { text: string, tooltip: string, type: string } } | null = null; + + var selectedUnits = getApp().getUnitsManager().getSelectedUnits(); + + var showAltitudeChange = selectedUnits.some((unit: Unit) => {return ["Aircraft", "Helicopter"].includes(unit.getCategory());}); + this.getElement().querySelector("#climb")?.classList.toggle("hide", !showAltitudeChange); + this.getElement().querySelector("#descend")?.classList.toggle("hide", !showAltitudeChange); + + /* Keep only the common "and" options, unless a single unit is selected */ + selectedUnits.forEach((unit: Unit) => { + var unitOptions = unit.getActions(); + if (options === null) { + options = unitOptions; + } else { + /* Delete all the "or" type options */ + for (let optionKey in options) { + if (options[optionKey].type == "or") { + delete options[optionKey]; + } + } + + /* Options of "and" type get shown if ALL units have it */ + for (let optionKey in options) { + if (!(optionKey in unitOptions)) { + delete options[optionKey]; + } + } + } + }); + + options = options ?? {}; + + const rapidControlsContainer = this.getElement().querySelector("#rapid-controls") as HTMLElement; + const unitActionButtons = rapidControlsContainer.querySelectorAll(".unit-action-button"); + for (let button of unitActionButtons) { + rapidControlsContainer.removeChild(button); + } + + for (let option in options) { + let button = document.createElement("button"); + button.title = options[option].tooltip; + button.classList.add("ol-button", "unit-action-button"); + button.id = option; + rapidControlsContainer.appendChild(button); + button.onclick = () => { + /* Since only common actions are shown in the rapid controls, we execute it only on the first unit */ + if (selectedUnits.length > 0) + selectedUnits[0].executeAction(null, option); } } } diff --git a/client/src/server/servermanager.ts b/client/src/server/servermanager.ts index 12f20206..1d36200f 100644 --- a/client/src/server/servermanager.ts +++ b/client/src/server/servermanager.ts @@ -295,6 +295,13 @@ export class ServerManager { this.PUT(data, callback); } + setOperateAs(ID: number, operateAs: number, callback: CallableFunction = () => {}) { + var command = { "ID": ID, "operateAs": operateAs } + var data = { "setOperateAs": command } + this.PUT(data, callback); + } + + refuel(ID: number, callback: CallableFunction = () => {}) { var command = { "ID": ID }; var data = { "refuel": command } @@ -331,6 +338,18 @@ export class ServerManager { this.PUT(data, callback); } + scenicAAA(ID: number, coalition: string, callback: CallableFunction = () => {}) { + var command = { "ID": ID, "coalition": coalition } + var data = { "scenicAAA": command } + this.PUT(data, callback); + } + + missOnPurpose(ID: number, coalition: string, callback: CallableFunction = () => {}) { + var command = { "ID": ID, "coalition": coalition } + var data = { "missOnPurpose": command } + this.PUT(data, callback); + } + setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) { var command = { "ID": ID, diff --git a/client/src/unit/databases/unitdatabase.ts b/client/src/unit/databases/unitdatabase.ts index ee3f4d0f..9491ac5e 100644 --- a/client/src/unit/databases/unitdatabase.ts +++ b/client/src/unit/databases/unitdatabase.ts @@ -115,7 +115,7 @@ export class UnitDatabase { var filteredBlueprints = this.getBlueprints(); var ranges: string[] = []; for (let unit in filteredBlueprints) { - var range = filteredBlueprints[unit].range; + var range = filteredBlueprints[unit].rangeType; if (range && range !== "" && !ranges.includes(range)) ranges.push(range); } @@ -127,7 +127,7 @@ export class UnitDatabase { var filteredBlueprints = this.getBlueprints(); var unitswithrange = []; for (let unit in filteredBlueprints) { - if (filteredBlueprints[unit].range === range) { + if (filteredBlueprints[unit].rangeType === range) { unitswithrange.push(filteredBlueprints[unit]); } } diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 6c081a5d..6cfb1897 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -1,6 +1,6 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet'; import { getApp } from '..'; -import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM, getGroundElevation } from '../other/utils'; +import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM, getGroundElevation, coalitionToEnum } from '../other/utils'; import { CustomMarker } from '../map/markers/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './databases/unitdatabase'; @@ -77,6 +77,7 @@ export class Unit extends CustomMarker { #contacts: Contact[] = []; #activePath: LatLng[] = []; #isLeader: boolean = false; + #operateAs: string = "blue"; #selectable: boolean; #selected: boolean = false; @@ -130,6 +131,7 @@ export class Unit extends CustomMarker { getContacts() { return this.#contacts }; getActivePath() { return this.#activePath }; getIsLeader() { return this.#isLeader }; + getOperateAs() { return this.#operateAs }; static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; @@ -232,6 +234,7 @@ export class Unit extends CustomMarker { case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", { detail: this })); break; case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break; case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); break; + case DataIndexes.operateAs: this.#operateAs = enumToCoalition(dataExtractor.extractUInt8()); break; } } @@ -291,7 +294,8 @@ export class Unit extends CustomMarker { ammo: this.#ammo, contacts: this.#contacts, activePath: this.#activePath, - isLeader: this.#isLeader + isLeader: this.#isLeader, + operateAs: this.#operateAs } } @@ -588,15 +592,7 @@ export class Unit extends CustomMarker { } isInViewport() { - - const mapBounds = getApp().getMap().getBounds(); - const unitPos = this.getPosition(); - - return (unitPos.lng > mapBounds.getWest() - && unitPos.lng < mapBounds.getEast() - && unitPos.lat > mapBounds.getSouth() - && unitPos.lat < mapBounds.getNorth()); - + return getApp().getMap().getBounds().contains(this.getPosition()); } /********************** Unit commands *************************/ @@ -693,6 +689,11 @@ export class Unit extends CustomMarker { getApp().getServerManager().setFollowRoads(this.ID, followRoads); } + setOperateAs(operateAs: string) { + if (!this.#human) + getApp().getServerManager().setOperateAs(this.ID, coalitionToEnum(operateAs)); + } + delete(explosion: boolean, immediate: boolean) { getApp().getServerManager().deleteUnit(this.ID, explosion, immediate); } @@ -739,6 +740,49 @@ export class Unit extends CustomMarker { }); } + scenicAAA() { + var coalition = "neutral"; + if (this.getCoalition() === "red") + coalition = "blue"; + else if (this.getCoalition() == "blue") + coalition = "red"; + //TODO + getApp().getServerManager().scenicAAA(this.ID, coalition); + } + + missOnPurpose() { + var coalition = "neutral"; + if (this.getCoalition() === "red") + coalition = "blue"; + else if (this.getCoalition() == "blue") + coalition = "red"; + //TODO + getApp().getServerManager().missOnPurpose(this.ID, coalition); + } + + /***********************************************/ + 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); + if (action === "attack") + getApp().getUnitsManager().selectedUnitsAttackUnit(this.ID); + else if (action === "refuel") + getApp().getUnitsManager().selectedUnitsRefuel(); + else if (action === "group-ground" || action === "group-navy") + getApp().getUnitsManager().selectedUnitsCreateGroup(); + else if (action === "scenic-aaa") + getApp().getUnitsManager().selectedUnitsScenicAAA(); + else if (action === "miss-aaa") + getApp().getUnitsManager().selectedUnitsMissOnPurpose(); + else if (action === "follow") + this.#showFollowOptions(e); + } + /***********************************************/ onAdd(map: Map): this { super.onAdd(map); @@ -752,18 +796,18 @@ export class Unit extends CustomMarker { if (this.#waitingForDoubleClick) { return; } - + // We'll wait for a doubleclick this.#waitingForDoubleClick = true; - this.#doubleClickTimer = window.setTimeout(() => { + this.#doubleClickTimer = window.setTimeout(() => { // 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) getApp().getUnitsManager().deselectAllUnits(); - + this.setSelected(!this.getSelected()); const detail = { "detail": { "unit": this } }; if (this.getSelected()) @@ -779,7 +823,6 @@ export class Unit extends CustomMarker { } #onDoubleClick(e: any) { - // Let single clicks work again this.#waitingForDoubleClick = false; clearTimeout(this.#doubleClickTimer); @@ -792,49 +835,49 @@ export class Unit extends CustomMarker { }); } - #onContextMenu(e: any) { - var options: { [key: string]: { text: string, tooltip: string } } = {}; - const selectedUnits = getApp().getUnitsManager().getSelectedUnits(); - const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories(); + getActionOptions() { + var options: { [key: string]: { text: string, tooltip: string, type: string } } | null = null; - options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it" }; + var units = getApp().getUnitsManager().getSelectedUnits(); + units.push(this); - if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) { - options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" }; - if (getApp().getUnitsManager().getSelectedUnitsCategories().length == 1 && getApp().getUnitsManager().getSelectedUnitsCategories()[0] === "Aircraft") - options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };; - } - else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) { - if (this.getCategory() == "Aircraft") { - options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR + /* Keep only the common "or" options or any "and" option */ + units.forEach((unit: Unit) => { + var unitOptions = unit.getActions(); + if (options === null) { + options = unitOptions; + } else { + /* Options of "or" type get shown if any one unit has it*/ + for (let optionKey in unitOptions) { + if (unitOptions[optionKey].type == "or") { + options[optionKey] = unitOptions[optionKey]; + } + } + + /* Options of "and" type get shown if ALL units have it */ + for (let optionKey in options) { + if (!(optionKey in unitOptions)) { + delete options[optionKey]; + } + } } - } + }); - if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined) - options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." }; + return options ?? {}; + } + + #onContextMenu(e: any) { + var options = this.getActionOptions(); if (Object.keys(options).length > 0) { getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => { getApp().getMap().hideUnitContextMenu(); - this.#executeAction(e, option); + this.executeAction(e, option); }); } } - #executeAction(e: any, action: string) { - if (action === "center-map") - getApp().getMap().centerOnUnit(this.ID); - if (action === "attack") - getApp().getUnitsManager().selectedUnitsAttackUnit(this.ID); - else if (action === "refuel") - getApp().getUnitsManager().selectedUnitsRefuel(); - else if (action === "group") - getApp().getUnitsManager().selectedUnitsCreateGroup(); - else if (action === "follow") - this.#showFollowOptions(e); - } - #showFollowOptions(e: any) { var options: { [key: string]: { text: string, tooltip: string } } = {}; @@ -874,14 +917,14 @@ export class Unit extends CustomMarker { var angleRad = deg2rad(angleDeg); var distance = ftToM(parseInt((dialog.querySelector(`#distance`)?.querySelector("input")).value)); var upDown = ftToM(parseInt((dialog.querySelector(`#up-down`)?.querySelector("input")).value)); - + // X: front-rear, positive front // Y: top-bottom, positive top // Z: left-right, positive right var x = distance * Math.cos(angleRad); var y = upDown; var z = distance * Math.sin(angleRad); - + getApp().getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z }); } }); @@ -1148,6 +1191,37 @@ export class AirUnit extends Unit { rotateToHeading: false }; } + + getActions() { + var options: { [key: string]: { text: string, tooltip: string, type: string } } = {}; + + /* Options if this unit is not selected */ + if (!this.getSelected()) { + /* Someone else is selected */ + if (getApp().getUnitsManager().getSelectedUnits().length > 0) { + options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" }; + options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position", type: "or" }; + } else { + options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" }; + } + } + /* Options if this unit is selected*/ + else if (this.getSelected()) { + /* This is the only selected unit */ + if (getApp().getUnitsManager().getSelectedUnits().length == 1) { + options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" }; + } else { + /* Provision */ + } + + options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB.", type: "and" }; // TODO Add some way of knowing which aircraft can AAR + } + /* All other options */ + else { + /* Provision */ + } + return options; + } } export class Aircraft extends AirUnit { @@ -1191,6 +1265,39 @@ export class GroundUnit extends Unit { }; } + getActions() { + var options: { [key: string]: { text: string, tooltip: string, type: string } } = {}; + + /* Options if this unit is not selected */ + if (!this.getSelected()) { + /* Someone else is selected */ + if (getApp().getUnitsManager().getSelectedUnits().length > 0) { + options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" }; + } else { + options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" }; + } + } + /* Options if this unit is selected*/ + else if (this.getSelected()) { + /* This is the only selected unit */ + if (getApp().getUnitsManager().getSelectedUnits().length == 1) { + options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" }; + } else { + options["group-ground"] = { text: "Create group", tooltip: "Create a group from the selected units", type: "and" }; + } + + if (["AAA", "flak"].includes(this.getType())) { + options["scenic-aaa"] = { text: "Scenic AAA", tooltip: "Shoot AAA in the air without aiming at any target, when a enemy unit gets close enough. WARNING: works correctly only on neutral units, blue or red units will aim", type: "and" }; + options["miss-aaa"] = { text: "Miss on purpose AAA", tooltip: "Shoot AAA towards the closest enemy unit, but don't aim precisely. WARNING: works correctly only on neutral units, blue or red units will aim", type: "and" }; + } + } + /* All other options */ + else { + /* Provision */ + } + return options; + } + getCategory() { return "GroundUnit"; } @@ -1222,6 +1329,34 @@ export class NavyUnit extends Unit { }; } + getActions() { + var options: { [key: string]: { text: string, tooltip: string, type: string } } = {}; + + /* Options if this unit is not selected */ + if (!this.getSelected()) { + /* Someone else is selected */ + if (getApp().getUnitsManager().getSelectedUnits().length > 0) { + options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" }; + } else { + options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" }; + } + } + /* Options if this unit is selected */ + else if (this.getSelected()) { + /* This is the only selected unit */ + if (getApp().getUnitsManager().getSelectedUnits().length == 1) { + options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" }; + } else { + options["group-navy"] = { text: "Create group", tooltip: "Create a group from the selected units", type: "and" }; + } + } + /* All other options */ + else { + /* Provision */ + } + return options; + } + getMarkerCategory() { return "navyunit"; } diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index 4a8383a2..0631214e 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -504,6 +504,19 @@ export class UnitsManager { this.#showActionMessage(selectedUnits, `follow roads set to ${followRoads}`); } + /** Instruct selected units to operate as a certain coalition + * + * @param operateAsBool If true, units will operate as blue + */ + selectedUnitsSetOperateAs(operateAsBool: boolean) { + var operateAs = operateAsBool? "blue": "red"; + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + for (let idx in selectedUnits) { + selectedUnits[idx].setOperateAs(operateAs); + } + this.#showActionMessage(selectedUnits, `operate as set to ${operateAs}`); + } + /** Instruct units to attack a specific unit * * @param ID ID of the unit to attack @@ -629,6 +642,28 @@ export class UnitsManager { }); this.#showActionMessage(selectedUnits, `unit simulating fire fight`); } + + /** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming + * + */ + selectedUnitsScenicAAA() { + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + for (let idx in selectedUnits) { + selectedUnits[idx].scenicAAA(); + } + this.#showActionMessage(selectedUnits, `unit set to perform scenic AAA`); + } + + /** Instruct units to enter into miss on purpose mode. Units will aim to the nearest enemy unit but not precisely. + * + */ + selectedUnitsMissOnPurpose() { + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + for (let idx in selectedUnits) { + selectedUnits[idx].missOnPurpose(); + } + this.#showActionMessage(selectedUnits, `unit set to perform miss on purpose AAA`); + } /*********************** Control operations on selected units ************************/ /** See getUnitsCategories for more info diff --git a/client/views/panels/unitcontrol.ejs b/client/views/panels/unitcontrol.ejs index 906fbbef..1aa80183 100644 --- a/client/views/panels/unitcontrol.ejs +++ b/client/views/panels/unitcontrol.ejs @@ -61,6 +61,12 @@ +
+

Operate as

+
+
Determines if the unit will target red or blue units when performing scenic tasks.
+
+

Unit active

@@ -81,11 +87,11 @@
- - - - - + + + + +
\ No newline at end of file diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 7e3d82e7..28f4fe00 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.4.4-alpha" -local debug = true -- True enables debug printing using DCS messages +local debug = false -- True enables debug printing using DCS messages -- .dll related variables Olympus.OlympusDLL = nil @@ -229,6 +229,14 @@ function Olympus.buildTask(groupName, options) -- Fire at a specific point elseif options['id'] == 'FireAtPoint' and options['lat'] and options['lng'] and options['radius'] then local point = coord.LLtoLO(options['lat'], options['lng'], 0) + local expendQtyEnabled = false + local expendQty = 0 + + if options['expendQty'] then + expendQtyEnabled = true + expendQty = options['expendQty'] + end + if options['alt'] then task = { id = 'FireAtPoint', @@ -236,7 +244,9 @@ function Olympus.buildTask(groupName, options) point = {x = point.x, y = point.z}, radius = options['radius'], altitude = options['alt'], - alt_type = 0 -- ASL + alt_type = 0, -- ASL + expendQtyEnabled = expendQtyEnabled, + expendQty = expendQty } } else @@ -244,7 +254,9 @@ function Olympus.buildTask(groupName, options) id = 'FireAtPoint', params = { point = {x = point.x, y = point.z}, - radius = options['radius'] + radius = options['radius'], + expendQtyEnabled = expendQtyEnabled, + expendQty = expendQty } } end diff --git a/scripts/python/addMissingUnits.py b/scripts/python/addMissingUnits.py new file mode 100644 index 00000000..091d5ad3 --- /dev/null +++ b/scripts/python/addMissingUnits.py @@ -0,0 +1,77 @@ +import sys +import json +import inspect +import difflib +from slpp import slpp as lua + +SEARCH_FOLDER = "D:\\Eagle Dynamics\\DCS World OpenBeta" + +sys.path.append("D:\\Documents\\dcs") + +from dcs.vehicles import * +from dcs.ships import * +from dcs.planes import * +from dcs.helicopters import * + +# The database file on which to operate is the first standard argument of the call +if len(sys.argv) > 1: + if (sys.argv[1] == "aircraft"): + filename = '..\\..\\client\\public\\databases\\units\\aircraftdatabase.json' + units_map = plane_map + elif (sys.argv[1] == "helicopter"): + filename = '..\\..\\client\\public\\databases\\units\\helicopterdatabase.json' + units_map = helicopter_map + elif (sys.argv[1] == "groundunit"): + filename = '..\\..\\client\\public\\databases\\units\\groundunitdatabase.json' + units_map = vehicle_map + elif (sys.argv[1] == "navyunit"): + filename = '..\\..\\client\\public\\databases\\units\\navyunitdatabase.json' + units_map = ship_map + + # Loads the database + with open(filename) as f: + database = json.load(f) + + for unit in units_map.values(): + if unit.id not in database: + database[unit.id] = { + "name": unit.id, + "coalition": "", + "era": "", + "label": unit.name, + "shortLabel": unit.name, + "type": unit.__qualname__.split(".")[0], + "enabled": True, + "liveries": {} + } + print("Added missing unit " + unit.id) + + to_remove = [] + to_change_case = {} + for id in database: + found = False + for unit in units_map.values(): + if unit.id == id: + found = True + elif unit.id.lower() == id.lower() : + to_change_case[unit.id] = database[id] + + if not found: + to_remove.append(id) + + for id in to_remove: + if database[id]["type"] == "SAM Site": + print("Skipping " + id + ", it is a SAM Site") + else: + del database[id] + print("Removed unit " + id) + + for id in to_change_case: + database[id] = to_change_case[id] + print("Changed case of unit " + id) + + # Dump everything in the database + with open(filename, "w") as f: + json.dump(database, f, indent=2) + +print("Done!") \ No newline at end of file diff --git a/scripts/python/generateGunData.py b/scripts/python/generateGunData.py deleted file mode 100644 index 858cc28e..00000000 --- a/scripts/python/generateGunData.py +++ /dev/null @@ -1,21 +0,0 @@ -import sys -import json -import inspect -import difflib -from slpp import slpp as lua - -SEARCH_FOLDER = "D:\\Eagle Dynamics\\DCS World OpenBeta" - -sys.path.append("..\\..\\..\\dcs-master\\dcs-master") - -from dcs.vehicles import * - -with open("gundata.h", "w") as f: - for unit in vehicle_map.values(): - if unit in Artillery.__dict__.values() or unit in Armor.__dict__.values() or unit in Infantry.__dict__.values(): - f.write('{"' + unit.id + '", {0.9, 860}}, \n') - -# Done! -print("Done!") - - \ No newline at end of file diff --git a/scripts/python/gundata.h b/scripts/python/gundata.h deleted file mode 100644 index d28a26e5..00000000 --- a/scripts/python/gundata.h +++ /dev/null @@ -1,109 +0,0 @@ -{"2B11 mortar", {0.9, 860}}, -{"SAU Gvozdika", {0.9, 860}}, -{"SAU Msta", {0.9, 860}}, -{"SAU Akatsia", {0.9, 860}}, -{"SAU 2-C9", {0.9, 860}}, -{"M-109", {0.9, 860}}, -{"SpGH_Dana", {0.9, 860}}, -{"AAV7", {0.9, 860}}, -{"BMD-1", {0.9, 860}}, -{"BMP-1", {0.9, 860}}, -{"BMP-2", {0.9, 860}}, -{"BMP-3", {0.9, 860}}, -{"BRDM-2", {0.9, 860}}, -{"BTR_D", {0.9, 860}}, -{"Cobra", {0.9, 860}}, -{"LAV-25", {0.9, 860}}, -{"M1043 HMMWV Armament", {0.9, 860}}, -{"M1045 HMMWV TOW", {0.9, 860}}, -{"M1126 Stryker ICV", {0.9, 860}}, -{"M-113", {0.9, 860}}, -{"M1134 Stryker ATGM", {0.9, 860}}, -{"M-2 Bradley", {0.9, 860}}, -{"MCV-80", {0.9, 860}}, -{"MTLB", {0.9, 860}}, -{"Marder", {0.9, 860}}, -{"TPZ", {0.9, 860}}, -{"Grad_FDDM", {0.9, 860}}, -{"Paratrooper RPG-16", {0.9, 860}}, -{"Paratrooper AKS-74", {0.9, 860}}, -{"Infantry AK Ins", {0.9, 860}}, -{"Soldier AK", {0.9, 860}}, -{"Infantry AK", {0.9, 860}}, -{"Soldier M249", {0.9, 860}}, -{"Soldier M4", {0.9, 860}}, -{"Soldier M4 GRG", {0.9, 860}}, -{"Soldier RPG", {0.9, 860}}, -{"MLRS FDDM", {0.9, 860}}, -{"Infantry AK ver2", {0.9, 860}}, -{"Infantry AK ver3", {0.9, 860}}, -{"Grad-URAL", {0.9, 860}}, -{"Uragan_BM-27", {0.9, 860}}, -{"Smerch", {0.9, 860}}, -{"Smerch_HE", {0.9, 860}}, -{"MLRS", {0.9, 860}}, -{"Challenger2", {0.9, 860}}, -{"Leclerc", {0.9, 860}}, -{"M-60", {0.9, 860}}, -{"M1128 Stryker MGS", {0.9, 860}}, -{"M-1 Abrams", {0.9, 860}}, -{"T-55", {0.9, 860}}, -{"T-72B", {0.9, 860}}, -{"T-80UD", {0.9, 860}}, -{"T-90", {0.9, 860}}, -{"Leopard1A3", {0.9, 860}}, -{"Merkava_Mk4", {0.9, 860}}, -{"JTAC", {0.9, 860}}, -{"Infantry Animated", {0.9, 860}}, -{"HL_DSHK", {0.9, 860}}, -{"HL_KORD", {0.9, 860}}, -{"tt_DSHK", {0.9, 860}}, -{"tt_KORD", {0.9, 860}}, -{"HL_B8M1", {0.9, 860}}, -{"tt_B8M1", {0.9, 860}}, -{"M4_Sherman", {0.9, 860}}, -{"M2A1_halftrack", {0.9, 860}}, -{"BTR-80", {0.9, 860}}, -{"T-72B3", {0.9, 860}}, -{"PT_76", {0.9, 860}}, -{"BTR-82A", {0.9, 860}}, -{"Chieftain_mk3", {0.9, 860}}, -{"Pz_IV_H", {0.9, 860}}, -{"Leopard-2A5", {0.9, 860}}, -{"Leopard-2", {0.9, 860}}, -{"leopard-2A4", {0.9, 860}}, -{"leopard-2A4_trs", {0.9, 860}}, -{"Sd_Kfz_251", {0.9, 860}}, -{"T155_Firtina", {0.9, 860}}, -{"VAB_Mephisto", {0.9, 860}}, -{"ZTZ96B", {0.9, 860}}, -{"ZBD04A", {0.9, 860}}, -{"PLZ05", {0.9, 860}}, -{"TYPE-59", {0.9, 860}}, -{"Tiger_I", {0.9, 860}}, -{"Tiger_II_H", {0.9, 860}}, -{"Pz_V_Panther_G", {0.9, 860}}, -{"Jagdpanther_G1", {0.9, 860}}, -{"JagdPz_IV", {0.9, 860}}, -{"Stug_IV", {0.9, 860}}, -{"SturmPzIV", {0.9, 860}}, -{"Wespe124", {0.9, 860}}, -{"Sd_Kfz_234_2_Puma", {0.9, 860}}, -{"soldier_mauser98", {0.9, 860}}, -{"Stug_III", {0.9, 860}}, -{"Elefant_SdKfz_184", {0.9, 860}}, -{"Pak40", {0.9, 860}}, -{"LeFH_18-40-105", {0.9, 860}}, -{"Cromwell_IV", {0.9, 860}}, -{"M4A4_Sherman_FF", {0.9, 860}}, -{"soldier_wwii_br_01", {0.9, 860}}, -{"Centaur_IV", {0.9, 860}}, -{"Churchill_VII", {0.9, 860}}, -{"Daimler_AC", {0.9, 860}}, -{"Tetrarch", {0.9, 860}}, -{"M12_GMC", {0.9, 860}}, -{"soldier_wwii_us", {0.9, 860}}, -{"M10_GMC", {0.9, 860}}, -{"M8_Greyhound", {0.9, 860}}, -{"M2A1-105", {0.9, 860}}, -{"M4_Tractor", {0.9, 860}}, diff --git a/src/core/include/datatypes.h b/src/core/include/datatypes.h index 207b72ee..dd098232 100644 --- a/src/core/include/datatypes.h +++ b/src/core/include/datatypes.h @@ -43,6 +43,7 @@ namespace DataIndex { contacts, activePath, isLeader, + operateAs, lastIndex, endOfData = 255 }; @@ -65,7 +66,9 @@ namespace State CARPET_BOMB, BOMB_BUILDING, FIRE_AT_AREA, - SIMULATE_FIRE_FIGHT + SIMULATE_FIRE_FIGHT, + SCENIC_AAA, + MISS_ON_PURPOSE }; }; diff --git a/src/core/include/unit.h b/src/core/include/unit.h index b36b4d9b..ebbcff94 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -100,6 +100,7 @@ public: virtual void setContacts(vector newValue); virtual void setActivePath(list newValue); virtual void setIsLeader(bool newValue) { updateValue(isLeader, newValue, DataIndex::isLeader); } + virtual void setOperateAs(unsigned char newValue) { updateValue(operateAs, newValue, DataIndex::operateAs); } /********** Getters **********/ virtual string getCategory() { return category; }; @@ -140,6 +141,7 @@ public: virtual vector getTargets() { return contacts; } virtual list getActivePath() { return activePath; } virtual bool getIsLeader() { return isLeader; } + virtual unsigned char getOperateAs() { return operateAs; } protected: unsigned int ID; @@ -182,10 +184,12 @@ protected: vector contacts; list activePath; bool isLeader = false; + unsigned char operateAs = 2; Coords activeDestination = Coords(NULL); /********** Other **********/ unsigned int taskCheckCounter = 0; + unsigned int internalCounter = 0; bool hasTaskAssigned = false; double initialFuel = 0; map updateTimeMap; diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index 9a6b2f1d..f7af7304 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -23,7 +23,8 @@ public: void deleteUnit(unsigned int ID, bool explosion, bool immediate); void acquireControl(unsigned int ID); void loadDatabases(); - + Unit* getClosestUnit(Unit* unit, unsigned char coalition, vector categories, double &distance); + private: map units; json::value missionDB; diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index 3f70a590..a84a69c3 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -73,6 +73,12 @@ void GroundUnit::setState(unsigned char newState) setTargetPosition(Coords(NULL)); break; } + case State::SCENIC_AAA: { + break; + } + case State::MISS_ON_PURPOSE: { + break; + } default: break; } @@ -99,6 +105,16 @@ void GroundUnit::setState(unsigned char newState) resetActiveDestination(); break; } + case State::SCENIC_AAA: { + clearActivePath(); + resetActiveDestination(); + break; + } + case State::MISS_ON_PURPOSE: { + clearActivePath(); + resetActiveDestination(); + break; + } default: break; } @@ -156,11 +172,13 @@ void GroundUnit::AIloop() scheduler->appendCommand(command); setHasTask(true); } + + break; } case State::SIMULATE_FIRE_FIGHT: { setTask("Simulating fire fight"); - if (!getHasTask() || (((double)(rand()) / (double)(RAND_MAX)) < 0.002)) { + if (!getHasTask() || internalCounter == 0) { double dist; double bearing1; double bearing2; @@ -175,7 +193,6 @@ void GroundUnit::AIloop() if (databaseEntry.has_number_field(L"barrelHeight") && databaseEntry.has_number_field(L"muzzleVelocity")) { barrelHeight = databaseEntry[L"barrelHeight"].as_number().to_double(); muzzleVelocity = databaseEntry[L"muzzleVelocity"].as_number().to_double(); - log(to_string(barrelHeight) + " " + to_string(muzzleVelocity)); } } @@ -193,6 +210,114 @@ void GroundUnit::AIloop() scheduler->appendCommand(command); setHasTask(true); } + + if (internalCounter == 0) + internalCounter = 20 / 0.05; + internalCounter--; + + break; + } + case State::SCENIC_AAA: { + setTask("Scenic AAA"); + + if ((!getHasTask() || internalCounter == 0) && getOperateAs() > 0) { + double distance = 0; + unsigned char targetCoalition = getOperateAs() == 2 ? 1 : 2; + Unit* target = unitsManager->getClosestUnit(this, targetCoalition, { "Aircraft", "Helicopter" }, distance); + + /* Only run if an enemy air unit is closer than 20km to avoid useless load */ + if (distance < 20000 /* m */) { + double r = 15; /* m */ + double barrelElevation = r * tan(acos(((double)(rand()) / (double)(RAND_MAX)))); + + double lat = 0; + double lng = 0; + double randomBearing = ((double)(rand()) / (double)(RAND_MAX)) * 360; + Geodesic::WGS84().Direct(position.lat, position.lng, randomBearing, r, lat, lng); + + std::ostringstream taskSS; + taskSS.precision(10); + taskSS << "{id = 'FireAtPoint', lat = " << lat << ", lng = " << lng << ", alt = " << position.alt + barrelElevation << ", radius = 0.001}"; + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); })); + scheduler->appendCommand(command); + setHasTask(true); + } + } + + if (internalCounter == 0) + internalCounter = 20 / 0.05; + internalCounter--; + + break; + } + case State::MISS_ON_PURPOSE: { + setTask("Missing on purpose"); + + /* Only run this when the internal counter reaches 0 to avoid excessive computations when no nearby target */ + if (internalCounter == 0 && getOperateAs() > 0) { + double distance = 0; + unsigned char targetCoalition = getOperateAs() == 2 ? 1 : 2; + Unit* target = unitsManager->getClosestUnit(this, targetCoalition, {"Aircraft", "Helicopter"}, distance); + + /* Only do if we have a valid target close enough for AAA */ + if (target != nullptr && distance < 10000 /* m */) { + /* Default gun values */ + double barrelHeight = 1.0; /* m */ + double muzzleVelocity = 860; /* m/s */ + double aimTime = 10; /* s */ + unsigned int shotsToFire = 10; + + if (database.has_object_field(to_wstring(name))) { + json::value databaseEntry = database[to_wstring(name)]; + if (databaseEntry.has_number_field(L"barrelHeight")) + barrelHeight = databaseEntry[L"barrelHeight"].as_number().to_double(); + if (databaseEntry.has_number_field(L"muzzleVelocity")) + muzzleVelocity = databaseEntry[L"muzzleVelocity"].as_number().to_double(); + if (databaseEntry.has_number_field(L"aimTime")) + aimTime = databaseEntry[L"aimTime"].as_number().to_double(); + if (databaseEntry.has_number_field(L"shotsToFire")) + shotsToFire = databaseEntry[L"shotsToFire"].as_number().to_uint32(); + } + + /* Approximate the flight time */ + if (muzzleVelocity != 0) + aimTime += distance / muzzleVelocity; + + internalCounter = (aimTime + 2) / 0.05; + + /* Compute where the target will be in aimTime seconds. We don't consider vertical velocity atm, since after all we are not really tring to hit */ + double aimDistance = target->getSpeed() * aimTime; + double aimLat = 0; + double aimLng = 0; + Geodesic::WGS84().Direct(target->getPosition().lat, target->getPosition().lng, target->getHeading() * 57.29577, aimDistance, aimLat, aimLng); /* TODO make util function */ + + /* Compute distance to the aim point */ + double dist; + double bearing1; + double bearing2; + Geodesic::WGS84().Inverse(position.lat, position.lng, aimLat, aimLng, dist, bearing1, bearing2); + + /* Send the command */ + std::ostringstream taskSS; + taskSS.precision(10); + taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << target->getPosition().alt << ", radius = 0.001, expendQty = " << shotsToFire << " }"; + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); })); + scheduler->appendCommand(command); + setHasTask(true); + + setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt)); + } + else { + if (getHasTask()) + resetTask(); + } + } + + if (internalCounter == 0) + internalCounter = 5 / 0.05; + internalCounter--; + + break; } default: break; diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index b1564bdf..df480c32 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -390,6 +390,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js Coords location; location.lat = lat; location.lng = lng; cloneOptions.push_back({ ID, location }); + log(username + " cloning unit with ID " + to_string(ID), true); } command = dynamic_cast(new Clone(cloneOptions, deleteOriginal)); @@ -575,7 +576,32 @@ void Scheduler::handleRequest(string key, json::value value, string username, js unit->setTargetPosition(loc); log(username + " tasked unit " + unit->getName() + " to simulate a fire fight", true); } - else if (key.compare("setCommandModeOptions") == 0) { + else if (key.compare("scenicAAA") == 0) + { + unsigned int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setState(State::SCENIC_AAA); + log(username + " tasked unit " + unit->getName() + " to enter scenic AAA state", true); + } + else if (key.compare("missOnPurpose") == 0) + { + unsigned int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setState(State::MISS_ON_PURPOSE); + log(username + " tasked unit " + unit->getName() + " to enter Miss On Purpose state", true); + } + else if (key.compare("setOperateAs") == 0) + { + unsigned int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + unsigned char operateAs = value[L"operateAs"].as_number().to_uint32(); + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setOperateAs(operateAs); + } + else if (key.compare("setCommandModeOptions") == 0) + { setCommandModeOptions(value); log(username + " updated the Command Mode Options", true); } diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 4aaf5d8a..a5414aab 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -191,6 +191,7 @@ void Unit::refreshLeaderData(unsigned long long time) { case DataIndex::radio: updateValue(radio, leader->radio, datumIndex); break; case DataIndex::generalSettings: updateValue(generalSettings, leader->generalSettings, datumIndex); break; case DataIndex::activePath: updateValue(activePath, leader->activePath, datumIndex); break; + case DataIndex::operateAs: updateValue(operateAs, leader->operateAs, datumIndex); break; } } } @@ -270,6 +271,7 @@ void Unit::getData(stringstream& ss, unsigned long long time) case DataIndex::contacts: appendVector(ss, datumIndex, contacts); break; case DataIndex::activePath: appendList(ss, datumIndex, activePath); break; case DataIndex::isLeader: appendNumeric(ss, datumIndex, isLeader); break; + case DataIndex::operateAs: appendNumeric(ss, datumIndex, operateAs); break; } } } diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 5a412ac0..a157956e 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -11,6 +11,9 @@ #include "scheduler.h" #include "defines.h" +#include +using namespace GeographicLib; + #include "base64.hpp" using namespace base64; @@ -148,6 +151,48 @@ void UnitsManager::deleteUnit(unsigned int ID, bool explosion, bool immediate) } } +Unit* UnitsManager::getClosestUnit(Unit* unit, unsigned char coalition, vector categories, double &distance) { + Unit* closestUnit = nullptr; + distance = 0; + + for (auto const& p : units) { + /* Check if the units category is of the correct type */ + bool requestedCategory = false; + for (auto const& category : categories) { + if (p.second->getCategory().compare(category) == 0) { + requestedCategory = true; + break; + } + } + + /* Check if the unit belongs to the desired coalition, is alive, and is of the category requested */ + if (requestedCategory && p.second->getCoalition() == coalition && p.second->getAlive()) { + /* Compute the distance from the unit to the tested unit */ + double dist; + double bearing1; + double bearing2; + Geodesic::WGS84().Inverse(unit->getPosition().lat, unit->getPosition().lng, p.second->getPosition().lat, p.second->getPosition().lng, dist, bearing1, bearing2); + + /* If the closest unit has not been assigned yet, assign it to this unit */ + if (closestUnit == nullptr) + { + closestUnit = p.second; + distance = dist; + + } + else { + /* Check if the unit is closer than the one already selected */ + if (dist < distance) { + closestUnit = p.second; + distance = dist; + } + } + } + } + + return closestUnit; +} + void UnitsManager::acquireControl(unsigned int ID) { Unit* leader = getGroupLeader(ID); if (leader != nullptr) {