From 875f3ebe68d2ef1d46580bbf3bfa0cb6f7e255dc Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 26 Jul 2023 22:39:26 +0200 Subject: [PATCH 1/4] Added handler to detect units and weapons creation --- scripts/OlympusCommand.lua | 379 ++++++++++++++++++++----------------- 1 file changed, 204 insertions(+), 175 deletions(-) diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index f07bae12..e8fdd894 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,11 +1,11 @@ -local version = "v0.4.0-alpha" +local version = "v0.4.1-alpha" -local debug = false +local debug = true Olympus.unitCounter = 1 Olympus.payloadRegistry = {} -Olympus.groupIndex = 0 -Olympus.groupStep = 5 +Olympus.unitIndex = 0 +Olympus.unitStep = 50 Olympus.OlympusDLL = nil Olympus.DLLsloaded = false @@ -13,8 +13,8 @@ Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\' Olympus.log = mist.Logger:new("Olympus", 'info') Olympus.missionData = {} +Olympus.units = {} Olympus.unitsData = {} -Olympus.unitNames = {} Olympus.missionStartTime = DCS.getRealTime() @@ -43,13 +43,7 @@ end -- Gets a unit class reference from a given ObjectID (the ID used by Olympus for unit referencing) function Olympus.getUnitByID(ID) - for name, table in pairs(mist.DBs.unitsByName) do - local unit = Unit.getByName(name) - if unit and unit["getObjectID"] and unit:getObjectID() == ID then - return unit - end - end - return nil + return Olympus.units[ID]; end function Olympus.getCountryIDByCoalition(coalition) @@ -651,6 +645,176 @@ function Olympus.setOnOff(groupName, onOff) end end + +function Olympus.setUnitsData(arg, time) + -- Units data + local units = {} + + local startIndex = Olympus.unitIndex + local endIndex = startIndex + Olympus.unitStep + local index = 0 + for ID, unit in pairs(Olympus.units) do + index = index + 1 + if index > startIndex then + if unit ~= nil then + local table = {} + table["category"] = "None" + + -- Get the object category in Olympus name + local objectCategory = unit:getCategory() + if objectCategory == Object.Category.UNIT then + if unit:getDesc().category == Unit.Category.AIRPLANE then + table["category"] = "Aircraft" + elseif unit:getDesc().category == Unit.Category.HELICOPTER then + table["category"] = "Helicopter" + elseif unit:getDesc().category == Unit.Category.GROUND_UNIT then + table["category"] = "GroundUnit" + elseif unit:getDesc().category == Unit.Category.SHIP then + table["category"] = "NavyUnit" + end + elseif objectCategory == Object.Category.WEAPON then + if unit:getDesc().category == Weapon.Category.MISSILE then + table["category"] = "Missile" + elseif unit:getDesc().category == Weapon.Category.ROCKET then + table["category"] = "Missile" + elseif unit:getDesc().category == Weapon.Category.BOMB then + table["category"] = "Bomb" + end + else + units[ID] = {isAlive = false} + Olympus.units[ID] = nil + end + + -- If the category is handled by Olympus, get the data + if table["category"] ~= "None" then + -- Compute unit position and heading + local lat, lng, alt = coord.LOtoLL(unit:getPoint()) + local position = unit:getPosition() + local heading = math.atan2( position.x.z, position.x.x ) + + -- Fill the data table + table["name"] = unit:getTypeName() + table["coalitionID"] = unit:getCoalition() + table["position"] = {} + table["position"]["lat"] = lat + table["position"]["lng"] = lng + table["position"]["alt"] = alt + table["speed"] = mist.vec.mag(unit:getVelocity()) + table["heading"] = heading + table["isAlive"] = unit:isExist() + + -- Data for real units only + if objectCategory == Object.Category.UNIT then + -- Get the targets detected by the group controller + local group = unit:getGroup() + local controller = group:getController() + local controllerTargets = controller:getDetectedTargets() + local contacts = {} + for i, target in ipairs(controllerTargets) do + for det, enum in pairs(Controller.Detection) do + if target.object ~= nil then + target["detectionMethod"] = det + contacts[#contacts + 1] = target + end + end + end + + table["country"] = unit:getCountry() + table["unitName"] = unit:getName() + table["groupName"] = group:getName() + table["isHuman"] = (unit:getPlayerName() ~= nil) + table["hasTask"] = controller:hasTask() + table["ammo"] = unit:getAmmo() --TODO remove a lot of stuff we don't really need + table["fuel"] = unit:getFuel() + table["life"] = unit:getLife() / unit:getLife0() + table["contacts"] = contacts + end + units[ID] = table + end + else + units[ID] = {isAlive = false} + Olympus.units[ID] = nil + end + end + if index >= endIndex then + break + end + end + if index ~= endIndex then + Olympus.unitIndex = 0 + else + Olympus.unitIndex = endIndex + end + + -- Assemble unitsData table + Olympus.unitsData["units"] = units + + Olympus.OlympusDLL.setUnitsData() + return time + 0.05 +end + +function Olympus.setMissionData(arg, time) + -- Bullseye data + local bullseyes = {} + for i = 0, 2 do + local bullseyeVec3 = coalition.getMainRefPoint(i) + local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3) + bullseyes[i] = {} + bullseyes[i]["latitude"] = bullseyeLatitude + bullseyes[i]["longitude"] = bullseyeLongitude + bullseyes[i]["coalition"] = Olympus.getCoalitionByCoalitionID(i) + end + + -- Airbases data + local base = world.getAirbases() + local airbases = {} + for i = 1, #base do + local info = {} + local latitude, longitude, altitude = coord.LOtoLL(Airbase.getPoint(base[i])) + info["callsign"] = Airbase.getCallsign(base[i]) + info["coalition"] = Olympus.getCoalitionByCoalitionID(Airbase.getCoalition(base[i])) + info["latitude"] = latitude + info["longitude"] = longitude + if Airbase.getUnit(base[i]) then + info["unitId"] = Airbase.getUnit(base[i]):getID() + end + airbases[i] = info + end + + -- Mission + local mission = {} + mission.theatre = env.mission.theatre + mission.dateAndTime = { + ["elapsedTime"] = DCS.getRealTime() - Olympus.missionStartTime, + ["time"] = mist.time.getDHMS(timer.getAbsTime()), + ["startTime"] = env.mission.start_time, + ["date"] = env.mission.date + } + + -- Assemble table + Olympus.missionData["bullseyes"] = bullseyes + Olympus.missionData["airbases"] = airbases + Olympus.missionData["mission"] = mission + + Olympus.OlympusDLL.setMissionData() + return time + 1 +end + +function Olympus.initializeUnits() + if mist and mist.DBs and mist.DBs.MEunitsById then + for id, unitTable in pairs(mist.DBs.MEunitsById) do + local unit = Unit.getByName(unitTable["unitName"]) + if unit then + Olympus.units[unit["id_"]] = unit + end + end + Olympus.debug("Olympus units table initialized", 2) + else + Olympus.debug("MIST DBs not ready", 2) + timer.scheduleFunction(Olympus.initializeUnits, {}, timer.getTime() + 1) + end +end + function Olympus.serializeTable(val, name, skipnewlines, depth) skipnewlines = skipnewlines or false depth = depth or 0 @@ -712,171 +876,11 @@ function Olympus.hasKey(tab, key) return false end -function Olympus.setUnitsData(arg, time) - -- Units data - local units = {} - - local startIndex = Olympus.groupIndex - local endIndex = startIndex + Olympus.groupStep - local index = 0 - if mist ~= nil and mist.DBs ~= nil and mist.DBs.groupsByName ~= nil then - for groupName, gp in pairs(mist.DBs.groupsByName) do - index = index + 1 - if index > startIndex then - if groupName ~= nil then - local group = Group.getByName(groupName) - if group ~= nil then - -- Get the targets detected by the group controller - local controller = group:getController() - local controllerTargets = controller:getDetectedTargets() - local contacts = {} - for i, target in ipairs(controllerTargets) do - for det, enum in pairs(Controller.Detection) do - if target.object ~= nil then - target["detectionMethod"] = det - contacts[#contacts + 1] = target - end - end - end - - -- Update the units position - for index, unit in pairs(group:getUnits()) do - local unitController = unit:getController() - local table = {} - table["category"] = "None" - - -- Get the object category in Olympus name - local objectCategory = unit:getCategory() - if objectCategory == Object.Category.UNIT then - if unit:getDesc().category == Unit.Category.AIRPLANE then - table["category"] = "Aircraft" - elseif unit:getDesc().category == Unit.Category.HELICOPTER then - table["category"] = "Helicopter" - elseif unit:getDesc().category == Unit.Category.GROUND_UNIT then - table["category"] = "GroundUnit" - elseif unit:getDesc().category == Unit.Category.SHIP then - table["category"] = "NavyUnit" - end - elseif objectCategory == Object.Category.WEAPON then - if unit:getDesc().category == Weapon.Category.MISSILE then - table["category"] = "Missile" - elseif unit:getDesc().category == Weapon.Category.ROCKET then - table["category"] = "Missile" - elseif unit:getDesc().category == Unit.Category.BOMB then - table["category"] = "Bomb" - end - end - - -- If the category is handled by Olympus, get the data - if table["category"] ~= "None" then - -- Compute unit position and heading - local lat, lng, alt = coord.LOtoLL(unit:getPoint()) - local position = unit:getPosition() - local heading = math.atan2( position.x.z, position.x.x ) - - -- Fill the data table - table["name"] = unit:getTypeName() - table["unitName"] = unit:getName() - table["groupName"] = group:getName() - table["isHuman"] = (unit:getPlayerName() ~= nil) - table["coalitionID"] = unit:getCoalition() - table["hasTask"] = controller:hasTask() - table["ammo"] = unit:getAmmo() --TODO remove a lot of stuff we don't really need - table["fuel"] = unit:getFuel() - table["life"] = unit:getLife() / unit:getLife0() - table["contacts"] = contacts - table["position"] = {} - table["position"]["lat"] = lat - table["position"]["lng"] = lng - table["position"]["alt"] = alt - table["speed"] = mist.vec.mag(unit:getVelocity()) - table["heading"] = heading - table["country"] = unit:getCountry() - table["isAlive"] = (unit:getLife() > 1) and unit:isExist() - - units[unit:getObjectID()] = table - Olympus.unitNames[unit:getObjectID()] = unit:getName() -- Used to find what units are dead, since they will not be in mist.DBs.groupsByName - end - end - end - end - end - if index >= endIndex then - break - end - end - if index ~= endIndex then - Olympus.groupIndex = 0 - else - Olympus.groupIndex = endIndex - end - end - - -- All the units that can't be retrieved by getByName are dead - for ID, name in pairs(Olympus.unitNames) do - local unit = Unit.getByName(name) - if unit == nil then - units[ID] = {isAlive = false} - Olympus.unitNames[ID] = nil - end - end - - -- Assemble unitsData table - Olympus.unitsData["units"] = units - - Olympus.OlympusDLL.setUnitsData() - return time + 0.05 -end - -function Olympus.setMissionData(arg, time) - -- Bullseye data - local bullseyes = {} - for i = 0, 2 do - local bullseyeVec3 = coalition.getMainRefPoint(i) - local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3) - bullseyes[i] = {} - bullseyes[i]["latitude"] = bullseyeLatitude - bullseyes[i]["longitude"] = bullseyeLongitude - bullseyes[i]["coalition"] = Olympus.getCoalitionByCoalitionID(i) - end - - -- Airbases data - local base = world.getAirbases() - local airbases = {} - for i = 1, #base do - local info = {} - local latitude, longitude, altitude = coord.LOtoLL(Airbase.getPoint(base[i])) - info["callsign"] = Airbase.getCallsign(base[i]) - info["coalition"] = Olympus.getCoalitionByCoalitionID(Airbase.getCoalition(base[i])) - info["latitude"] = latitude - info["longitude"] = longitude - if Airbase.getUnit(base[i]) then - info["unitId"] = Airbase.getUnit(base[i]):getID() - end - airbases[i] = info - end - - -- Mission - local mission = {} - mission.theatre = env.mission.theatre - mission.dateAndTime = { - ["elapsedTime"] = DCS.getRealTime() - Olympus.missionStartTime, - ["time"] = mist.time.getDHMS(timer.getAbsTime()), - ["startTime"] = env.mission.start_time, - ["date"] = env.mission.date - } - - -- Assemble table - Olympus.missionData["bullseyes"] = bullseyes - Olympus.missionData["airbases"] = airbases - Olympus.missionData["mission"] = mission - - Olympus.OlympusDLL.setMissionData() - return time + 1 -end +------------------------------------------------------------------------------------------------------ +-- Olympus startup script +------------------------------------------------------------------------------------------------------ local OlympusName = 'Olympus ' .. version .. ' C++ module'; -isOlympusModuleInitialized=true; Olympus.DLLsloaded = Olympus.loadDLLs() if Olympus.DLLsloaded then Olympus.notify(OlympusName..' successfully loaded.', 20) @@ -884,7 +888,32 @@ else Olympus.notify('Failed to load '..OlympusName, 20) end +-- Create the handler to detect new units +if handler ~= nil then + world.removeEventHandler(handler) + Olympus.debug("Olympus handler removed" , 2) +end +handler = {} +function handler:onEvent(event) + Olympus.debug(Olympus.serializeTable(event), 2) + if event.id == 1 then + local weapon = event.weapon + Olympus.units[weapon["id_"]] = weapon + Olympus.debug("New weapon created " .. weapon["id_"], 2) + elseif event.id == 15 then + local unit = event.initiator + Olympus.units[unit["id_"]] = unit + Olympus.debug("New unit created " .. unit["id_"], 2) + end +end +world.addEventHandler(handler) + +-- Start the periodic functions timer.scheduleFunction(Olympus.setUnitsData, {}, timer.getTime() + 0.05) timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1) +-- Initialize the ME units +Olympus.initializeUnits() + Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true) + From 0150ae9df12032abb84828543e2a1b80345db660 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 27 Jul 2023 16:27:59 +0200 Subject: [PATCH 2/4] Splitted weapons and units managers --- client/demo.js | 49 +- client/src/@types/unit.d.ts | 2 +- client/src/atc/atcboard.ts | 2 +- client/src/atc/unitdatatable.ts | 2 +- .../src/controls/coalitionareacontextmenu.ts | 2 +- client/src/controls/mapcontextmenu.ts | 8 +- client/src/index.ts | 10 +- client/src/map/map.ts | 2 +- client/src/mission/missionhandler.ts | 8 +- client/src/other/utils.ts | 8 +- client/src/panels/hotgrouppanel.ts | 2 +- client/src/panels/mouseinfopanel.ts | 2 +- client/src/panels/unitcontrolpanel.ts | 4 +- client/src/panels/unitinfopanel.ts | 4 +- client/src/{units => server}/dataextractor.ts | 0 client/src/server/server.ts | 24 +- .../src/{units => unit}/aircraftdatabase.ts | 8008 ++++++++--------- client/src/{units => unit}/citiesDatabase.ts | 0 .../src/{units => unit}/groundunitdatabase.ts | 0 .../src/{units => unit}/helicopterdatabase.ts | 1242 +-- .../src/{units => unit}/navyunitdatabase.ts | 0 client/src/{units => unit}/unit.ts | 81 +- client/src/{units => unit}/unitdatabase.ts | 0 client/src/{units => unit}/unitsmanager.ts | 8 +- client/src/weapon/weapon.ts | 323 + client/src/weapon/weaponsmanager.ts | 84 + scripts/OlympusCommand.lua | 143 +- src/core/include/datatypes.h | 67 + src/core/include/unit.h | 66 - src/core/include/weapon.h | 102 +- src/core/include/weaponsmanager.h | 21 + src/core/src/core.cpp | 32 +- src/core/src/server.cpp | 14 +- src/core/src/unitsmanager.cpp | 2 +- src/core/src/weapon.cpp | 91 +- src/core/src/weaponsmanager.cpp | 65 + src/olympus/src/olympus.cpp | 20 + src/shared/include/defines.h | 1 + 38 files changed, 5616 insertions(+), 4883 deletions(-) rename client/src/{units => server}/dataextractor.ts (100%) rename client/src/{units => unit}/aircraftdatabase.ts (97%) rename client/src/{units => unit}/citiesDatabase.ts (100%) rename client/src/{units => unit}/groundunitdatabase.ts (100%) rename client/src/{units => unit}/helicopterdatabase.ts (97%) rename client/src/{units => unit}/navyunitdatabase.ts (100%) rename client/src/{units => unit}/unit.ts (93%) rename client/src/{units => unit}/unitdatabase.ts (100%) rename client/src/{units => unit}/unitsmanager.ts (99%) create mode 100644 client/src/weapon/weapon.ts create mode 100644 client/src/weapon/weaponsmanager.ts create mode 100644 src/core/include/weaponsmanager.h create mode 100644 src/core/src/weaponsmanager.cpp diff --git a/client/demo.js b/client/demo.js index 9560bedf..11cd10a4 100644 --- a/client/demo.js +++ b/client/demo.js @@ -33,22 +33,7 @@ const DEMO_UNIT_DATA = { ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], contacts: [{ID: 1, detectionMethod: 16}], activePath: [ ] - }, ["3"]:{ category: "Missile", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "", unitName: "Cool guy 1-3", groupName: "Cool group 3", state: 1, task: "Being cool", - hasTask: false, position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, - desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0, - formationOffset: { x: 0, y: 0, z: 0 }, - targetID: 0, - targetPosition: { lat: 0, lng: 0, alt: 0 }, - ROE: 2, - reactionToThreat: 1, - emissionsCountermeasures: 1, - TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 }, - radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, - generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, - ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], - contacts: [{ID: 1, detectionMethod: 16}], - activePath: [ ] - }, ["4"]:{ category: "Helicopter", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "AH-64D_BLK_II", unitName: "Cool guy 1-4", groupName: "Cool group 3", state: 1, task: "Being cool", + }, ["3"]:{ category: "Helicopter", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "AH-64D_BLK_II", unitName: "Cool guy 1-4", groupName: "Cool group 3", state: 1, task: "Being cool", hasTask: false, position: { lat: 37.1, 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,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: [ ] - }, ["5"]:{ 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: 1, 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 }, @@ -76,10 +61,10 @@ const DEMO_UNIT_DATA = { radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ], - contacts: [{ID: 1, detectionMethod: 16}], + contacts: [{ID: 1001, detectionMethod: 16}], activePath: [ ], isLeader: true - }, ["6"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "Gepard", unitName: "Cool guy 2-2", groupName: "Cool group 4", state: 1, task: "Being cool", + }, ["5"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, 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 }, @@ -92,16 +77,21 @@ const DEMO_UNIT_DATA = { radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], - contacts: [{ID: 1, detectionMethod: 16}], + contacts: [], activePath: [ ], isLeader: false } } +const DEMO_WEAPONS_DATA = { + ["1001"]:{ category: "Missile", alive: true, coalition: 2, name: "", position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 45 * Math.PI / 180 }, +} + class DemoDataGenerator { constructor(app) { app.get('/demo/units', (req, res) => this.units(req, res)); + app.get('/demo/weapons', (req, res) => this.weapons(req, res)); app.get('/demo/logs', (req, res) => this.logs(req, res)); app.get('/demo/bullseyes', (req, res) => this.bullseyes(req, res)); app.get('/demo/airbases', (req, res) => this.airbases(req, res)); @@ -168,6 +158,25 @@ class DemoDataGenerator { res.end(Buffer.from(array, 'binary')); }; + weapons(req, res){ + var array = new Uint8Array(); + var time = Date.now(); + array = this.concat(array, this.uint64ToByteArray(BigInt(time))); + for (let idx in DEMO_WEAPONS_DATA) { + const weapon = DEMO_WEAPONS_DATA[idx]; + array = this.concat(array, this.uint32ToByteArray(idx)); + array = this.appendString(array, weapon.category, 1); + array = this.appendUint8(array, weapon.alive, 2); + array = this.appendUint16(array, weapon.coalition, 5); + array = this.appendString(array, weapon.name, 7); + array = this.appendCoordinates(array, weapon.position, 13); + array = this.appendDouble(array, weapon.speed, 14); + array = this.appendDouble(array, weapon.heading, 15); + array = this.concat(array, this.uint8ToByteArray(255)); + } + res.end(Buffer.from(array, 'binary')); + }; + concat(array1, array2) { var mergedArray = new Uint8Array(array1.length + array2.length); mergedArray.set(array1); diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index 4026b637..94bb0e7d 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -1,6 +1,6 @@ import { LatLng } from "leaflet" -interface UnitIconOptions { +interface ObjectIconOptions { showState: boolean, showVvi: boolean, showHotgroup: boolean, diff --git a/client/src/atc/atcboard.ts b/client/src/atc/atcboard.ts index 8ec4fd69..1c4f31b3 100644 --- a/client/src/atc/atcboard.ts +++ b/client/src/atc/atcboard.ts @@ -1,7 +1,7 @@ import { Dropdown } from "../controls/dropdown"; import { zeroAppend } from "../other/utils"; import { ATC } from "./atc"; -import { Unit } from "../units/unit"; +import { Unit } from "../unit/unit"; import { getMissionHandler, getUnitsManager } from ".."; import Sortable from "sortablejs"; import { FlightInterface } from "./atc"; diff --git a/client/src/atc/unitdatatable.ts b/client/src/atc/unitdatatable.ts index 54b0674e..ea28c44e 100644 --- a/client/src/atc/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -1,6 +1,6 @@ import { getUnitsManager } from ".."; import { Panel } from "../panels/panel"; -import { Unit } from "../units/unit"; +import { Unit } from "../unit/unit"; export class UnitDataTable extends Panel { constructor(id: string) { diff --git a/client/src/controls/coalitionareacontextmenu.ts b/client/src/controls/coalitionareacontextmenu.ts index f7f36585..b15bf325 100644 --- a/client/src/controls/coalitionareacontextmenu.ts +++ b/client/src/controls/coalitionareacontextmenu.ts @@ -6,7 +6,7 @@ import { ContextMenu } from "./contextmenu"; import { Dropdown } from "./dropdown"; import { Slider } from "./slider"; import { Switch } from "./switch"; -import { groundUnitDatabase } from "../units/groundunitdatabase"; +import { groundUnitDatabase } from "../unit/groundunitdatabase"; import { createCheckboxOption, getCheckboxOptions } from "../other/utils"; export class CoalitionAreaContextMenu extends ContextMenu { diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 9a362d42..5d472fc9 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -1,16 +1,16 @@ import { LatLng } from "leaflet"; import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from ".."; import { spawnExplosion, spawnSmoke } from "../server/server"; -import { aircraftDatabase } from "../units/aircraftdatabase"; -import { groundUnitDatabase } from "../units/groundunitdatabase"; -import { helicopterDatabase } from "../units/helicopterdatabase"; +import { aircraftDatabase } from "../unit/aircraftdatabase"; +import { groundUnitDatabase } from "../unit/groundunitdatabase"; +import { helicopterDatabase } from "../unit/helicopterdatabase"; import { ContextMenu } from "./contextmenu"; import { Dropdown } from "./dropdown"; import { Switch } from "./switch"; import { Slider } from "./slider"; import { ftToM } from "../other/utils"; import { GAME_MASTER } from "../constants/constants"; -import { navyUnitDatabase } from "../units/navyunitdatabase"; +import { navyUnitDatabase } from "../unit/navyunitdatabase"; import { CoalitionArea } from "../map/coalitionarea"; export class MapContextMenu extends ContextMenu { diff --git a/client/src/index.ts b/client/src/index.ts index 1a935ff9..fb53c5a5 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -1,5 +1,5 @@ import { Map } from "./map/map" -import { UnitsManager } from "./units/unitsmanager"; +import { UnitsManager } from "./unit/unitsmanager"; import { UnitInfoPanel } from "./panels/unitinfopanel"; import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; import { MissionHandler } from "./mission/missionhandler"; @@ -18,10 +18,12 @@ import { HotgroupPanel } from "./panels/hotgrouppanel"; import { SVGInjector } from "@tanem/svg-injector"; import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants"; import { ServerStatusPanel } from "./panels/serverstatuspanel"; +import { WeaponsManager } from "./weapon/weaponsmanager"; var map: Map; var unitsManager: UnitsManager; +var weaponsManager: WeaponsManager; var missionHandler: MissionHandler; var aic: AIC; @@ -48,6 +50,7 @@ function setup() { /* Initialize base functionalitites */ unitsManager = new UnitsManager(); + weaponsManager = new WeaponsManager(); map = new Map('map-container'); missionHandler = new MissionHandler(); @@ -222,6 +225,11 @@ export function getUnitsManager() { return unitsManager; } +export function getWeaponsManager() { + return weaponsManager; +} + + export function getMissionHandler() { return missionHandler; } diff --git a/client/src/map/map.ts b/client/src/map/map.ts index da49ea3d..30803c55 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -6,7 +6,7 @@ import { UnitContextMenu } from "../controls/unitcontextmenu"; import { AirbaseContextMenu } from "../controls/airbasecontextmenu"; import { Dropdown } from "../controls/dropdown"; import { Airbase } from "../mission/airbase"; -import { Unit } from "../units/unit"; +import { Unit } from "../unit/unit"; import { bearing, createCheckboxOption } from "../other/utils"; import { DestinationPreviewMarker } from "./destinationpreviewmarker"; import { TemporaryUnitMarker } from "./temporaryunitmarker"; diff --git a/client/src/mission/missionhandler.ts b/client/src/mission/missionhandler.ts index a8648670..163b2295 100644 --- a/client/src/mission/missionhandler.ts +++ b/client/src/mission/missionhandler.ts @@ -5,11 +5,11 @@ import { Bullseye } from "./bullseye"; import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "../constants/constants"; import { setCommandModeOptions } from "../server/server"; import { Dropdown } from "../controls/dropdown"; -import { groundUnitDatabase } from "../units/groundunitdatabase"; +import { groundUnitDatabase } from "../unit/groundunitdatabase"; import { createCheckboxOption, getCheckboxOptions } from "../other/utils"; -import { aircraftDatabase } from "../units/aircraftdatabase"; -import { helicopterDatabase } from "../units/helicopterdatabase"; -import { navyUnitDatabase } from "../units/navyunitdatabase"; +import { aircraftDatabase } from "../unit/aircraftdatabase"; +import { helicopterDatabase } from "../unit/helicopterdatabase"; +import { navyUnitDatabase } from "../unit/navyunitdatabase"; export class MissionHandler { #bullseyes: { [name: string]: Bullseye } = {}; diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index 374673c8..ac9b5e1d 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -1,9 +1,9 @@ import { LatLng, Point, Polygon } from "leaflet"; import * as turf from "@turf/turf"; -import { UnitDatabase } from "../units/unitdatabase"; -import { aircraftDatabase } from "../units/aircraftdatabase"; -import { helicopterDatabase } from "../units/helicopterdatabase"; -import { groundUnitDatabase } from "../units/groundunitdatabase"; +import { UnitDatabase } from "../unit/unitdatabase"; +import { aircraftDatabase } from "../unit/aircraftdatabase"; +import { helicopterDatabase } from "../unit/helicopterdatabase"; +import { groundUnitDatabase } from "../unit/groundunitdatabase"; import { Buffer } from "buffer"; import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants"; import { Dropdown } from "../controls/dropdown"; diff --git a/client/src/panels/hotgrouppanel.ts b/client/src/panels/hotgrouppanel.ts index f0abdb85..5af828e6 100644 --- a/client/src/panels/hotgrouppanel.ts +++ b/client/src/panels/hotgrouppanel.ts @@ -1,5 +1,5 @@ import { getUnitsManager } from ".."; -import { Unit } from "../units/unit"; +import { Unit } from "../unit/unit"; import { Panel } from "./panel"; export class HotgroupPanel extends Panel { diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index d1beeaef..923cca9e 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -1,7 +1,7 @@ import { Icon, LatLng, Marker, Polyline } from "leaflet"; import { getMap, getMissionHandler, getUnitsManager } from ".."; import { distance, bearing, zeroAppend, mToNm, nmToFt } from "../other/utils"; -import { Unit } from "../units/unit"; +import { Unit } from "../unit/unit"; import { Panel } from "./panel"; import formatcoords from "formatcoords"; diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 1232ce45..821fc6a2 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -2,8 +2,8 @@ import { SVGInjector } from "@tanem/svg-injector"; import { getUnitsManager } from ".."; import { Dropdown } from "../controls/dropdown"; import { Slider } from "../controls/slider"; -import { aircraftDatabase } from "../units/aircraftdatabase"; -import { Unit } from "../units/unit"; +import { aircraftDatabase } from "../unit/aircraftdatabase"; +import { Unit } from "../unit/unit"; import { Panel } from "./panel"; import { Switch } from "../controls/switch"; import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants"; diff --git a/client/src/panels/unitinfopanel.ts b/client/src/panels/unitinfopanel.ts index cedb32bc..2672bce2 100644 --- a/client/src/panels/unitinfopanel.ts +++ b/client/src/panels/unitinfopanel.ts @@ -1,8 +1,8 @@ import { getUnitsManager } from ".."; import { Ammo } from "../@types/unit"; import { ConvertDDToDMS, rad2deg } from "../other/utils"; -import { aircraftDatabase } from "../units/aircraftdatabase"; -import { Unit } from "../units/unit"; +import { aircraftDatabase } from "../unit/aircraftdatabase"; +import { Unit } from "../unit/unit"; import { Panel } from "./panel"; export class UnitInfoPanel extends Panel { diff --git a/client/src/units/dataextractor.ts b/client/src/server/dataextractor.ts similarity index 100% rename from client/src/units/dataextractor.ts rename to client/src/server/dataextractor.ts diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 0dbd2734..6abcd9a5 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -1,5 +1,5 @@ import { LatLng } from 'leaflet'; -import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitDataTable, getUnitsManager, setLoginStatus } from '..'; +import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitsManager, getWeaponsManager, setLoginStatus } from '..'; import { GeneralSettings, Radio, TACAN } from '../@types/unit'; import { ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants'; @@ -9,6 +9,7 @@ var paused: boolean = false; var REST_ADDRESS = "http://localhost:30000/olympus"; var DEMO_ADDRESS = window.location.href + "demo"; const UNITS_URI = "units"; +const WEAPONS_URI = "weapons"; const LOGS_URI = "logs"; const AIRBASES_URI = "airbases"; const BULLSEYE_URI = "bullseyes"; @@ -128,6 +129,10 @@ export function getUnits(callback: CallableFunction, refresh: boolean = false) { GET(callback, UNITS_URI, { time: refresh ? 0 : lastUpdateTimes[UNITS_URI] }, 'arraybuffer'); } +export function getWeapons(callback: CallableFunction, refresh: boolean = false) { + GET(callback, WEAPONS_URI, { time: refresh ? 0 : lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer'); +} + export function addDestination(ID: number, path: any) { var command = { "ID": ID, "path": path } var data = { "setPath": command } @@ -383,6 +388,15 @@ export function startUpdate() { } }, 250); + window.setInterval(() => { + if (!getPaused()) { + getWeapons((buffer: ArrayBuffer) => { + var time = getWeaponsManager()?.update(buffer); + return time; + }, false); + } + }, 250); + window.setInterval(() => { if (!getPaused()) { getUnits((buffer: ArrayBuffer) => { @@ -394,14 +408,6 @@ export function startUpdate() { }, 5000); } -export function requestUpdate() { - /* Main update rate = 250ms is minimum time, equal to server update time. */ - if (!getPaused()) { - getUnits((buffer: ArrayBuffer) => { return getUnitsManager()?.update(buffer); }, false); - } - window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000); -} - export function checkSessionHash(newSessionHash: string) { if (sessionHash != null) { if (newSessionHash != sessionHash) diff --git a/client/src/units/aircraftdatabase.ts b/client/src/unit/aircraftdatabase.ts similarity index 97% rename from client/src/units/aircraftdatabase.ts rename to client/src/unit/aircraftdatabase.ts index 049e64c3..e9293f92 100644 --- a/client/src/units/aircraftdatabase.ts +++ b/client/src/unit/aircraftdatabase.ts @@ -1,4004 +1,4004 @@ -import { getMissionHandler } from ".."; -import { GAME_MASTER } from "../constants/constants"; -import { UnitDatabase } from "./unitdatabase" - -export class AircraftDatabase extends UnitDatabase { - constructor() { - super(); - - this.blueprints = { - "A-10C_2": { - "name": "A-10C_2", - "coalition": "blue", - "era": "Late Cold War", - "label": "A-10C Warthog", - "shortLabel": "10", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Mk-84", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 1 - }, - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "Mk-82", - "quantity": 6 - } - ], - "roles": [ - "CAS" - ], - "code": "Mk-82*6,Mk-84*2,AIM-9*2,ECM", - "name": "Heavy / Mk-84 / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "AGM-65D", - "quantity": 4 - }, - { - "name": "CBU-97", - "quantity": 4 - }, - { - "name": "TGP", - "quantity": 1 - }, - { - "name": "ECM", - "quantity": 1 - }, - { - "name": "AIM-9", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "AGM-65D*4, CBU-97*4,TGP, ECM, AIM-9*2", - "name": "Heavy / AGM-65D / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "GBU-12", - "quantity": 6 - }, - { - "name": "GBU-10", - "quantity": 2 - }, - { - "name": "TGP", - "quantity": 1 - }, - { - "name": "AIM-9", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "GBU-12*6,GBU-10*2,TGP, AIM-9*2", - "name": "Heavy / GBU-12 / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "a-10.png" - }, - "AJS37": { - "name": "AJS37", - "coalition": "blue", - "label": "AJS37 Viggen", - "era": "Mid Cold War", - "shortLabel": "37", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "BK90", - "quantity": 2 - }, - { - "name": "RB-74", - "quantity": 2 - }, - { - "name": "XT", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "Strike: BK90 (MJ1)*2, RB-74*2, XT", - "name": "Heavy / BK90 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "ARAK M70 HE", - "quantity": 4 - }, - { - "name": "XT", - "quantity": 1 - } - ], - "roles": [ - "CAS" - ], - "code": "CAS: ARAK M70 HE*4, XT", - "name": "Heavy / ARAK M79 HE / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "RB05", - "quantity": 2 - }, - { - "name": "RB74", - "quantity": 2 - }, - { - "name": "XT", - "quantity": 1 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "Anti-ship (RB05): RB-05A*2, RB-74*2, XT", - "name": "Heavy / RB05 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "viggen.png" - }, - "AV8BNA": { - "name": "AV8BNA", - "coalition": "blue", - "label": "AV8BNA Harrier", - "era": "Late Cold War", - "shortLabel": "8", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "GBU-38", - "quantity": 2 - }, - { - "name": "AIM-9M", - "quantity": 1 - }, - { - "name": "AGM-122 Sidearm", - "quantity": 1 - }, - { - "name": "Fuel 300", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "H-M-H 3", - "name": "Heavy / GBU-38 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "AGM-65F", - "quantity": 4 - }, - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "GAU-12", - "quantity": 1 - } - ], - "roles": [ - "CAS" - ], - "code": "Anti Armor", - "name": "Heavy / AGM-65F / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "av8bna.png" - }, - "C-101CC": { - "name": "C-101CC", - "coalition": "blue", - "label": "C-101CC", - "era": "Late Cold War", - "shortLabel": "101", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "DEFA 553 CANNON (I)", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "2*AIM-9M, DEFA 553 CANNON (I)", - "name": "Light / Fox 2 / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "BELOUGA", - "quantity": 2 - }, - { - "name": "BIN-200", - "quantity": 2 - }, - { - "name": "AN-M3 CANNON", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "2*AIM-9M ,2*BELOUGA,2*BIN-200, AN-M3 CANNON", - "name": "Heavy / BELOUGA, BIN-200 / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "c-101.png" - }, - "H-6J": { - "name": "H-6J", - "coalition": "red", - "label": "H-6J Badger", - "era": "Mid Cold War", - "shortLabel": "H6", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "250-3 LD Bomb", - "quantity": 36 - } - ], - "roles": [ - "Strike" - ], - "code": "250-3 LD Bomb x 36", - "name": "Heavy / Bombs / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "KD-20", - "quantity": 4 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "KD-20 x 4", - "name": "Heavy / KD-20 / Long Range" - } - ], - "filename": "h-6.png" - }, - "J-11A": { - "name": "J-11A", - "coalition": "red", - "label": "J-11A Flaming Dragon", - "era": "Modern", - "shortLabel": "11", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "FAB-500", - "quantity": 8 - }, - { - "name": "R-73", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-500x8,R-73x2,ECM", - "name": "Heavy / Fox 2 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-77", - "quantity": 2 - }, - { - "name": "R-73", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-77x2, R-73x2", - "name": "Light / Fox 3 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-27ER", - "quantity": 2 - }, - { - "name": "R-73", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-27ERx2, R-73x2", - "name": "Light / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-27.png" - }, - "JF-17": { - "name": "JF-17", - "coalition": "red", - "label": "JF-17 Thunder", - "era": "Modern", - "shortLabel": "17", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "PL-5E2", - "quantity": 2 - }, - { - "name": "C802AK", - "quantity": 2 - }, - { - "name": "800L Tank", - "quantity": 1 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "PL-5Ex2, C802AKx2, 800L Tank", - "name": "Heavy / C802AK ASM / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "PL-5E2", - "quantity": 2 - }, - { - "name": "GBU-12", - "quantity": 2 - }, - { - "name": "800L Tank", - "quantity": 1 - }, - { - "name": "WMD7", - "quantity": 1 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "PL-5Ex2, 2*GBU-12x2, 800L Tank, WMD7", - "name": "Heavy / C802AK ASM / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "PL-5E2", - "quantity": 2 - }, - { - "name": "SD-10", - "quantity": 2 - }, - { - "name": "1100L Tank", - "quantity": 2 - }, - { - "name": "WMD7", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "PL-5Ex2, SD-10x2, 1100L Tankx2, WMD7", - "name": "Heavy / Fox 3 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "jf-17.png" - }, - "F-16C_50": { - "name": "F-16C_50", - "coalition": "blue", - "label": "F-16C Viper", - "era": "Late Cold War", - "shortLabel": "16", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 3 - }, - { - "name": "AIM-120C", - "quantity": 2 - }, - { - "name": "AIM-9X", - "quantity": 4 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-120C*2, AIM-9X*4, FUEL*2", - "name": "Heavy / Fox 3 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-120C", - "quantity": 2 - }, - { - "name": "AIM-9X", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 1 - }, - { - "name": "TGP", - "quantity": 1 - }, - { - "name": "AGM-65D", - "quantity": 4 - } - ], - "roles": [ - "CAS" - ], - "code": "AIM-120C*2, AIM-9X*2, AGM-65D*4, FUEL*2, ECM, TGP", - "name": "Heavy / Fox 3, AGM-65D / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-120C", - "quantity": 2 - }, - { - "name": "AIM-9X", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 1 - }, - { - "name": "TGP", - "quantity": 1 - }, - { - "name": "GBU-10", - "quantity": 4 - } - ], - "roles": [ - "Strike" - ], - "code": "AIM-120C*2, AIM-9X*2, GBU-10*2, FUEL*2, ECM, TGP", - "name": "Heavy / Fox 3, GBU-10 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-16c.png" - }, - "F-5E-3": { - "name": "F-5E-3", - "coalition": "blue", - "label": "F-5E Tiger", - "era": "Mid Cold War", - "shortLabel": "5", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Fuel 275", - "quantity": 3 - }, - { - "name": "AIM-9P5", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-9P5*2, Fuel 275*3", - "name": "Heavy / Fox 2 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "Mk-82", - "quantity": 4 - }, - { - "name": "AIM-9P5", - "quantity": 2 - }, - { - "name": "Fuel 275", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "Mk-82LD*4,AIM-9P*2,Fuel 275", - "name": "Heavy / Fox 2 / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-5.png" - }, - "F-86F Sabre": { - "name": "F-86F Sabre", - "coalition": "blue", - "label": "F-86F Sabre", - "era": "Early Cold War", - "shortLabel": "86", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "120gal Fuel", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "120gal Fuel*2", - "name": "Light / Guns / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "HVAR", - "quantity": 16 - } - ], - "roles": [ - "CAS" - ], - "code": "HVAR*16", - "name": "Light / HVAR / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "AN-M64", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "AN-M64*2", - "name": "Light / AN-M64 / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Light / Guns / Short Range" - } - ], - "filename": "f-5.png" - }, - "F-14A-135-GR": { - "name": "F-14A-135-GR", - "coalition": "blue", - "label": "F-14A-135-GR Tomcat", - "era": "Mid Cold War", - "shortLabel": "14A", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-54A", - "quantity": 2 - }, - { - "name": "AIM-7F", - "quantity": 1 - }, - { - "name": "AIM-9L", - "quantity": 4 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-54A-MK47*2, AIM-7F*1, AIM-9L*4, XT*2", - "name": "Heavy / Fox 3 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-7F", - "quantity": 4 - }, - { - "name": "AIM-9L", - "quantity": 4 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-7F*4, AIM-9L*4, XT*2", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-7M", - "quantity": 1 - }, - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "GBU-12", - "quantity": 2 - }, - { - "name": "LANTIRN", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "AIM-7M*1, AIM-9M*2, XT*2, GBU-12*2, LANTIRN", - "name": "Heavy / Fox 3, GBU-12 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-14.png" - }, - "F-14B": { - "name": "F-14B", - "coalition": "blue", - "label": "F-14B Tomcat", - "era": "Late Cold War", - "shortLabel": "14B", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-54C", - "quantity": 2 - }, - { - "name": "AIM-7M", - "quantity": 3 - }, - { - "name": "AIM-9M", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-54C-MK47*2, AIM-7M*3, AIM-9M*2, XT*2", - "name": "Heavy / Fox 3 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-7M", - "quantity": 6 - }, - { - "name": "AIM-9M", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-7M*6, AIM-9M*2, XT*2", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-7M", - "quantity": 1 - }, - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "GBU-12", - "quantity": 2 - }, - { - "name": "LANTIRN", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "AIM-7M*1, AIM-9M*2, XT*2, GBU-12*2, LANTIRN", - "name": "Heavy / Fox 3, GBU-12 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-14.png" - }, - "FA-18C_hornet": { - "name": "FA-18C_hornet", - "coalition": "blue", - "era": "Late Cold War", - "label": "F/A-18C", - "shortLabel": "18", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 3 - }, - { - "name": "AIM-120C-5", - "quantity": 6 - }, - { - "name": "AIM-9X", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-9X*2, AIM-120C-5*6, FUEL*3", - "name": "Heavy / Fox 3 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 3 - }, - { - "name": "AIM-7M", - "quantity": 4 - }, - { - "name": "AIM-9M", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-9M*2, AIM-7M*4, FUEL*3", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 1 - }, - { - "name": "AIM-120C-5", - "quantity": 1 - }, - { - "name": "AIM-9X", - "quantity": 2 - }, - { - "name": "AGM-88C", - "quantity": 2 - } - ], - "roles": [ - "SEAD" - ], - "code": "AIM-9X*2, AIM-120C-5*2, AGM-88C*2, FUEL", - "name": "Heavy / Fox 3, AGM-88C / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 1 - }, - { - "name": "AIM-120C-5", - "quantity": 1 - }, - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "AGM-84D", - "quantity": 4 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "AIM-9M*2, AIM-120C-5*1, AGM-84D*4, ATFLIR, FUEL", - "name": "Heavy / Fox 3, AGM-84D / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 1 - }, - { - "name": "AIM-120C-5", - "quantity": 1 - }, - { - "name": "AIM-9X", - "quantity": 2 - }, - { - "name": "GBU-12", - "quantity": 4 - }, - { - "name": "GBU-38", - "quantity": 4 - } - ], - "roles": [ - "Strike" - ], - "code": "AIM-9X*2, AIM-120C-5*1, GBU-38*4, GBU-12*4, ATFLIR, FUEL", - "name": "Heavy / Fox 3, GBU-12, GBU-38 / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "fa-18c.png" - }, - "I-16": { - "name": "I-16", - "coalition": "", - "label": "I-16", - "era": "WW2", - "shortLabel": "I16", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "i-16.png" - }, - "L-39ZA": { - "name": "L-39ZA", - "coalition": "red", - "label": "L-39ZA", - "era": "Mid Cold War", - "shortLabel": "39", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "S-5KO", - "quantity": 32 - } - ], - "roles": [ - "CAS" - ], - "code": "S-5KOx32", - "name": "Heavy / S-5KO / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "FAB-100", - "quantity": 4 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-100x4", - "name": "Heavy / FAB-100 / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-60M", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-60Mx2", - "name": "Light / Fox 2 / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "l-39.png" - }, - "M-2000C": { - "name": "M-2000C", - "coalition": "blue", - "label": "M-2000C Mirage", - "era": "Late Cold War", - "shortLabel": "M2KC", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Matra Magic II", - "quantity": 2 - }, - { - "name": "Super 530D", - "quantity": 2 - }, - { - "name": "Eclair", - "quantity": 1 - }, - { - "name": "fuel", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "Fox / S530D / Magic / Eclair", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "Matra Magic II", - "quantity": 2 - }, - { - "name": "Mk82", - "quantity": 4 - }, - { - "name": "fuel", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "Kilo / 4xMk-82 / Magic", - "name": "Heavy / Mk 82 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "Matra Magic II", - "quantity": 2 - }, - { - "name": "BAP-100", - "quantity": 18 - }, - { - "name": "fuel", - "quantity": 2 - } - ], - "roles": [ - "Runway Strike" - ], - "code": "Bravo / BAP-100 / Magic", - "name": "Heavy / BAP-100 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "m2000.png" - }, - "MB-339A": { - "name": "MB-339A", - "coalition": "blue", - "label": "MB-339A", - "era": "Mid Cold War", - "shortLabel": "339A", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "320L TipTanks", - "quantity": 2 - }, - { - "name": "DEFA 553 GunPods", - "quantity": 2 - }, - { - "name": "Mk83", - "quantity": 2 - }, - { - "name": "Mk81", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "A - 2*320L TipTanks + 2*DEFA-553 GunPods + 2*Mk.83 + 2*Mk.81 ", - "name": "Heavy / Mk81, Mk83 / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "320L TipTanks", - "quantity": 2 - }, - { - "name": "DEFA GunPods", - "quantity": 2 - }, - { - "name": "LAU-10(Zuni Rockets)", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "AA - 2*320L TipTanks + 2*DEFA-553 GunPods + 2*LAU-10(Zuni Rockets) [ARMADA]", - "name": "Heavy / Mk 82 / Medium Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "c-101.png" - }, - "MiG-19P": { - "name": "MiG-19P", - "coalition": "red", - "label": "MiG-19 Farmer", - "era": "Early Cold War", - "shortLabel": "19", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "K-13A Atoll", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "K-13A x 2", - "name": "Light / Fox-2 / Short range" - }, - { - "fuel": 1, - "items": [ - { - "name": "K-13A Atoll", - "quantity": 2 - }, - { - "name": "167 gal tanks", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "K-13A x 2, PTB-760 x 2", - "name": "Medium / Fox-2 / Medium range" - }, - { - "fuel": 1, - "items": [ - { - "name": "FAB-250", - "quantity": 2 - }, - { - "name": "ORO-57K", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-250 x 2, ORO-57K x 2", - "name": "Medium / FAB250, ORO57K / Short range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Light / Guns / Short range" - } - ], - "filename": "mig-19.png" - }, - "MiG-21Bis": { - "name": "MiG-21Bis", - "coalition": "red", - "label": "MiG-21 Fishbed", - "era": "Mid Cold War", - "shortLabel": "21", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-3 Atoll", - "quantity": 2 - }, - { - "name": "R-60 Aphid", - "quantity": 2 - }, - { - "name": "130 gal tanks", - "quantity": 1 - }, - { - "name": "ASO-2 Countermeasures", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "Patrol, short range", - "name": "Light / Fox-2 / Short range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-3 Atoll", - "quantity": 2 - }, - { - "name": "R-60 Aphid", - "quantity": 2 - }, - { - "name": "210 gal tanks", - "quantity": 1 - }, - { - "name": "ASO-2 Countermeasures", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "Patrol, medium range", - "name": "Medium / Fox-2 / Medium range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-3R Atoll", - "quantity": 2 - }, - { - "name": "210 gal tanks", - "quantity": 1 - }, - { - "name": "ASO-2 Countermeasures", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "Patrol, R-3R Only", - "name": "Medium / Fox-1 / Medium range" - }, - { - "fuel": 1, - "items": [ - { - "name": "GROM", - "quantity": 2 - }, - { - "name": "FAB-250", - "quantity": 2 - }, - { - "name": "210 gal tanks", - "quantity": 1 - }, - { - "name": "ASO-2 Countermeasures", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "Few big targets, GROM + BOMBS", - "name": "Heavy / GROM, FAB250 / Medium range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mig-21.png" - }, - "Mirage-F1EE": { - "name": "Mirage-F1EE", - "coalition": "blue", - "label": "Mirage-F1EE", - "era": "Mid Cold War", - "shortLabel": "F1EE", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "AIM-9JULI", - "quantity": 2 - }, - { - "name": "R530EM", - "quantity": 2 - }, - { - "name": "1137L Fuel Tank", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "2*AIM9-JULI, 2*R530EM, 1*Fuel Tank", - "name": "Medium / Fox 1 / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "AIM-9JULI", - "quantity": 2 - }, - { - "name": "SAMP 400 LD", - "quantity": 8 - } - ], - "roles": [ - "Strike" - ], - "code": "2*AIM-9JULI, 8*SAMP 400 LD", - "name": "Heavy / SAMP400 / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-5.png" - }, - "A-20G": { - "name": "A-20G", - "coalition": "", - "label": "A-20G Havoc", - "era": "WW2", - "shortLabel": "A20", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "12.7mm M2 HMG", - "quantity": 6 - }, - { - "name": "500lb Bomb LD", - "quantity": 4 - } - ], - "roles": [ - "Strike" - ], - "code": "500 lb GP bomb LD*4", - "name": "Medium / Bombs / Medium Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "a-20.png" - }, - "Bf-109K-4": { - "name": "Bf-109K-4", - "coalition": "", - "label": "Bf-109K-4 Fritz", - "era": "WW2", - "shortLabel": "109", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "30mm MK108 Gun", - "quantity": 1 - }, - { - "name": "13mm MG131 Gun", - "quantity": 2 - }, - { - "name": "SC500", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "500 lb GP bomb LD*4", - "name": "Medium / Bombs / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "30mm MK108 Gun", - "quantity": 1 - }, - { - "name": "13mm MG131 Gun", - "quantity": 2 - }, - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Light / Guns / Short Range" - } - ], - "filename": "bf109.png" - }, - "FW-190A8": { - "name": "FW-190A8", - "coalition": "", - "label": "FW-190A8 Bosch", - "era": "WW2", - "shortLabel": "190A8", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "20mm MG151 Gun", - "quantity": 4 - }, - { - "name": "13mm MG131 Gun", - "quantity": 2 - }, - { - "name": "SD500", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "SD 500 A", - "name": "Medium / Bombs / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "20mm MG151 Gun", - "quantity": 4 - }, - { - "name": "13mm MG131 Gun", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Light / Guns / Short Range" - } - ], - "filename": "fw190.png" - }, - "FW-190D9": { - "name": "FW-190D9", - "coalition": "", - "label": "FW-190D9 Jerry", - "era": "WW2", - "shortLabel": "190D9", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "20mm MG151 Gun", - "quantity": 4 - }, - { - "name": "13mm MG131 Gun", - "quantity": 2 - }, - { - "name": "SC500", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "SD 500 A", - "name": "Medium / Bombs / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "20mm MG151 Gun", - "quantity": 4 - }, - { - "name": "13mm MG131 Gun", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Light / Guns / Short Range" - } - ], - "filename": "fw190.png" - }, - "MosquitoFBMkVI": { - "name": "MosquitoFBMkVI", - "coalition": "", - "label": "Mosquito FB MkVI", - "era": "WW2", - "shortLabel": "Mosquito", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "20mm Hispano Gun", - "quantity": 4 - }, - { - "name": "7.7mm MG", - "quantity": 4 - }, - { - "name": "500 lb GP Mk.V", - "quantity": 2 - }, - { - "name": "500 lb GP Short tail", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "500 lb GP Mk.V*2, 500 lb GP Short tail*2", - "name": "Medium / Bombs / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "20mm Hispano Gun", - "quantity": 4 - }, - { - "name": "7.7mm MG", - "quantity": 4 - } - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Light / Guns / Medium Range" - } - ], - "filename": "mosquito.png" - }, - "P-47D-40": { - "name": "P-47D-40", - "coalition": "", - "label": "P-47D Thunderbolt", - "era": "WW2", - "shortLabel": "P47", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "12.7mm HMG", - "quantity": 8 - }, - { - "name": "AN-M65", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "AN-M65*2", - "name": "Medium / Bombs / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "12.7mm HMG", - "quantity": 8 - } - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Light / Guns / Medium Range" - } - ], - "filename": "p-47.png" - }, - "P-51D-30-NA": { - "name": "P-51D-30-NA", - "coalition": "", - "label": "P-51D Mustang", - "era": "WW2", - "shortLabel": "P51", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "12.7mm HMG", - "quantity": 6 - }, - { - "name": "HVAR", - "quantity": 10 - } - ], - "roles": [ - "Strike" - ], - "code": "HVAR*10", - "name": "Medium / Rockets / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "12.7mm HMG", - "quantity": 6 - } - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Light / Guns / Medium Range" - } - ], - "filename": "p-51.png" - }, - "A-50": { - "name": "A-50", - "coalition": "red", - "label": "A-50 Mainstay", - "era": "Late Cold War", - "shortLabel": "A50", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "AWACS" - ], - "code": "", - "name": "Default AWACS" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "a-50.png" - }, - "An-26B": { - "name": "An-26B", - "coalition": "red", - "label": "An-26B Curl", - "era": "Mid Cold War", - "shortLabel": "26", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Transport" - ], - "code": "", - "name": "Default Transport" - } - ], - "filename": "an-26.png" - }, - "An-30M": { - "name": "An-30M", - "coalition": "red", - "label": "An-30M Clank", - "era": "Mid Cold War", - "shortLabel": "30", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Reconnaissance" - ], - "code": "", - "name": "Default Reconnaissance" - } - ], - "filename": "a-50.png" - }, - "B-1B": { - "name": "B-1B", - "coalition": "blue", - "label": "B-1B Lancer", - "era": "Late Cold War", - "shortLabel": "1", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Mk-84", - "quantity": 24 - } - ], - "roles": [ - "Strike" - ], - "code": "Mk-84*24", - "name": "Heavy / Mk-84 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "b-1.png" - }, - "B-52H": { - "name": "B-52H", - "coalition": "blue", - "label": "B-52H Stratofortress", - "era": "Early Cold War", - "shortLabel": "52", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Mk-84", - "quantity": 18 - } - ], - "roles": [ - "Strike" - ], - "code": "Mk-84*18", - "name": "Heavy / Mk-84 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "b-52.png" - }, - "C-130": { - "name": "C-130", - "coalition": "blue", - "label": "C-130 Hercules", - "era": "Early Cold War", - "shortLabel": "130", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Transport" - ], - "code": "C-130", - "name": "Default Transport" - } - ], - "filename": "c-130.png" - }, - "C-17A": { - "name": "C-17A", - "coalition": "blue", - "label": "C-17A Globemaster", - "era": "Modern", - "shortLabel": "C17", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Transport" - ], - "code": "", - "name": "Default Transport" - } - ], - "filename": "c-17.png" - }, - "E-3A": { - "name": "E-3A", - "coalition": "blue", - "label": "E-3A Sentry", - "era": "Mid Cold War", - "shortLabel": "E3", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "AWACS" - ], - "code": "", - "name": "Blue Air Force AWACS" - } - ], - "filename": "e-3.png" - }, - "E-2C": { - "name": "E-2C", - "coalition": "blue", - "label": "E-2C Hawkeye", - "era": "Mid Cold War", - "shortLabel": "2C", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "AWACS" - ], - "code": "", - "name": "Blue Navy AWACS" - } - ], - "filename": "e-2.png" - }, - "F-117A": { - "name": "F-117A", - "coalition": "blue", - "label": "F-117A Nighthawk", - "era": "Late Cold War", - "shortLabel": "117", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "GBU-10", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "GBU-10*2", - "name": "Heavy / GBU-10 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-117.png" - }, - "F-15C": { - "name": "F-15C", - "coalition": "blue", - "label": "F-15C Eagle", - "era": "Late Cold War", - "shortLabel": "15", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 3 - }, - { - "name": "AIM-120B", - "quantity": 6 - }, - { - "name": "AIM-9M", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-9*2,AIM-120*6,Fuel*3", - "name": "Heavy / Fox 3 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 3 - }, - { - "name": "AIM-7", - "quantity": 4 - }, - { - "name": "AIM-9M", - "quantity": 4 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-9*4,AIM-7*4,Fuel", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-15.png" - }, - "F-15E": { - "name": "F-15E", - "coalition": "blue", - "label": "F-15E Strike Eagle", - "era": "Late Cold War", - "shortLabel": "15", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 3 - }, - { - "name": "AIM-120B", - "quantity": 2 - }, - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "Mk-84", - "quantity": 8 - } - ], - "roles": [ - "CAS" - ], - "code": "AIM-120B*2,AIM-9M*2,FUEL*3,Mk-84*8", - "name": "Heavy / Fox 3, Mk-84 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 1 - }, - { - "name": "AIM-120B", - "quantity": 2 - }, - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "GBU-12", - "quantity": 4 - }, - { - "name": "GBU-38", - "quantity": 4 - }, - { - "name": "AGM-154C", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "AIM-120B*2,AIM-9M*2,FUEL,GBU-12*4,GBU-38*4,AGM-154C*2", - "name": "Heavy / Fox 3, GBU-12, GBU-38, AGM-154C / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-15.png" - }, - "F-4E": { - "name": "F-4E", - "coalition": "blue", - "label": "F-4E Phantom II", - "era": "Mid Cold War", - "shortLabel": "4", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "AIM-7M", - "quantity": 4 - }, - { - "name": "AIM-9M", - "quantity": 4 - } - ], - "roles": [ - "CAP" - ], - "code": "AIM-9*4,AIM-7*4,Fuel*2", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "ECM", - "quantity": 1 - }, - { - "name": "AIM-7M", - "quantity": 2 - }, - { - "name": "Mk-82", - "quantity": 18 - } - ], - "roles": [ - "Strike" - ], - "code": "Mk-82*18,AIM-7*2,ECM", - "name": "Heavy / Fox 1, Mk-82 / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "ECM", - "quantity": 1 - }, - { - "name": "AIM-7M", - "quantity": 2 - }, - { - "name": "AGM-65K", - "quantity": 4 - }, - { - "name": "Fuel", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "AGM-65K*4,AIM-7*2,Fuel*2,ECM", - "name": "Heavy / Fox 1, AGM-65K / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "f-4.png" - }, - "IL-76MD": { - "name": "IL-76MD", - "coalition": "red", - "label": "IL-76MD Candid", - "era": "Mid Cold War", - "shortLabel": "76", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Transport" - ], - "code": "", - "name": "Default Transport" - } - ], - "filename": "il-76.png" - }, - "IL-78M": { - "name": "IL-78M", - "coalition": "red", - "label": "IL-78M Midas", - "era": "Late Cold War", - "shortLabel": "78", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Tanker" - ], - "code": "", - "name": "Default Tanker" - } - ], - "filename": "il-76.png" - }, - "KC-135": { - "name": "KC-135", - "coalition": "blue", - "label": "KC-135 Stratotanker", - "era": "Early Cold War", - "shortLabel": "135", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Tanker" - ], - "code": "", - "name": "Default Tanker" - } - ], - "filename": "kc-135.png" - }, - "KC135MPRS": { - "name": "KC135MPRS", - "coalition": "blue", - "label": "KC-135 MPRS Stratotanker", - "era": "Early Cold War", - "shortLabel": "135M", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Tanker" - ], - "code": "", - "name": "Default Tanker" - } - ], - "filename": "kc-135.png" - }, - "S-3B Tanker": { - "name": "S-3B Tanker", - "coalition": "blue", - "label": "S-3B Tanker", - "era": "Early Cold War", - "shortLabel": "S3B", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Tanker" - ], - "code": "", - "name": "Default Tanker" - } - ], - "filename": "s-3.png" - }, - "MiG-15bis": { - "name": "MiG-15bis", - "coalition": "red", - "label": "MiG-15 Fagot", - "era": "Early Cold War", - "shortLabel": "M15", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "300L Fuel Tanks", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "2*300L", - "name": "Medium / Guns / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "FAB-100M", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "2*FAB-100M", - "name": "Medium / FAB-100M / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "CAP" - ], - "code": "", - "name": "Light / Guns / Short Range" - }, - ], - "filename": "mig-15.png" - }, - "MiG-23MLD": { - "name": "MiG-23MLD", - "coalition": "red", - "label": "MiG-23 Flogger", - "era": "Mid Cold War", - "shortLabel": "23", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Fuel-800", - "quantity": 1 - }, - { - "name": "R-60M", - "quantity": 4 - }, - { - "name": "R-24R", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-24R*2,R-60M*4,Fuel-800", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "Fuel-800", - "quantity": 1 - }, - { - "name": "FAB-500", - "quantity": 2 - }, - { - "name": "R-60M", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-500*2,R-60M*2,Fuel-800", - "name": "Heavy / FAB-500 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mig-23.png" - }, - "MiG-25RBT": { - "name": "MiG-25RBT", - "coalition": "red", - "label": "MiG-25RBT Foxbat", - "era": "Mid Cold War", - "shortLabel": "25", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-60M", - "quantity": 2 - } - ], - "roles": [ - "Reconnaissance" - ], - "code": "R-60M*2", - "name": "Heavy / Fox 2 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "FAB-500", - "quantity": 2 - }, - { - "name": "R-60M", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-500x2_60x2", - "name": "Heavy / FAB-500 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mig-25.png" - }, - "MiG-25PD": { - "name": "MiG-25PD", - "coalition": "red", - "label": "MiG-25PD Foxbat", - "era": "Mid Cold War", - "shortLabel": "25", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-40R", - "quantity": 2 - }, - { - "name": "R-60M", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-40R*2,R-60M*2", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mig-25.png" - }, - "MiG-27K": { - "name": "MiG-27K", - "coalition": "red", - "label": "MiG-27K Flogger-D", - "era": "Mid Cold War", - "shortLabel": "27", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "B-8", - "quantity": 4 - } - ], - "roles": [ - "CAS" - ], - "code": "B-8*4", - "name": "Heavy / B-8 / Short Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "Kh-29L", - "quantity": 2 - }, - { - "name": "R-60M", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "Kh-29L*2,R-60M*2,Fuel", - "name": "Heavy / Fox 2, Kh-29L / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "FAB-250", - "quantity": 6 - }, - { - "name": "R-60M", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-250*6,R-60M*2,Fuel", - "name": "Heavy / Fox 2, FAB250 / Medium Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mig-23.png" - }, - "MiG-29A": { - "name": "MiG-29A", - "coalition": "red", - "label": "MiG-29A Fulcrum", - "era": "Late Cold War", - "shortLabel": "29A", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 4 - }, - { - "name": "R-27R", - "quantity": 2 - }, - { - "name": "Fuel-1500", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2,R-27R*2,Fuel-1500", - "name": "Heavy / Fox 1, HOBS Fox 2 / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-60M", - "quantity": 4 - }, - { - "name": "R-27R", - "quantity": 2 - }, - { - "name": "Fuel-1500", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "R-60M*4,R-27R*2,Fuel-1500", - "name": "Heavy / Fox 1 / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "FAB-500", - "quantity": 4 - }, - { - "name": "Fuel-1500", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-500*4,R-73*2,Fuel", - "name": "Heavy / Fox 2, FAB500 / Medium Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mig-29.png" - }, - "MiG-29S": { - "name": "MiG-29S", - "coalition": "red", - "label": "MiG-29S Fulcrum", - "era": "Late Cold War", - "shortLabel": "29", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 4 - }, - { - "name": "R-27R", - "quantity": 2 - }, - { - "name": "Fuel-1500", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2,R-27R*2,Fuel-1500", - "name": "Heavy / Fox 1 / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-60M", - "quantity": 4 - }, - { - "name": "R-27R", - "quantity": 2 - }, - { - "name": "Fuel-1500", - "quantity": 1 - } - ], - "roles": [ - "CAP" - ], - "code": "R-60M*4,R-27R*2", - "name": "Heavy / Fox 1 / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "S-24", - "quantity": 4 - }, - { - "name": "Fuel-1500", - "quantity": 1 - } - ], - "roles": [ - "CAS" - ], - "code": "S-24*4,R-73*2,Fuel", - "name": "Heavy / Fox 2, S-24 / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "FAB-500", - "quantity": 4 - }, - { - "name": "Fuel-1500", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-500*4,R-73*2,Fuel", - "name": "Heavy / Fox 2, FAB500 / Medium Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mig-29.png" - }, - "MiG-31": { - "name": "MiG-31", - "coalition": "red", - "label": "MiG-31 Foxhound", - "era": "Late Cold War", - "shortLabel": "31", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-33", - "quantity": 4 - }, - { - "name": "R-40T", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-40T*2,R-33*4", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mig-23.png" - }, - "MQ-9 Reaper": { - "name": "MQ-9 Reaper", - "coalition": "blue", - "label": "MQ-9 Reaper", - "era": "Modern", - "shortLabel": "9", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "AGM-114K", - "quantity": 12 - } - ], - "roles": [ - "Drone" - ], - "code": "AGM-114K*12", - "name": "Default Drone" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "i-16.png" - }, - "Su-17M4": { - "name": "Su-17M4", - "coalition": "red", - "label": "Su-17M4 Fitter", - "era": "Mid Cold War", - "shortLabel": "17M4", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-60M", - "quantity": 2 - }, - { - "name": "B-8", - "quantity": 4 - }, - { - "name": "fuel", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "B-8*4,R-60M*2,Fuel*2", - "name": "Heavy / B-8 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-17.png" - }, - "Su-24M": { - "name": "Su-24M", - "coalition": "red", - "label": "Su-24M Fencer", - "era": "Mid Cold War", - "shortLabel": "24", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-60M", - "quantity": 2 - }, - { - "name": "FAB-1500", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-1500*2,R-60M*2", - "name": "Heavy / FAB-500 / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-24.png" - }, - "Su-25": { - "name": "Su-25", - "coalition": "red", - "label": "Su-25A Frogfoot", - "era": "Late Cold War", - "shortLabel": "S25", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-60M", - "quantity": 2 - }, - { - "name": "UB-13", - "quantity": 6 - }, - { - "name": "fuel", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "UB-13*6,R-60M*2,Fuel*2", - "name": "Heavy / Rockets / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-60M", - "quantity": 2 - }, - { - "name": "B-8MI", - "quantity": 2 - }, - { - "name": "RBK-500", - "quantity": 2 - }, - { - "name": "Kh-25ML", - "quantity": 2 - }, - { - "name": "2-25L", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "2-25L*2, KH-25ML*2, RBK-500*2, B-8MI*2, R-60M*2", - "name": "Heavy / Everything A-G / Medium Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-25.png" - }, - "Su-25T": { - "name": "Su-25", - "coalition": "red", - "label": "Su-25T Frogfoot", - "era": "Late Cold War", - "shortLabel": "S25T", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Kh-29L", - "quantity": 2 - }, - { - "name": "Kh-25ML", - "quantity": 4 - }, - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "Mercury LLTV Pod", - "quantity": 1 - }, - { - "name": "MPS-410", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "Kh-29L*2,Kh-25ML*4,R-73*2,Mercury LLTV Pod,MPS-410", - "name": "Heavy / Everything A-G / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "APU-8 Vikhr-M", - "quantity": 2 - }, - { - "name": "Kh-25ML", - "quantity": 2 - }, - { - "name": "SPPU-22*2", - "quantity": 2 - }, - { - "name": "Mercury LLTV Pod", - "quantity": 1 - }, - { - "name": "MPS-410", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "APU-8 Vikhr-M*2,Kh-25ML,R-73*2,SPPU-22*2,Mercury LLTV Pod,MPS-410", - "name": "Heavy / Everything A-G / Medium Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "FAB-500", - "quantity": 6 - }, - { - "name": "R-60M", - "quantity": 2 - }, - { - "name": "Fuel", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-500*6,R-60M*2,Fuel*2", - "name": "Medium / FAB-500 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-25.png" - }, - "Su-27": { - "name": "Su-27", - "coalition": "red", - "label": "Su-27 Flanker", - "era": "Late Cold War", - "shortLabel": "27", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-27ER", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2,R-27ER*2,ECM", - "name": "Light / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-27ER", - "quantity": 2 - }, - { - "name": "R-27ET", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2,R-27ER*2,R-27ET*2,ECM", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-27ET", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2,R-27ET*2,ECM", - "name": "Heavy / Fox 2 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "S-25", - "quantity": 4 - }, - { - "name": "FAB-500", - "quantity": 4 - } - ], - "roles": [ - "Strike" - ], - "code": "S-25*4, FAB-500*4, R-73*2, ECM", - "name": "Heavy / Fox 2, Bombs, Rockets / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-27.png" - }, - "Su-30": { - "name": "Su-30", - "coalition": "red", - "label": "Su-30 Super Flanker", - "era": "Late Cold War", - "shortLabel": "30", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-77", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2,R-77*2", - "name": "Light / Fox 3 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-77", - "quantity": 2 - }, - { - "name": "R-27ER", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2,R-77*2,R-27ER*2,ECM", - "name": "Heavy / Fox 3, Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-77", - "quantity": 2 - }, - { - "name": "FAB-1500", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-1500*2,R-73*2,R-77*2,ECM", - "name": "Heavy / Fox 3, Bombs / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-34.png" - }, - "Su-33": { - "name": "Su-33", - "coalition": "red", - "label": "Su-33 Navy Flanker", - "era": "Late Cold War", - "shortLabel": "33", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-27ER", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2, R-27ER*2", - "name": "Light / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "R-27ER", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 2 - } - ], - "roles": [ - "CAP" - ], - "code": "R-73*2,R-27ET*2,R-27ER*2,ECM", - "name": "Heavy / Fox 1 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "S-25", - "quantity": 4 - }, - { - "name": "FAB-250", - "quantity": 4 - }, - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 2 - } - ], - "roles": [ - "Strike" - ], - "code": "S-25*4,FAB-250*4,R-73*2,ECM", - "name": "Heavy / Rockets, Bombs / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-34.png" - }, - "Su-34": { - "name": "Su-34", - "coalition": "red", - "label": "Su-34 Hellduck", - "era": "Modern", - "shortLabel": "34", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "R-73", - "quantity": 2 - }, - { - "name": "FAB-250", - "quantity": 4 - }, - { - "name": "UB-13", - "quantity": 4 - }, - { - "name": "ECM", - "quantity": 1 - } - ], - "roles": [ - "CAS" - ], - "code": "UB-13*4,FAB-250*4,R-73*2,ECM", - "name": "Heavy / Mixed Ground Ordinance / Short Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "su-34.png" - }, - "Tornado IDS": { - "name": "Tornado IDS", - "coalition": "blue", - "label": "Tornado IDS", - "era": "Late Cold War", - "shortLabel": "IDS", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "Mk-82", - "quantity": 4 - } - ], - "roles": [ - "CAS" - ], - "code": "Mk-82*4,AIM-9*2,Fuel*2", - "name": "Heavy / Mk-84 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "tornado.png" - }, - "Tornado GR4": { - "name": "Tornado GR4", - "coalition": "blue", - "label": "Tornado GR4", - "era": "Late Cold War", - "shortLabel": "GR4", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "ALARM", - "quantity": 4 - }, - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 1 - } - ], - "roles": [ - "SEAD" - ], - "code": "ALARM*4, Fuel*2, ECM", - "name": "Heavy / ALARM / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "GBU-16", - "quantity": 2 - }, - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 1 - } - ], - "roles": [ - "Strike" - ], - "code": "GBU-16*2, AIM-9M*2, Fuel*2, ECM", - "name": "Heavy / GBU-16 / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "AIM-9M", - "quantity": 2 - }, - { - "name": "Sea Eagle", - "quantity": 2 - }, - { - "name": "fuel", - "quantity": 2 - }, - { - "name": "ECM", - "quantity": 1 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "Sea Eagle*2, AIM-9M*2, Fuel*2, ECM", - "name": "Heavy / Sea Eagle / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "tornado.png" - }, - "Tu-142": { - "name": "Tu-142", - "coalition": "red", - "label": "Tu-142 Bear", - "era": "Mid Cold War", - "shortLabel": "142", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Kh-35", - "quantity": 6 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "Kh-35*6", - "name": "Heavy / Kh-35 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "tu-95.png" - }, - "Tu-160": { - "name": "Tu-160", - "coalition": "red", - "label": "Tu-160 Blackjack", - "era": "Late Cold War", - "shortLabel": "160", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Kh-65", - "quantity": 12 - } - ], - "roles": [ - "Strike" - ], - "code": "Kh-65*12", - "name": "Heavy / Kh-65 / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "tu-160.png" - }, - "Tu-22M3": { - "name": "Tu-22M3", - "coalition": "red", - "label": "Tu-22M3 Backfire", - "era": "Late Cold War", - "shortLabel": "T22", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Kh-22n", - "quantity": 2 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "Kh-22N*2", - "name": "Heavy / Kh-22N / Long Range" - }, - { - "fuel": 1, - "items": [ - { - "name": "FAB-250", - "quantity": 69 - } - ], - "roles": [ - "Strike" - ], - "code": "FAB-250*69", - "name": "Heavy / Kh-22n / Long Range" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "tu-22.png" - }, - "Tu-95MS": { - "name": "Tu-95MS", - "coalition": "red", - "label": "Tu-95MS Bear", - "era": "Mid Cold War", - "shortLabel": "95", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Kh-65", - "quantity": 6 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "Kh-65*6", - "name": "Heavy / Kh-65 / Long Range" - }, - { - "fuel": 1, - "items": [ - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "tu-95.png" - } - } - } - - getCategory() { - return "Aircraft"; - } - - getSpawnPointsByName(name: string) { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) - return 0; - - const blueprint = this.getByName(name); - if (blueprint?.era == "WW2") - return 20; - else if (blueprint?.era == "Early Cold War") - return 50; - else if (blueprint?.era == "Mid Cold War") - return 100; - else if (blueprint?.era == "Late Cold War") - return 200; - else if (blueprint?.era == "Modern") - return 400; - return 0; - } -} - -export var aircraftDatabase = new AircraftDatabase(); - +import { getMissionHandler } from ".."; +import { GAME_MASTER } from "../constants/constants"; +import { UnitDatabase } from "./unitdatabase" + +export class AircraftDatabase extends UnitDatabase { + constructor() { + super(); + + this.blueprints = { + "A-10C_2": { + "name": "A-10C_2", + "coalition": "blue", + "era": "Late Cold War", + "label": "A-10C Warthog", + "shortLabel": "10", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Mk-84", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 1 + }, + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "Mk-82", + "quantity": 6 + } + ], + "roles": [ + "CAS" + ], + "code": "Mk-82*6,Mk-84*2,AIM-9*2,ECM", + "name": "Heavy / Mk-84 / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "AGM-65D", + "quantity": 4 + }, + { + "name": "CBU-97", + "quantity": 4 + }, + { + "name": "TGP", + "quantity": 1 + }, + { + "name": "ECM", + "quantity": 1 + }, + { + "name": "AIM-9", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "AGM-65D*4, CBU-97*4,TGP, ECM, AIM-9*2", + "name": "Heavy / AGM-65D / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "GBU-12", + "quantity": 6 + }, + { + "name": "GBU-10", + "quantity": 2 + }, + { + "name": "TGP", + "quantity": 1 + }, + { + "name": "AIM-9", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "GBU-12*6,GBU-10*2,TGP, AIM-9*2", + "name": "Heavy / GBU-12 / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "a-10.png" + }, + "AJS37": { + "name": "AJS37", + "coalition": "blue", + "label": "AJS37 Viggen", + "era": "Mid Cold War", + "shortLabel": "37", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "BK90", + "quantity": 2 + }, + { + "name": "RB-74", + "quantity": 2 + }, + { + "name": "XT", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "Strike: BK90 (MJ1)*2, RB-74*2, XT", + "name": "Heavy / BK90 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "ARAK M70 HE", + "quantity": 4 + }, + { + "name": "XT", + "quantity": 1 + } + ], + "roles": [ + "CAS" + ], + "code": "CAS: ARAK M70 HE*4, XT", + "name": "Heavy / ARAK M79 HE / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "RB05", + "quantity": 2 + }, + { + "name": "RB74", + "quantity": 2 + }, + { + "name": "XT", + "quantity": 1 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "Anti-ship (RB05): RB-05A*2, RB-74*2, XT", + "name": "Heavy / RB05 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "viggen.png" + }, + "AV8BNA": { + "name": "AV8BNA", + "coalition": "blue", + "label": "AV8BNA Harrier", + "era": "Late Cold War", + "shortLabel": "8", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "GBU-38", + "quantity": 2 + }, + { + "name": "AIM-9M", + "quantity": 1 + }, + { + "name": "AGM-122 Sidearm", + "quantity": 1 + }, + { + "name": "Fuel 300", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "H-M-H 3", + "name": "Heavy / GBU-38 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "AGM-65F", + "quantity": 4 + }, + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "GAU-12", + "quantity": 1 + } + ], + "roles": [ + "CAS" + ], + "code": "Anti Armor", + "name": "Heavy / AGM-65F / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "av8bna.png" + }, + "C-101CC": { + "name": "C-101CC", + "coalition": "blue", + "label": "C-101CC", + "era": "Late Cold War", + "shortLabel": "101", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "DEFA 553 CANNON (I)", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "2*AIM-9M, DEFA 553 CANNON (I)", + "name": "Light / Fox 2 / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "BELOUGA", + "quantity": 2 + }, + { + "name": "BIN-200", + "quantity": 2 + }, + { + "name": "AN-M3 CANNON", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "2*AIM-9M ,2*BELOUGA,2*BIN-200, AN-M3 CANNON", + "name": "Heavy / BELOUGA, BIN-200 / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "c-101.png" + }, + "H-6J": { + "name": "H-6J", + "coalition": "red", + "label": "H-6J Badger", + "era": "Mid Cold War", + "shortLabel": "H6", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "250-3 LD Bomb", + "quantity": 36 + } + ], + "roles": [ + "Strike" + ], + "code": "250-3 LD Bomb x 36", + "name": "Heavy / Bombs / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "KD-20", + "quantity": 4 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "KD-20 x 4", + "name": "Heavy / KD-20 / Long Range" + } + ], + "filename": "h-6.png" + }, + "J-11A": { + "name": "J-11A", + "coalition": "red", + "label": "J-11A Flaming Dragon", + "era": "Modern", + "shortLabel": "11", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "FAB-500", + "quantity": 8 + }, + { + "name": "R-73", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-500x8,R-73x2,ECM", + "name": "Heavy / Fox 2 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-77", + "quantity": 2 + }, + { + "name": "R-73", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-77x2, R-73x2", + "name": "Light / Fox 3 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-27ER", + "quantity": 2 + }, + { + "name": "R-73", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-27ERx2, R-73x2", + "name": "Light / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-27.png" + }, + "JF-17": { + "name": "JF-17", + "coalition": "red", + "label": "JF-17 Thunder", + "era": "Modern", + "shortLabel": "17", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "PL-5E2", + "quantity": 2 + }, + { + "name": "C802AK", + "quantity": 2 + }, + { + "name": "800L Tank", + "quantity": 1 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "PL-5Ex2, C802AKx2, 800L Tank", + "name": "Heavy / C802AK ASM / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "PL-5E2", + "quantity": 2 + }, + { + "name": "GBU-12", + "quantity": 2 + }, + { + "name": "800L Tank", + "quantity": 1 + }, + { + "name": "WMD7", + "quantity": 1 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "PL-5Ex2, 2*GBU-12x2, 800L Tank, WMD7", + "name": "Heavy / C802AK ASM / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "PL-5E2", + "quantity": 2 + }, + { + "name": "SD-10", + "quantity": 2 + }, + { + "name": "1100L Tank", + "quantity": 2 + }, + { + "name": "WMD7", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "PL-5Ex2, SD-10x2, 1100L Tankx2, WMD7", + "name": "Heavy / Fox 3 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "jf-17.png" + }, + "F-16C_50": { + "name": "F-16C_50", + "coalition": "blue", + "label": "F-16C Viper", + "era": "Late Cold War", + "shortLabel": "16", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 3 + }, + { + "name": "AIM-120C", + "quantity": 2 + }, + { + "name": "AIM-9X", + "quantity": 4 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-120C*2, AIM-9X*4, FUEL*2", + "name": "Heavy / Fox 3 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-120C", + "quantity": 2 + }, + { + "name": "AIM-9X", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 1 + }, + { + "name": "TGP", + "quantity": 1 + }, + { + "name": "AGM-65D", + "quantity": 4 + } + ], + "roles": [ + "CAS" + ], + "code": "AIM-120C*2, AIM-9X*2, AGM-65D*4, FUEL*2, ECM, TGP", + "name": "Heavy / Fox 3, AGM-65D / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-120C", + "quantity": 2 + }, + { + "name": "AIM-9X", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 1 + }, + { + "name": "TGP", + "quantity": 1 + }, + { + "name": "GBU-10", + "quantity": 4 + } + ], + "roles": [ + "Strike" + ], + "code": "AIM-120C*2, AIM-9X*2, GBU-10*2, FUEL*2, ECM, TGP", + "name": "Heavy / Fox 3, GBU-10 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-16c.png" + }, + "F-5E-3": { + "name": "F-5E-3", + "coalition": "blue", + "label": "F-5E Tiger", + "era": "Mid Cold War", + "shortLabel": "5", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Fuel 275", + "quantity": 3 + }, + { + "name": "AIM-9P5", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-9P5*2, Fuel 275*3", + "name": "Heavy / Fox 2 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "Mk-82", + "quantity": 4 + }, + { + "name": "AIM-9P5", + "quantity": 2 + }, + { + "name": "Fuel 275", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "Mk-82LD*4,AIM-9P*2,Fuel 275", + "name": "Heavy / Fox 2 / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-5.png" + }, + "F-86F Sabre": { + "name": "F-86F Sabre", + "coalition": "blue", + "label": "F-86F Sabre", + "era": "Early Cold War", + "shortLabel": "86", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "120gal Fuel", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "120gal Fuel*2", + "name": "Light / Guns / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "HVAR", + "quantity": 16 + } + ], + "roles": [ + "CAS" + ], + "code": "HVAR*16", + "name": "Light / HVAR / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "AN-M64", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "AN-M64*2", + "name": "Light / AN-M64 / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Light / Guns / Short Range" + } + ], + "filename": "f-5.png" + }, + "F-14A-135-GR": { + "name": "F-14A-135-GR", + "coalition": "blue", + "label": "F-14A-135-GR Tomcat", + "era": "Mid Cold War", + "shortLabel": "14A", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-54A", + "quantity": 2 + }, + { + "name": "AIM-7F", + "quantity": 1 + }, + { + "name": "AIM-9L", + "quantity": 4 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-54A-MK47*2, AIM-7F*1, AIM-9L*4, XT*2", + "name": "Heavy / Fox 3 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-7F", + "quantity": 4 + }, + { + "name": "AIM-9L", + "quantity": 4 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-7F*4, AIM-9L*4, XT*2", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-7M", + "quantity": 1 + }, + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "GBU-12", + "quantity": 2 + }, + { + "name": "LANTIRN", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "AIM-7M*1, AIM-9M*2, XT*2, GBU-12*2, LANTIRN", + "name": "Heavy / Fox 3, GBU-12 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-14.png" + }, + "F-14B": { + "name": "F-14B", + "coalition": "blue", + "label": "F-14B Tomcat", + "era": "Late Cold War", + "shortLabel": "14B", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-54C", + "quantity": 2 + }, + { + "name": "AIM-7M", + "quantity": 3 + }, + { + "name": "AIM-9M", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-54C-MK47*2, AIM-7M*3, AIM-9M*2, XT*2", + "name": "Heavy / Fox 3 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-7M", + "quantity": 6 + }, + { + "name": "AIM-9M", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-7M*6, AIM-9M*2, XT*2", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-7M", + "quantity": 1 + }, + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "GBU-12", + "quantity": 2 + }, + { + "name": "LANTIRN", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "AIM-7M*1, AIM-9M*2, XT*2, GBU-12*2, LANTIRN", + "name": "Heavy / Fox 3, GBU-12 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-14.png" + }, + "FA-18C_hornet": { + "name": "FA-18C_hornet", + "coalition": "blue", + "era": "Late Cold War", + "label": "F/A-18C", + "shortLabel": "18", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 3 + }, + { + "name": "AIM-120C-5", + "quantity": 6 + }, + { + "name": "AIM-9X", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-9X*2, AIM-120C-5*6, FUEL*3", + "name": "Heavy / Fox 3 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 3 + }, + { + "name": "AIM-7M", + "quantity": 4 + }, + { + "name": "AIM-9M", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-9M*2, AIM-7M*4, FUEL*3", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 1 + }, + { + "name": "AIM-120C-5", + "quantity": 1 + }, + { + "name": "AIM-9X", + "quantity": 2 + }, + { + "name": "AGM-88C", + "quantity": 2 + } + ], + "roles": [ + "SEAD" + ], + "code": "AIM-9X*2, AIM-120C-5*2, AGM-88C*2, FUEL", + "name": "Heavy / Fox 3, AGM-88C / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 1 + }, + { + "name": "AIM-120C-5", + "quantity": 1 + }, + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "AGM-84D", + "quantity": 4 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "AIM-9M*2, AIM-120C-5*1, AGM-84D*4, ATFLIR, FUEL", + "name": "Heavy / Fox 3, AGM-84D / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 1 + }, + { + "name": "AIM-120C-5", + "quantity": 1 + }, + { + "name": "AIM-9X", + "quantity": 2 + }, + { + "name": "GBU-12", + "quantity": 4 + }, + { + "name": "GBU-38", + "quantity": 4 + } + ], + "roles": [ + "Strike" + ], + "code": "AIM-9X*2, AIM-120C-5*1, GBU-38*4, GBU-12*4, ATFLIR, FUEL", + "name": "Heavy / Fox 3, GBU-12, GBU-38 / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "fa-18c.png" + }, + "I-16": { + "name": "I-16", + "coalition": "", + "label": "I-16", + "era": "WW2", + "shortLabel": "I16", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "i-16.png" + }, + "L-39ZA": { + "name": "L-39ZA", + "coalition": "red", + "label": "L-39ZA", + "era": "Mid Cold War", + "shortLabel": "39", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "S-5KO", + "quantity": 32 + } + ], + "roles": [ + "CAS" + ], + "code": "S-5KOx32", + "name": "Heavy / S-5KO / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "FAB-100", + "quantity": 4 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-100x4", + "name": "Heavy / FAB-100 / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-60M", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-60Mx2", + "name": "Light / Fox 2 / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "l-39.png" + }, + "M-2000C": { + "name": "M-2000C", + "coalition": "blue", + "label": "M-2000C Mirage", + "era": "Late Cold War", + "shortLabel": "M2KC", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Matra Magic II", + "quantity": 2 + }, + { + "name": "Super 530D", + "quantity": 2 + }, + { + "name": "Eclair", + "quantity": 1 + }, + { + "name": "fuel", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "Fox / S530D / Magic / Eclair", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "Matra Magic II", + "quantity": 2 + }, + { + "name": "Mk82", + "quantity": 4 + }, + { + "name": "fuel", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "Kilo / 4xMk-82 / Magic", + "name": "Heavy / Mk 82 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "Matra Magic II", + "quantity": 2 + }, + { + "name": "BAP-100", + "quantity": 18 + }, + { + "name": "fuel", + "quantity": 2 + } + ], + "roles": [ + "Runway Strike" + ], + "code": "Bravo / BAP-100 / Magic", + "name": "Heavy / BAP-100 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "m2000.png" + }, + "MB-339A": { + "name": "MB-339A", + "coalition": "blue", + "label": "MB-339A", + "era": "Mid Cold War", + "shortLabel": "339A", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "320L TipTanks", + "quantity": 2 + }, + { + "name": "DEFA 553 GunPods", + "quantity": 2 + }, + { + "name": "Mk83", + "quantity": 2 + }, + { + "name": "Mk81", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "A - 2*320L TipTanks + 2*DEFA-553 GunPods + 2*Mk.83 + 2*Mk.81 ", + "name": "Heavy / Mk81, Mk83 / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "320L TipTanks", + "quantity": 2 + }, + { + "name": "DEFA GunPods", + "quantity": 2 + }, + { + "name": "LAU-10(Zuni Rockets)", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "AA - 2*320L TipTanks + 2*DEFA-553 GunPods + 2*LAU-10(Zuni Rockets) [ARMADA]", + "name": "Heavy / Mk 82 / Medium Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "c-101.png" + }, + "MiG-19P": { + "name": "MiG-19P", + "coalition": "red", + "label": "MiG-19 Farmer", + "era": "Early Cold War", + "shortLabel": "19", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "K-13A Atoll", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "K-13A x 2", + "name": "Light / Fox-2 / Short range" + }, + { + "fuel": 1, + "items": [ + { + "name": "K-13A Atoll", + "quantity": 2 + }, + { + "name": "167 gal tanks", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "K-13A x 2, PTB-760 x 2", + "name": "Medium / Fox-2 / Medium range" + }, + { + "fuel": 1, + "items": [ + { + "name": "FAB-250", + "quantity": 2 + }, + { + "name": "ORO-57K", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-250 x 2, ORO-57K x 2", + "name": "Medium / FAB250, ORO57K / Short range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Light / Guns / Short range" + } + ], + "filename": "mig-19.png" + }, + "MiG-21Bis": { + "name": "MiG-21Bis", + "coalition": "red", + "label": "MiG-21 Fishbed", + "era": "Mid Cold War", + "shortLabel": "21", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-3 Atoll", + "quantity": 2 + }, + { + "name": "R-60 Aphid", + "quantity": 2 + }, + { + "name": "130 gal tanks", + "quantity": 1 + }, + { + "name": "ASO-2 Countermeasures", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "Patrol, short range", + "name": "Light / Fox-2 / Short range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-3 Atoll", + "quantity": 2 + }, + { + "name": "R-60 Aphid", + "quantity": 2 + }, + { + "name": "210 gal tanks", + "quantity": 1 + }, + { + "name": "ASO-2 Countermeasures", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "Patrol, medium range", + "name": "Medium / Fox-2 / Medium range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-3R Atoll", + "quantity": 2 + }, + { + "name": "210 gal tanks", + "quantity": 1 + }, + { + "name": "ASO-2 Countermeasures", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "Patrol, R-3R Only", + "name": "Medium / Fox-1 / Medium range" + }, + { + "fuel": 1, + "items": [ + { + "name": "GROM", + "quantity": 2 + }, + { + "name": "FAB-250", + "quantity": 2 + }, + { + "name": "210 gal tanks", + "quantity": 1 + }, + { + "name": "ASO-2 Countermeasures", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "Few big targets, GROM + BOMBS", + "name": "Heavy / GROM, FAB250 / Medium range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mig-21.png" + }, + "Mirage-F1EE": { + "name": "Mirage-F1EE", + "coalition": "blue", + "label": "Mirage-F1EE", + "era": "Mid Cold War", + "shortLabel": "F1EE", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "AIM-9JULI", + "quantity": 2 + }, + { + "name": "R530EM", + "quantity": 2 + }, + { + "name": "1137L Fuel Tank", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "2*AIM9-JULI, 2*R530EM, 1*Fuel Tank", + "name": "Medium / Fox 1 / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "AIM-9JULI", + "quantity": 2 + }, + { + "name": "SAMP 400 LD", + "quantity": 8 + } + ], + "roles": [ + "Strike" + ], + "code": "2*AIM-9JULI, 8*SAMP 400 LD", + "name": "Heavy / SAMP400 / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-5.png" + }, + "A-20G": { + "name": "A-20G", + "coalition": "", + "label": "A-20G Havoc", + "era": "WW2", + "shortLabel": "A20", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "12.7mm M2 HMG", + "quantity": 6 + }, + { + "name": "500lb Bomb LD", + "quantity": 4 + } + ], + "roles": [ + "Strike" + ], + "code": "500 lb GP bomb LD*4", + "name": "Medium / Bombs / Medium Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "a-20.png" + }, + "Bf-109K-4": { + "name": "Bf-109K-4", + "coalition": "", + "label": "Bf-109K-4 Fritz", + "era": "WW2", + "shortLabel": "109", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "30mm MK108 Gun", + "quantity": 1 + }, + { + "name": "13mm MG131 Gun", + "quantity": 2 + }, + { + "name": "SC500", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "500 lb GP bomb LD*4", + "name": "Medium / Bombs / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "30mm MK108 Gun", + "quantity": 1 + }, + { + "name": "13mm MG131 Gun", + "quantity": 2 + }, + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Light / Guns / Short Range" + } + ], + "filename": "bf109.png" + }, + "FW-190A8": { + "name": "FW-190A8", + "coalition": "", + "label": "FW-190A8 Bosch", + "era": "WW2", + "shortLabel": "190A8", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "20mm MG151 Gun", + "quantity": 4 + }, + { + "name": "13mm MG131 Gun", + "quantity": 2 + }, + { + "name": "SD500", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "SD 500 A", + "name": "Medium / Bombs / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "20mm MG151 Gun", + "quantity": 4 + }, + { + "name": "13mm MG131 Gun", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Light / Guns / Short Range" + } + ], + "filename": "fw190.png" + }, + "FW-190D9": { + "name": "FW-190D9", + "coalition": "", + "label": "FW-190D9 Jerry", + "era": "WW2", + "shortLabel": "190D9", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "20mm MG151 Gun", + "quantity": 4 + }, + { + "name": "13mm MG131 Gun", + "quantity": 2 + }, + { + "name": "SC500", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "SD 500 A", + "name": "Medium / Bombs / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "20mm MG151 Gun", + "quantity": 4 + }, + { + "name": "13mm MG131 Gun", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Light / Guns / Short Range" + } + ], + "filename": "fw190.png" + }, + "MosquitoFBMkVI": { + "name": "MosquitoFBMkVI", + "coalition": "", + "label": "Mosquito FB MkVI", + "era": "WW2", + "shortLabel": "Mosquito", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "20mm Hispano Gun", + "quantity": 4 + }, + { + "name": "7.7mm MG", + "quantity": 4 + }, + { + "name": "500 lb GP Mk.V", + "quantity": 2 + }, + { + "name": "500 lb GP Short tail", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "500 lb GP Mk.V*2, 500 lb GP Short tail*2", + "name": "Medium / Bombs / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "20mm Hispano Gun", + "quantity": 4 + }, + { + "name": "7.7mm MG", + "quantity": 4 + } + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Light / Guns / Medium Range" + } + ], + "filename": "mosquito.png" + }, + "P-47D-40": { + "name": "P-47D-40", + "coalition": "", + "label": "P-47D Thunderbolt", + "era": "WW2", + "shortLabel": "P47", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "12.7mm HMG", + "quantity": 8 + }, + { + "name": "AN-M65", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "AN-M65*2", + "name": "Medium / Bombs / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "12.7mm HMG", + "quantity": 8 + } + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Light / Guns / Medium Range" + } + ], + "filename": "p-47.png" + }, + "P-51D-30-NA": { + "name": "P-51D-30-NA", + "coalition": "", + "label": "P-51D Mustang", + "era": "WW2", + "shortLabel": "P51", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "12.7mm HMG", + "quantity": 6 + }, + { + "name": "HVAR", + "quantity": 10 + } + ], + "roles": [ + "Strike" + ], + "code": "HVAR*10", + "name": "Medium / Rockets / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "12.7mm HMG", + "quantity": 6 + } + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Light / Guns / Medium Range" + } + ], + "filename": "p-51.png" + }, + "A-50": { + "name": "A-50", + "coalition": "red", + "label": "A-50 Mainstay", + "era": "Late Cold War", + "shortLabel": "A50", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "AWACS" + ], + "code": "", + "name": "Default AWACS" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "a-50.png" + }, + "An-26B": { + "name": "An-26B", + "coalition": "red", + "label": "An-26B Curl", + "era": "Mid Cold War", + "shortLabel": "26", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Transport" + ], + "code": "", + "name": "Default Transport" + } + ], + "filename": "an-26.png" + }, + "An-30M": { + "name": "An-30M", + "coalition": "red", + "label": "An-30M Clank", + "era": "Mid Cold War", + "shortLabel": "30", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Reconnaissance" + ], + "code": "", + "name": "Default Reconnaissance" + } + ], + "filename": "a-50.png" + }, + "B-1B": { + "name": "B-1B", + "coalition": "blue", + "label": "B-1B Lancer", + "era": "Late Cold War", + "shortLabel": "1", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Mk-84", + "quantity": 24 + } + ], + "roles": [ + "Strike" + ], + "code": "Mk-84*24", + "name": "Heavy / Mk-84 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "b-1.png" + }, + "B-52H": { + "name": "B-52H", + "coalition": "blue", + "label": "B-52H Stratofortress", + "era": "Early Cold War", + "shortLabel": "52", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Mk-84", + "quantity": 18 + } + ], + "roles": [ + "Strike" + ], + "code": "Mk-84*18", + "name": "Heavy / Mk-84 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "b-52.png" + }, + "C-130": { + "name": "C-130", + "coalition": "blue", + "label": "C-130 Hercules", + "era": "Early Cold War", + "shortLabel": "130", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Transport" + ], + "code": "C-130", + "name": "Default Transport" + } + ], + "filename": "c-130.png" + }, + "C-17A": { + "name": "C-17A", + "coalition": "blue", + "label": "C-17A Globemaster", + "era": "Modern", + "shortLabel": "C17", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Transport" + ], + "code": "", + "name": "Default Transport" + } + ], + "filename": "c-17.png" + }, + "E-3A": { + "name": "E-3A", + "coalition": "blue", + "label": "E-3A Sentry", + "era": "Mid Cold War", + "shortLabel": "E3", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "AWACS" + ], + "code": "", + "name": "Blue Air Force AWACS" + } + ], + "filename": "e-3.png" + }, + "E-2C": { + "name": "E-2C", + "coalition": "blue", + "label": "E-2C Hawkeye", + "era": "Mid Cold War", + "shortLabel": "2C", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "AWACS" + ], + "code": "", + "name": "Blue Navy AWACS" + } + ], + "filename": "e-2.png" + }, + "F-117A": { + "name": "F-117A", + "coalition": "blue", + "label": "F-117A Nighthawk", + "era": "Late Cold War", + "shortLabel": "117", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "GBU-10", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "GBU-10*2", + "name": "Heavy / GBU-10 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-117.png" + }, + "F-15C": { + "name": "F-15C", + "coalition": "blue", + "label": "F-15C Eagle", + "era": "Late Cold War", + "shortLabel": "15", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 3 + }, + { + "name": "AIM-120B", + "quantity": 6 + }, + { + "name": "AIM-9M", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-9*2,AIM-120*6,Fuel*3", + "name": "Heavy / Fox 3 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 3 + }, + { + "name": "AIM-7", + "quantity": 4 + }, + { + "name": "AIM-9M", + "quantity": 4 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-9*4,AIM-7*4,Fuel", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-15.png" + }, + "F-15E": { + "name": "F-15E", + "coalition": "blue", + "label": "F-15E Strike Eagle", + "era": "Late Cold War", + "shortLabel": "15", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 3 + }, + { + "name": "AIM-120B", + "quantity": 2 + }, + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "Mk-84", + "quantity": 8 + } + ], + "roles": [ + "CAS" + ], + "code": "AIM-120B*2,AIM-9M*2,FUEL*3,Mk-84*8", + "name": "Heavy / Fox 3, Mk-84 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 1 + }, + { + "name": "AIM-120B", + "quantity": 2 + }, + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "GBU-12", + "quantity": 4 + }, + { + "name": "GBU-38", + "quantity": 4 + }, + { + "name": "AGM-154C", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "AIM-120B*2,AIM-9M*2,FUEL,GBU-12*4,GBU-38*4,AGM-154C*2", + "name": "Heavy / Fox 3, GBU-12, GBU-38, AGM-154C / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-15.png" + }, + "F-4E": { + "name": "F-4E", + "coalition": "blue", + "label": "F-4E Phantom II", + "era": "Mid Cold War", + "shortLabel": "4", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "AIM-7M", + "quantity": 4 + }, + { + "name": "AIM-9M", + "quantity": 4 + } + ], + "roles": [ + "CAP" + ], + "code": "AIM-9*4,AIM-7*4,Fuel*2", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "ECM", + "quantity": 1 + }, + { + "name": "AIM-7M", + "quantity": 2 + }, + { + "name": "Mk-82", + "quantity": 18 + } + ], + "roles": [ + "Strike" + ], + "code": "Mk-82*18,AIM-7*2,ECM", + "name": "Heavy / Fox 1, Mk-82 / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "ECM", + "quantity": 1 + }, + { + "name": "AIM-7M", + "quantity": 2 + }, + { + "name": "AGM-65K", + "quantity": 4 + }, + { + "name": "Fuel", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "AGM-65K*4,AIM-7*2,Fuel*2,ECM", + "name": "Heavy / Fox 1, AGM-65K / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "f-4.png" + }, + "IL-76MD": { + "name": "IL-76MD", + "coalition": "red", + "label": "IL-76MD Candid", + "era": "Mid Cold War", + "shortLabel": "76", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Transport" + ], + "code": "", + "name": "Default Transport" + } + ], + "filename": "il-76.png" + }, + "IL-78M": { + "name": "IL-78M", + "coalition": "red", + "label": "IL-78M Midas", + "era": "Late Cold War", + "shortLabel": "78", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Tanker" + ], + "code": "", + "name": "Default Tanker" + } + ], + "filename": "il-76.png" + }, + "KC-135": { + "name": "KC-135", + "coalition": "blue", + "label": "KC-135 Stratotanker", + "era": "Early Cold War", + "shortLabel": "135", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Tanker" + ], + "code": "", + "name": "Default Tanker" + } + ], + "filename": "kc-135.png" + }, + "KC135MPRS": { + "name": "KC135MPRS", + "coalition": "blue", + "label": "KC-135 MPRS Stratotanker", + "era": "Early Cold War", + "shortLabel": "135M", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Tanker" + ], + "code": "", + "name": "Default Tanker" + } + ], + "filename": "kc-135.png" + }, + "S-3B Tanker": { + "name": "S-3B Tanker", + "coalition": "blue", + "label": "S-3B Tanker", + "era": "Early Cold War", + "shortLabel": "S3B", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Tanker" + ], + "code": "", + "name": "Default Tanker" + } + ], + "filename": "s-3.png" + }, + "MiG-15bis": { + "name": "MiG-15bis", + "coalition": "red", + "label": "MiG-15 Fagot", + "era": "Early Cold War", + "shortLabel": "M15", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "300L Fuel Tanks", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "2*300L", + "name": "Medium / Guns / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "FAB-100M", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "2*FAB-100M", + "name": "Medium / FAB-100M / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "CAP" + ], + "code": "", + "name": "Light / Guns / Short Range" + }, + ], + "filename": "mig-15.png" + }, + "MiG-23MLD": { + "name": "MiG-23MLD", + "coalition": "red", + "label": "MiG-23 Flogger", + "era": "Mid Cold War", + "shortLabel": "23", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Fuel-800", + "quantity": 1 + }, + { + "name": "R-60M", + "quantity": 4 + }, + { + "name": "R-24R", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-24R*2,R-60M*4,Fuel-800", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "Fuel-800", + "quantity": 1 + }, + { + "name": "FAB-500", + "quantity": 2 + }, + { + "name": "R-60M", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-500*2,R-60M*2,Fuel-800", + "name": "Heavy / FAB-500 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mig-23.png" + }, + "MiG-25RBT": { + "name": "MiG-25RBT", + "coalition": "red", + "label": "MiG-25RBT Foxbat", + "era": "Mid Cold War", + "shortLabel": "25", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-60M", + "quantity": 2 + } + ], + "roles": [ + "Reconnaissance" + ], + "code": "R-60M*2", + "name": "Heavy / Fox 2 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "FAB-500", + "quantity": 2 + }, + { + "name": "R-60M", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-500x2_60x2", + "name": "Heavy / FAB-500 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mig-25.png" + }, + "MiG-25PD": { + "name": "MiG-25PD", + "coalition": "red", + "label": "MiG-25PD Foxbat", + "era": "Mid Cold War", + "shortLabel": "25", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-40R", + "quantity": 2 + }, + { + "name": "R-60M", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-40R*2,R-60M*2", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mig-25.png" + }, + "MiG-27K": { + "name": "MiG-27K", + "coalition": "red", + "label": "MiG-27K Flogger-D", + "era": "Mid Cold War", + "shortLabel": "27", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "B-8", + "quantity": 4 + } + ], + "roles": [ + "CAS" + ], + "code": "B-8*4", + "name": "Heavy / B-8 / Short Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "Kh-29L", + "quantity": 2 + }, + { + "name": "R-60M", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "Kh-29L*2,R-60M*2,Fuel", + "name": "Heavy / Fox 2, Kh-29L / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "FAB-250", + "quantity": 6 + }, + { + "name": "R-60M", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-250*6,R-60M*2,Fuel", + "name": "Heavy / Fox 2, FAB250 / Medium Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mig-23.png" + }, + "MiG-29A": { + "name": "MiG-29A", + "coalition": "red", + "label": "MiG-29A Fulcrum", + "era": "Late Cold War", + "shortLabel": "29A", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 4 + }, + { + "name": "R-27R", + "quantity": 2 + }, + { + "name": "Fuel-1500", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2,R-27R*2,Fuel-1500", + "name": "Heavy / Fox 1, HOBS Fox 2 / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-60M", + "quantity": 4 + }, + { + "name": "R-27R", + "quantity": 2 + }, + { + "name": "Fuel-1500", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "R-60M*4,R-27R*2,Fuel-1500", + "name": "Heavy / Fox 1 / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "FAB-500", + "quantity": 4 + }, + { + "name": "Fuel-1500", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-500*4,R-73*2,Fuel", + "name": "Heavy / Fox 2, FAB500 / Medium Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mig-29.png" + }, + "MiG-29S": { + "name": "MiG-29S", + "coalition": "red", + "label": "MiG-29S Fulcrum", + "era": "Late Cold War", + "shortLabel": "29", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 4 + }, + { + "name": "R-27R", + "quantity": 2 + }, + { + "name": "Fuel-1500", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2,R-27R*2,Fuel-1500", + "name": "Heavy / Fox 1 / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-60M", + "quantity": 4 + }, + { + "name": "R-27R", + "quantity": 2 + }, + { + "name": "Fuel-1500", + "quantity": 1 + } + ], + "roles": [ + "CAP" + ], + "code": "R-60M*4,R-27R*2", + "name": "Heavy / Fox 1 / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "S-24", + "quantity": 4 + }, + { + "name": "Fuel-1500", + "quantity": 1 + } + ], + "roles": [ + "CAS" + ], + "code": "S-24*4,R-73*2,Fuel", + "name": "Heavy / Fox 2, S-24 / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "FAB-500", + "quantity": 4 + }, + { + "name": "Fuel-1500", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-500*4,R-73*2,Fuel", + "name": "Heavy / Fox 2, FAB500 / Medium Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mig-29.png" + }, + "MiG-31": { + "name": "MiG-31", + "coalition": "red", + "label": "MiG-31 Foxhound", + "era": "Late Cold War", + "shortLabel": "31", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-33", + "quantity": 4 + }, + { + "name": "R-40T", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-40T*2,R-33*4", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mig-23.png" + }, + "MQ-9 Reaper": { + "name": "MQ-9 Reaper", + "coalition": "blue", + "label": "MQ-9 Reaper", + "era": "Modern", + "shortLabel": "9", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "AGM-114K", + "quantity": 12 + } + ], + "roles": [ + "Drone" + ], + "code": "AGM-114K*12", + "name": "Default Drone" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "i-16.png" + }, + "Su-17M4": { + "name": "Su-17M4", + "coalition": "red", + "label": "Su-17M4 Fitter", + "era": "Mid Cold War", + "shortLabel": "17M4", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-60M", + "quantity": 2 + }, + { + "name": "B-8", + "quantity": 4 + }, + { + "name": "fuel", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "B-8*4,R-60M*2,Fuel*2", + "name": "Heavy / B-8 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-17.png" + }, + "Su-24M": { + "name": "Su-24M", + "coalition": "red", + "label": "Su-24M Fencer", + "era": "Mid Cold War", + "shortLabel": "24", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-60M", + "quantity": 2 + }, + { + "name": "FAB-1500", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-1500*2,R-60M*2", + "name": "Heavy / FAB-500 / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-24.png" + }, + "Su-25": { + "name": "Su-25", + "coalition": "red", + "label": "Su-25A Frogfoot", + "era": "Late Cold War", + "shortLabel": "S25", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-60M", + "quantity": 2 + }, + { + "name": "UB-13", + "quantity": 6 + }, + { + "name": "fuel", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "UB-13*6,R-60M*2,Fuel*2", + "name": "Heavy / Rockets / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-60M", + "quantity": 2 + }, + { + "name": "B-8MI", + "quantity": 2 + }, + { + "name": "RBK-500", + "quantity": 2 + }, + { + "name": "Kh-25ML", + "quantity": 2 + }, + { + "name": "2-25L", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "2-25L*2, KH-25ML*2, RBK-500*2, B-8MI*2, R-60M*2", + "name": "Heavy / Everything A-G / Medium Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-25.png" + }, + "Su-25T": { + "name": "Su-25", + "coalition": "red", + "label": "Su-25T Frogfoot", + "era": "Late Cold War", + "shortLabel": "S25T", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Kh-29L", + "quantity": 2 + }, + { + "name": "Kh-25ML", + "quantity": 4 + }, + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "Mercury LLTV Pod", + "quantity": 1 + }, + { + "name": "MPS-410", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "Kh-29L*2,Kh-25ML*4,R-73*2,Mercury LLTV Pod,MPS-410", + "name": "Heavy / Everything A-G / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "APU-8 Vikhr-M", + "quantity": 2 + }, + { + "name": "Kh-25ML", + "quantity": 2 + }, + { + "name": "SPPU-22*2", + "quantity": 2 + }, + { + "name": "Mercury LLTV Pod", + "quantity": 1 + }, + { + "name": "MPS-410", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "APU-8 Vikhr-M*2,Kh-25ML,R-73*2,SPPU-22*2,Mercury LLTV Pod,MPS-410", + "name": "Heavy / Everything A-G / Medium Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "FAB-500", + "quantity": 6 + }, + { + "name": "R-60M", + "quantity": 2 + }, + { + "name": "Fuel", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-500*6,R-60M*2,Fuel*2", + "name": "Medium / FAB-500 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-25.png" + }, + "Su-27": { + "name": "Su-27", + "coalition": "red", + "label": "Su-27 Flanker", + "era": "Late Cold War", + "shortLabel": "27", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-27ER", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2,R-27ER*2,ECM", + "name": "Light / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-27ER", + "quantity": 2 + }, + { + "name": "R-27ET", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2,R-27ER*2,R-27ET*2,ECM", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-27ET", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2,R-27ET*2,ECM", + "name": "Heavy / Fox 2 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "S-25", + "quantity": 4 + }, + { + "name": "FAB-500", + "quantity": 4 + } + ], + "roles": [ + "Strike" + ], + "code": "S-25*4, FAB-500*4, R-73*2, ECM", + "name": "Heavy / Fox 2, Bombs, Rockets / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-27.png" + }, + "Su-30": { + "name": "Su-30", + "coalition": "red", + "label": "Su-30 Super Flanker", + "era": "Late Cold War", + "shortLabel": "30", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-77", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2,R-77*2", + "name": "Light / Fox 3 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-77", + "quantity": 2 + }, + { + "name": "R-27ER", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2,R-77*2,R-27ER*2,ECM", + "name": "Heavy / Fox 3, Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-77", + "quantity": 2 + }, + { + "name": "FAB-1500", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-1500*2,R-73*2,R-77*2,ECM", + "name": "Heavy / Fox 3, Bombs / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-34.png" + }, + "Su-33": { + "name": "Su-33", + "coalition": "red", + "label": "Su-33 Navy Flanker", + "era": "Late Cold War", + "shortLabel": "33", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-27ER", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2, R-27ER*2", + "name": "Light / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "R-27ER", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 2 + } + ], + "roles": [ + "CAP" + ], + "code": "R-73*2,R-27ET*2,R-27ER*2,ECM", + "name": "Heavy / Fox 1 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "S-25", + "quantity": 4 + }, + { + "name": "FAB-250", + "quantity": 4 + }, + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 2 + } + ], + "roles": [ + "Strike" + ], + "code": "S-25*4,FAB-250*4,R-73*2,ECM", + "name": "Heavy / Rockets, Bombs / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-34.png" + }, + "Su-34": { + "name": "Su-34", + "coalition": "red", + "label": "Su-34 Hellduck", + "era": "Modern", + "shortLabel": "34", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "R-73", + "quantity": 2 + }, + { + "name": "FAB-250", + "quantity": 4 + }, + { + "name": "UB-13", + "quantity": 4 + }, + { + "name": "ECM", + "quantity": 1 + } + ], + "roles": [ + "CAS" + ], + "code": "UB-13*4,FAB-250*4,R-73*2,ECM", + "name": "Heavy / Mixed Ground Ordinance / Short Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "su-34.png" + }, + "Tornado IDS": { + "name": "Tornado IDS", + "coalition": "blue", + "label": "Tornado IDS", + "era": "Late Cold War", + "shortLabel": "IDS", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "Mk-82", + "quantity": 4 + } + ], + "roles": [ + "CAS" + ], + "code": "Mk-82*4,AIM-9*2,Fuel*2", + "name": "Heavy / Mk-84 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "tornado.png" + }, + "Tornado GR4": { + "name": "Tornado GR4", + "coalition": "blue", + "label": "Tornado GR4", + "era": "Late Cold War", + "shortLabel": "GR4", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "ALARM", + "quantity": 4 + }, + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 1 + } + ], + "roles": [ + "SEAD" + ], + "code": "ALARM*4, Fuel*2, ECM", + "name": "Heavy / ALARM / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "GBU-16", + "quantity": 2 + }, + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 1 + } + ], + "roles": [ + "Strike" + ], + "code": "GBU-16*2, AIM-9M*2, Fuel*2, ECM", + "name": "Heavy / GBU-16 / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "AIM-9M", + "quantity": 2 + }, + { + "name": "Sea Eagle", + "quantity": 2 + }, + { + "name": "fuel", + "quantity": 2 + }, + { + "name": "ECM", + "quantity": 1 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "Sea Eagle*2, AIM-9M*2, Fuel*2, ECM", + "name": "Heavy / Sea Eagle / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "tornado.png" + }, + "Tu-142": { + "name": "Tu-142", + "coalition": "red", + "label": "Tu-142 Bear", + "era": "Mid Cold War", + "shortLabel": "142", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Kh-35", + "quantity": 6 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "Kh-35*6", + "name": "Heavy / Kh-35 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "tu-95.png" + }, + "Tu-160": { + "name": "Tu-160", + "coalition": "red", + "label": "Tu-160 Blackjack", + "era": "Late Cold War", + "shortLabel": "160", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Kh-65", + "quantity": 12 + } + ], + "roles": [ + "Strike" + ], + "code": "Kh-65*12", + "name": "Heavy / Kh-65 / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "tu-160.png" + }, + "Tu-22M3": { + "name": "Tu-22M3", + "coalition": "red", + "label": "Tu-22M3 Backfire", + "era": "Late Cold War", + "shortLabel": "T22", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Kh-22n", + "quantity": 2 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "Kh-22N*2", + "name": "Heavy / Kh-22N / Long Range" + }, + { + "fuel": 1, + "items": [ + { + "name": "FAB-250", + "quantity": 69 + } + ], + "roles": [ + "Strike" + ], + "code": "FAB-250*69", + "name": "Heavy / Kh-22n / Long Range" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "tu-22.png" + }, + "Tu-95MS": { + "name": "Tu-95MS", + "coalition": "red", + "label": "Tu-95MS Bear", + "era": "Mid Cold War", + "shortLabel": "95", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Kh-65", + "quantity": 6 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "Kh-65*6", + "name": "Heavy / Kh-65 / Long Range" + }, + { + "fuel": 1, + "items": [ + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "tu-95.png" + } + } + } + + getCategory() { + return "Aircraft"; + } + + getSpawnPointsByName(name: string) { + if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) + return 0; + + const blueprint = this.getByName(name); + if (blueprint?.era == "WW2") + return 20; + else if (blueprint?.era == "Early Cold War") + return 50; + else if (blueprint?.era == "Mid Cold War") + return 100; + else if (blueprint?.era == "Late Cold War") + return 200; + else if (blueprint?.era == "Modern") + return 400; + return 0; + } +} + +export var aircraftDatabase = new AircraftDatabase(); + diff --git a/client/src/units/citiesDatabase.ts b/client/src/unit/citiesDatabase.ts similarity index 100% rename from client/src/units/citiesDatabase.ts rename to client/src/unit/citiesDatabase.ts diff --git a/client/src/units/groundunitdatabase.ts b/client/src/unit/groundunitdatabase.ts similarity index 100% rename from client/src/units/groundunitdatabase.ts rename to client/src/unit/groundunitdatabase.ts diff --git a/client/src/units/helicopterdatabase.ts b/client/src/unit/helicopterdatabase.ts similarity index 97% rename from client/src/units/helicopterdatabase.ts rename to client/src/unit/helicopterdatabase.ts index 18d7a792..4b08916f 100644 --- a/client/src/units/helicopterdatabase.ts +++ b/client/src/unit/helicopterdatabase.ts @@ -1,621 +1,621 @@ -import { getMissionHandler } from ".."; -import { GAME_MASTER } from "../constants/constants"; -import { UnitDatabase } from "./unitdatabase" - -export class HelicopterDatabase extends UnitDatabase { - constructor() { - super(); - - this.blueprints = { - "AH-64D_BLK_II": { - "name": "AH-64D_BLK_II", - "coalition": "blue", - "era": "Modern", - "label": "AH-64D Apache", - "shortLabel": "AH64", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "AGM-114k Hellfire", - "quantity": 8 - }, - { - "name": "M151 Rocket Pod", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "2 * M261: M151 (6PD), 2 * Hellfire station: 4*AGM-114K", - "name": "Gun / ATGM / Rocket" - }, - { - "fuel": 1, - "items": [ - { - "name": "AGM-114K Hellfire", - "quantity": 16 - } - ], - "roles": [ - "CAS" - ], - "code": "4 * Hellfire station: 4*AGM-114K", - "name": "Gun / ATGM" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "ah-64.png" - }, - "Ka-50_3": { - "name": "Ka-50_3", - "coalition": "red", - "era": "Late Cold War", - "label": "Ka-50 Hokum A", - "shortLabel": "K50", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Igla", - "quantity": 4 - } - ], - "roles": [ - "CAP" - ], - "code": "4xIgla", - "name": "Gun / Fox 2" - }, - { - "fuel": 1, - "items": [ - { - "name": "Igla", - "quantity": 4 - }, - { - "name": "S-13", - "quantity": 10 - }, - { - "name": "Kh-25ML", - "quantity": 2 - } - ], - "roles": [ - "Anti-Ship" - ], - "code": "2xKh-25ML, 10xS-13, 4xIgla", - "name": "Gun / ASM / Rockets / Fox 2" - }, - { - "fuel": 1, - "items": [ - { - "name": "Igla", - "quantity": 4 - }, - { - "name": "S-80FP", - "quantity": 40 - }, - { - "name": "Vikhr-M", - "quantity": 12 - } - ], - "roles": [ - "CAS" - ], - "code": "12x9A4172, 40xS-8OFP, 4xIgla", - "name": "Gun / ATGM / Rockets / Fox 2" - }, - { - "fuel": 1, - "items": [ - { - "name": "Igla", - "quantity": 4 - }, - { - "name": "S-80FP", - "quantity": 40 - }, - { - "name": "Vikhr-M", - "quantity": 12 - } - ], - "roles": [ - "CAS" - ], - "code": "12x9A4172, 40xS-8OFP, 4xIgla", - "name": "Gun / ATGM" - }, - { - "fuel": 1, - "items": [ - { - "name": "Igla", - "quantity": 4 - }, - { - "name": "FAB-500", - "quantity": 2 - }, - { - "name": "S-13", - "quantity": 10 - } - ], - "roles": [ - "Strike" - ], - "code": "10xS-13, 2xFAB-500, 4xIgla", - "name": "Gun / Bombs / Rockets / Fox 2" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "ka-50.png" - }, - "Mi-24P": { - "name": "Mi-24P", - "coalition": "red", - "era": "Mid Cold War", - "label": "Mi-24P Hind", - "shortLabel": "Mi24", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "S-8KOM", - "quantity": 40 - }, - { - "name": "9M114 ATGM", - "quantity": 8 - } - ], - "roles": [ - "CAS" - ], - "code": "2xB8V20 (S-8KOM)+8xATGM 9M114", - "name": "Gun / ATGM / Rockets" - }, - { - "fuel": 1, - "items": [ - { - "name": "S-24B", - "quantity": 4 - }, - { - "name": "9M114 ATGM", - "quantity": 4 - } - ], - "roles": [ - "Strike" - ], - "code": "4xS-24B+4xATGM 9M114", - "name": "Gun / ATGM / Rockets" - }, - { - "fuel": 1, - "items": [ - { - "name": "GUV-1 Grenade Launcher", - "quantity": 4 - }, - { - "name": "9M114 ATGM", - "quantity": 4 - } - ], - "roles": [ - "CAS" - ], - "code": "4xGUV-1 AP30+4xATGM 9M114", - "name": "Gun / ATGM / Grenade Launcher" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mi-24.png" - }, - "SA342L": { - "name": "SA342L", - "coalition": "blue", - "era": "Mid Cold War", - "label": "SA342L Gazelle", - "shortLabel": "342", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "20mm Cannon", - "quantity": 1 - }, - { - "name": "SNEB68", - "quantity": 8 - } - ], - "roles": [ - "Recon" - ], - "code": "M621, 8xSNEB68 EAP", - "name": "Gun / ATGM / Rockets" - } - ], - "filename": "sa-342.png" - }, - "SA342M": { - "name": "SA342M", - "coalition": "blue", - "era": "Mid Cold War", - "label": "SA342M Gazelle", - "shortLabel": "342", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "HOT3", - "quantity": 4 - } - ], - "roles": [ - "CAS" - ], - "code": "HOT3x4", - "name": "ATGM" - } - ], - "filename": "sa-342.png" - }, - "SA342Mistral": { - "name": "SA342Mistral", - "coalition": "blue", - "era": "Mid Cold War", - "label": "SA342Mistral Gazelle", - "shortLabel": "342", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "Mistral", - "quantity": 4 - } - ], - "roles": [ - "CAP" - ], - "code": "Mistral x 4", - "name": "Fox 2" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "sa-342.png" - }, - "AH-1W": { - "name": "AH-1W", - "coalition": "blue", - "era": "Mid Cold War", - "label": "AH-1W Cobra", - "shortLabel": "AH1", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "BGM-71 TOW", - "quantity": 8 - }, - { - "name": "Hydra-70 WP", - "quantity": 38 - } - ], - "roles": [ - "CAS" - ], - "code": "8xBGM-71, 38xHYDRA-70 WP", - "name": "TOW / Hydra" - }, - { - "fuel": 1, - "items": [ - { - "name": "Hydra-70", - "quantity": 76 - } - ], - "roles": [ - "CAS" - ], - "code": "76xHYDRA-70", - "name": "Hydra" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "ah-1.png" - }, - "Mi-26": { - "name": "Mi-26", - "coalition": "red", - "era": "Late Cold War", - "label": "Mi-26 Halo", - "shortLabel": "M26", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Transport" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mi-26.png" - }, - "Mi-28N": { - "name": "Mi-28N", - "coalition": "red", - "era": "Modern", - "label": "Mi-28N Havoc", - "shortLabel": "M28", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "9M114 Shturm", - "quantity": 16 - }, - { - "name": "S-8", - "quantity": 40 - } - ], - "roles": [ - "CAS" - ], - "code": "16x9M114, 40xS-8", - "name": "ATGM / S-8" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mi-28.png" - }, - "Mi-8MT": { - "name": "Mi-8MT", - "coalition": "red", - "era": "Mid Cold War", - "label": "Mi-8MT Hip", - "shortLabel": "Mi8", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "UPK", - "quantity": 2 - }, - { - "name": "B8", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "2 x UPK +2 x B8", - "name": "Rockets / Gunpods" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Transport" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "mi-8.png" - }, - "SH-60B": { - "name": "SH-60B", - "coalition": "blue", - "era": "Mid Cold War", - "label": "SH-60B Seahawk", - "shortLabel": "S60", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "AGM-119 ASM", - "quantity": 1 - } - ], - "roles": [ - "CAS" - ], - "code": "AGM-119", - "name": "ASM" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Transport" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "uh-60.png" - }, - "UH-60A": { - "name": "UH-60A", - "coalition": "blue", - "era": "Mid Cold War", - "label": "UH-60A Blackhawk", - "shortLabel": "U60", - "loadouts": [ - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "Transport" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "uh-60.png" - }, - "UH-1H": { - "name": "UH-1H", - "coalition": "blue", - "era": "Early Cold War", - "label": "UH-1H Huey", - "shortLabel": "UH1", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "M134 Minigun", - "quantity": 2 - }, - { - "name": "XM-158", - "quantity": 2 - } - ], - "roles": [ - "CAS" - ], - "code": "M134 Minigun*2, XM158*2", - "name": "Miniguns / XM158" - }, - { - "fuel": 1, - "items": [ - ], - "roles": [ - "Transport" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "uh-1.png" - } - } - } - - getSpawnPointsByName(name: string) { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) - return 0; - - const blueprint = this.getByName(name); - if (blueprint?.era == "WW2") - return 20; - else if (blueprint?.era == "Early Cold War") - return 50; - else if (blueprint?.era == "Mid Cold War") - return 100; - else if (blueprint?.era == "Late Cold War") - return 200; - else if (blueprint?.era == "Modern") - return 400; - return 0; - } - - getCategory() { - return "Helicopter"; - } -} - -export var helicopterDatabase = new HelicopterDatabase(); - +import { getMissionHandler } from ".."; +import { GAME_MASTER } from "../constants/constants"; +import { UnitDatabase } from "./unitdatabase" + +export class HelicopterDatabase extends UnitDatabase { + constructor() { + super(); + + this.blueprints = { + "AH-64D_BLK_II": { + "name": "AH-64D_BLK_II", + "coalition": "blue", + "era": "Modern", + "label": "AH-64D Apache", + "shortLabel": "AH64", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "AGM-114k Hellfire", + "quantity": 8 + }, + { + "name": "M151 Rocket Pod", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "2 * M261: M151 (6PD), 2 * Hellfire station: 4*AGM-114K", + "name": "Gun / ATGM / Rocket" + }, + { + "fuel": 1, + "items": [ + { + "name": "AGM-114K Hellfire", + "quantity": 16 + } + ], + "roles": [ + "CAS" + ], + "code": "4 * Hellfire station: 4*AGM-114K", + "name": "Gun / ATGM" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "ah-64.png" + }, + "Ka-50_3": { + "name": "Ka-50_3", + "coalition": "red", + "era": "Late Cold War", + "label": "Ka-50 Hokum A", + "shortLabel": "K50", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Igla", + "quantity": 4 + } + ], + "roles": [ + "CAP" + ], + "code": "4xIgla", + "name": "Gun / Fox 2" + }, + { + "fuel": 1, + "items": [ + { + "name": "Igla", + "quantity": 4 + }, + { + "name": "S-13", + "quantity": 10 + }, + { + "name": "Kh-25ML", + "quantity": 2 + } + ], + "roles": [ + "Anti-Ship" + ], + "code": "2xKh-25ML, 10xS-13, 4xIgla", + "name": "Gun / ASM / Rockets / Fox 2" + }, + { + "fuel": 1, + "items": [ + { + "name": "Igla", + "quantity": 4 + }, + { + "name": "S-80FP", + "quantity": 40 + }, + { + "name": "Vikhr-M", + "quantity": 12 + } + ], + "roles": [ + "CAS" + ], + "code": "12x9A4172, 40xS-8OFP, 4xIgla", + "name": "Gun / ATGM / Rockets / Fox 2" + }, + { + "fuel": 1, + "items": [ + { + "name": "Igla", + "quantity": 4 + }, + { + "name": "S-80FP", + "quantity": 40 + }, + { + "name": "Vikhr-M", + "quantity": 12 + } + ], + "roles": [ + "CAS" + ], + "code": "12x9A4172, 40xS-8OFP, 4xIgla", + "name": "Gun / ATGM" + }, + { + "fuel": 1, + "items": [ + { + "name": "Igla", + "quantity": 4 + }, + { + "name": "FAB-500", + "quantity": 2 + }, + { + "name": "S-13", + "quantity": 10 + } + ], + "roles": [ + "Strike" + ], + "code": "10xS-13, 2xFAB-500, 4xIgla", + "name": "Gun / Bombs / Rockets / Fox 2" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "ka-50.png" + }, + "Mi-24P": { + "name": "Mi-24P", + "coalition": "red", + "era": "Mid Cold War", + "label": "Mi-24P Hind", + "shortLabel": "Mi24", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "S-8KOM", + "quantity": 40 + }, + { + "name": "9M114 ATGM", + "quantity": 8 + } + ], + "roles": [ + "CAS" + ], + "code": "2xB8V20 (S-8KOM)+8xATGM 9M114", + "name": "Gun / ATGM / Rockets" + }, + { + "fuel": 1, + "items": [ + { + "name": "S-24B", + "quantity": 4 + }, + { + "name": "9M114 ATGM", + "quantity": 4 + } + ], + "roles": [ + "Strike" + ], + "code": "4xS-24B+4xATGM 9M114", + "name": "Gun / ATGM / Rockets" + }, + { + "fuel": 1, + "items": [ + { + "name": "GUV-1 Grenade Launcher", + "quantity": 4 + }, + { + "name": "9M114 ATGM", + "quantity": 4 + } + ], + "roles": [ + "CAS" + ], + "code": "4xGUV-1 AP30+4xATGM 9M114", + "name": "Gun / ATGM / Grenade Launcher" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mi-24.png" + }, + "SA342L": { + "name": "SA342L", + "coalition": "blue", + "era": "Mid Cold War", + "label": "SA342L Gazelle", + "shortLabel": "342", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "20mm Cannon", + "quantity": 1 + }, + { + "name": "SNEB68", + "quantity": 8 + } + ], + "roles": [ + "Recon" + ], + "code": "M621, 8xSNEB68 EAP", + "name": "Gun / ATGM / Rockets" + } + ], + "filename": "sa-342.png" + }, + "SA342M": { + "name": "SA342M", + "coalition": "blue", + "era": "Mid Cold War", + "label": "SA342M Gazelle", + "shortLabel": "342", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "HOT3", + "quantity": 4 + } + ], + "roles": [ + "CAS" + ], + "code": "HOT3x4", + "name": "ATGM" + } + ], + "filename": "sa-342.png" + }, + "SA342Mistral": { + "name": "SA342Mistral", + "coalition": "blue", + "era": "Mid Cold War", + "label": "SA342Mistral Gazelle", + "shortLabel": "342", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "Mistral", + "quantity": 4 + } + ], + "roles": [ + "CAP" + ], + "code": "Mistral x 4", + "name": "Fox 2" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "sa-342.png" + }, + "AH-1W": { + "name": "AH-1W", + "coalition": "blue", + "era": "Mid Cold War", + "label": "AH-1W Cobra", + "shortLabel": "AH1", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "BGM-71 TOW", + "quantity": 8 + }, + { + "name": "Hydra-70 WP", + "quantity": 38 + } + ], + "roles": [ + "CAS" + ], + "code": "8xBGM-71, 38xHYDRA-70 WP", + "name": "TOW / Hydra" + }, + { + "fuel": 1, + "items": [ + { + "name": "Hydra-70", + "quantity": 76 + } + ], + "roles": [ + "CAS" + ], + "code": "76xHYDRA-70", + "name": "Hydra" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "ah-1.png" + }, + "Mi-26": { + "name": "Mi-26", + "coalition": "red", + "era": "Late Cold War", + "label": "Mi-26 Halo", + "shortLabel": "M26", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Transport" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mi-26.png" + }, + "Mi-28N": { + "name": "Mi-28N", + "coalition": "red", + "era": "Modern", + "label": "Mi-28N Havoc", + "shortLabel": "M28", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "9M114 Shturm", + "quantity": 16 + }, + { + "name": "S-8", + "quantity": 40 + } + ], + "roles": [ + "CAS" + ], + "code": "16x9M114, 40xS-8", + "name": "ATGM / S-8" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mi-28.png" + }, + "Mi-8MT": { + "name": "Mi-8MT", + "coalition": "red", + "era": "Mid Cold War", + "label": "Mi-8MT Hip", + "shortLabel": "Mi8", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "UPK", + "quantity": 2 + }, + { + "name": "B8", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "2 x UPK +2 x B8", + "name": "Rockets / Gunpods" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Transport" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "mi-8.png" + }, + "SH-60B": { + "name": "SH-60B", + "coalition": "blue", + "era": "Mid Cold War", + "label": "SH-60B Seahawk", + "shortLabel": "S60", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "AGM-119 ASM", + "quantity": 1 + } + ], + "roles": [ + "CAS" + ], + "code": "AGM-119", + "name": "ASM" + }, + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Transport" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "uh-60.png" + }, + "UH-60A": { + "name": "UH-60A", + "coalition": "blue", + "era": "Mid Cold War", + "label": "UH-60A Blackhawk", + "shortLabel": "U60", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Transport" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "uh-60.png" + }, + "UH-1H": { + "name": "UH-1H", + "coalition": "blue", + "era": "Early Cold War", + "label": "UH-1H Huey", + "shortLabel": "UH1", + "loadouts": [ + { + "fuel": 1, + "items": [ + { + "name": "M134 Minigun", + "quantity": 2 + }, + { + "name": "XM-158", + "quantity": 2 + } + ], + "roles": [ + "CAS" + ], + "code": "M134 Minigun*2, XM158*2", + "name": "Miniguns / XM158" + }, + { + "fuel": 1, + "items": [ + ], + "roles": [ + "Transport" + ], + "code": "", + "name": "Empty Loadout" + } + ], + "filename": "uh-1.png" + } + } + } + + getSpawnPointsByName(name: string) { + if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) + return 0; + + const blueprint = this.getByName(name); + if (blueprint?.era == "WW2") + return 20; + else if (blueprint?.era == "Early Cold War") + return 50; + else if (blueprint?.era == "Mid Cold War") + return 100; + else if (blueprint?.era == "Late Cold War") + return 200; + else if (blueprint?.era == "Modern") + return 400; + return 0; + } + + getCategory() { + return "Helicopter"; + } +} + +export var helicopterDatabase = new HelicopterDatabase(); + diff --git a/client/src/units/navyunitdatabase.ts b/client/src/unit/navyunitdatabase.ts similarity index 100% rename from client/src/units/navyunitdatabase.ts rename to client/src/unit/navyunitdatabase.ts diff --git a/client/src/units/unit.ts b/client/src/unit/unit.ts similarity index 93% rename from client/src/units/unit.ts rename to client/src/unit/unit.ts index 6a77c196..6f6c879f 100644 --- a/client/src/units/unit.ts +++ b/client/src/unit/unit.ts @@ -7,8 +7,8 @@ import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './unitdatabase'; import { TargetMarker } from '../map/targetmarker'; import { BOMBING, CARPET_BOMBING, DLINK, DataIndexes, FIRE_AT_AREA, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants'; -import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, UnitIconOptions } from '../@types/unit'; -import { DataExtractor } from './dataextractor'; +import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions } from '../@types/unit'; +import { DataExtractor } from '../server/dataextractor'; import { groundUnitDatabase } from './groundunitdatabase'; import { navyUnitDatabase } from './navyunitdatabase'; @@ -135,8 +135,6 @@ export class Unit extends CustomMarker { if (type === "GroundUnit") return GroundUnit; if (type === "Aircraft") return Aircraft; if (type === "Helicopter") return Helicopter; - if (type === "Missile") return Missile; - if (type === "Bomb") return Bomb; if (type === "NavyUnit") return NavyUnit; } @@ -297,7 +295,7 @@ export class Unit extends CustomMarker { return getUnitDatabaseByCategory(this.getMarkerCategory()); } - getIconOptions(): UnitIconOptions { + getIconOptions(): ObjectIconOptions { // Default values, overloaded by child classes if needed return { showState: false, @@ -976,7 +974,7 @@ export class Unit extends CustomMarker { if (getMap().getVisibilityOptions()[SHOW_CONTACT_LINES]) { for (let index in this.#contacts) { var contactData = this.#contacts[index]; - var contact = getUnitsManager().getUnitByID(contactData.ID) + var contact = getUnitsManager().getUnitByID(contactData.ID); if (contact != null && contact.getAlive()) { var startLatLng = new LatLng(this.#position.lat, this.#position.lng); var endLatLng: LatLng; @@ -1149,74 +1147,3 @@ export class NavyUnit extends Unit { return blueprint?.type? blueprint.type: ""; } } - -export class Weapon extends Unit { - constructor(ID: number) { - super(ID); - this.setSelectable(false); - } -} - -export class Missile extends Weapon { - constructor(ID: number) { - super(ID); - } - - getCategory() { - return "Missile"; - } - - getMarkerCategory() { - if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)) - return "missile"; - else - return "aircraft"; - } - - getIconOptions() { - return { - showState: false, - showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), - showHotgroup: false, - showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showShortLabel: false, - showFuel: false, - showAmmo: false, - showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), - showCallsign: false, - rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC) - }; - } -} - -export class Bomb extends Weapon { - constructor(ID: number) { - super(ID); - } - - getCategory() { - return "Bomb"; - } - - getMarkerCategory() { - if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)) - return "bomb"; - else - return "aircraft"; - } - - getIconOptions() { - return { - showState: false, - showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), - showHotgroup: false, - showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showShortLabel: false, - showFuel: false, - showAmmo: false, - showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), - showCallsign: false, - rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC) - }; - } -} diff --git a/client/src/units/unitdatabase.ts b/client/src/unit/unitdatabase.ts similarity index 100% rename from client/src/units/unitdatabase.ts rename to client/src/unit/unitdatabase.ts diff --git a/client/src/units/unitsmanager.ts b/client/src/unit/unitsmanager.ts similarity index 99% rename from client/src/units/unitsmanager.ts rename to client/src/unit/unitsmanager.ts index 43650697..28652824 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -6,9 +6,9 @@ import { bearingAndDistanceToLatLng, deg2rad, keyEventWasInInput, latLngToMercat import { CoalitionArea } from "../map/coalitionarea"; import { groundUnitDatabase } from "./groundunitdatabase"; import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT, NONE } from "../constants/constants"; -import { DataExtractor } from "./dataextractor"; +import { DataExtractor } from "../server/dataextractor"; import { Contact } from "../@types/unit"; -import { citiesDatabase } from "./citiesdatabase"; +import { citiesDatabase } from "./citiesDatabase"; import { aircraftDatabase } from "./aircraftdatabase"; import { helicopterDatabase } from "./helicopterdatabase"; import { navyUnitDatabase } from "./navyunitdatabase"; @@ -73,10 +73,6 @@ export class UnitsManager { } } - removeUnit(ID: number) { - - } - update(buffer: ArrayBuffer) { var dataExtractor = new DataExtractor(buffer); var updateTime = Number(dataExtractor.extractUInt64()); diff --git a/client/src/weapon/weapon.ts b/client/src/weapon/weapon.ts new file mode 100644 index 00000000..539881d6 --- /dev/null +++ b/client/src/weapon/weapon.ts @@ -0,0 +1,323 @@ +import { LatLng, DivIcon, Map } from 'leaflet'; +import { getMap, getMissionHandler, getUnitsManager } from '..'; +import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils'; +import { CustomMarker } from '../map/custommarker'; +import { SVGInjector } from '@tanem/svg-injector'; +import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants'; +import { ObjectIconOptions } from '../@types/unit'; +import { DataExtractor } from '../server/dataextractor'; + +export class Weapon extends CustomMarker { + ID: number; + + #alive: boolean = false; + #coalition: string = "neutral"; + #name: string = ""; + #position: LatLng = new LatLng(0, 0, 0); + #speed: number = 0; + #heading: number = 0; + + #hidden: boolean = false; + #detectionMethods: number[] = []; + + getAlive() {return this.#alive}; + getCoalition() {return this.#coalition}; + getName() {return this.#name}; + getPosition() {return this.#position}; + getSpeed() {return this.#speed}; + getHeading() {return this.#heading}; + + static getConstructor(type: string) { + if (type === "Missile") return Missile; + if (type === "Bomb") return Bomb; + } + + constructor(ID: number) { + super(new LatLng(0, 0), { riseOnHover: true, keyboard: false }); + + this.ID = ID; + + /* Deselect units if they are hidden */ + document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { + window.setTimeout(() => { !this.getHidden() }, 300); + }); + + document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { + window.setTimeout(() => { !this.getHidden() }, 300); + }); + + document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => { + this.#updateMarker(); + }); + } + + getCategory() { + // Overloaded by child classes + return ""; + } + + /********************** Unit data *************************/ + setData(dataExtractor: DataExtractor) { + var updateMarker = !getMap().hasLayer(this); + + var datumIndex = 0; + while (datumIndex != DataIndexes.endOfData) { + datumIndex = dataExtractor.extractUInt8(); + switch (datumIndex) { + case DataIndexes.category: dataExtractor.extractString(); break; + case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break; + case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break; + case DataIndexes.name: this.#name = dataExtractor.extractString(); break; + case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break; + case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break; + case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break; + } + } + + if (updateMarker) + this.#updateMarker(); + } + + getData() { + return { + category: this.getCategory(), + ID: this.ID, + alive: this.#alive, + coalition: this.#coalition, + name: this.#name, + position: this.#position, + speed: this.#speed, + heading: this.#heading + } + } + + getMarkerCategory(): string { + return ""; + } + + getIconOptions(): ObjectIconOptions { + // Default values, overloaded by child classes if needed + return { + showState: false, + showVvi: false, + showHotgroup: false, + showUnitIcon: true, + showShortLabel: false, + showFuel: false, + showAmmo: false, + showSummary: true, + showCallsign: true, + rotateToHeading: false + } + } + + setAlive(newAlive: boolean) { + this.#alive = newAlive; + } + + belongsToCommandedCoalition() { + if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition) + return false; + return true; + } + + getType() { + return ""; + } + + /********************** Icon *************************/ + createIcon(): void { + /* Set the icon */ + var icon = new DivIcon({ + className: 'leaflet-unit-icon', + iconAnchor: [25, 25], + iconSize: [50, 50], + }); + this.setIcon(icon); + + var el = document.createElement("div"); + el.classList.add("unit"); + el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); + el.setAttribute("data-coalition", this.#coalition); + + // Generate and append elements depending on active options + // Velocity vector + if (this.getIconOptions().showVvi) { + var vvi = document.createElement("div"); + vvi.classList.add("unit-vvi"); + vvi.toggleAttribute("data-rotate-to-heading"); + el.append(vvi); + } + + // Main icon + if (this.getIconOptions().showUnitIcon) { + var unitIcon = document.createElement("div"); + unitIcon.classList.add("unit-icon"); + var img = document.createElement("img"); + img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`; + img.onload = () => SVGInjector(img); + unitIcon.appendChild(img); + unitIcon.toggleAttribute("data-rotate-to-heading", this.getIconOptions().rotateToHeading); + el.append(unitIcon); + } + + this.getElement()?.appendChild(el); + } + + /********************** Visibility *************************/ + updateVisibility() { + const hiddenUnits = getUnitsManager().getHiddenTypes(); + var hidden = (hiddenUnits.includes(this.getMarkerCategory())) || + (hiddenUnits.includes(this.#coalition)) || + (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0); + + this.setHidden(hidden || !this.#alive); + } + + setHidden(hidden: boolean) { + this.#hidden = hidden; + + /* Add the marker if not present */ + if (!getMap().hasLayer(this) && !this.getHidden()) { + if (getMap().isZooming()) + this.once("zoomend", () => {this.addTo(getMap())}) + else + this.addTo(getMap()); + } + + /* Hide the marker if necessary*/ + if (getMap().hasLayer(this) && this.getHidden()) { + getMap().removeLayer(this); + } + } + + getHidden() { + return this.#hidden; + } + + setDetectionMethods(newDetectionMethods: number[]) { + if (!this.belongsToCommandedCoalition()) { + /* Check if the detection methods of this unit have changed */ + if (this.#detectionMethods.length !== newDetectionMethods.length || this.getDetectionMethods().some(value => !newDetectionMethods.includes(value))) { + /* Force a redraw of the unit to reflect the new status of the detection methods */ + this.setHidden(true); + this.#detectionMethods = newDetectionMethods; + this.updateVisibility(); + } + } + } + + getDetectionMethods() { + return this.#detectionMethods; + } + + /***********************************************/ + onAdd(map: Map): this { + super.onAdd(map); + /* If this is the first time adding this unit to the map, remove the temporary marker */ + getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng)); + return this; + } + + #updateMarker() { + this.updateVisibility(); + + /* Draw the marker */ + if (!this.getHidden()) { + if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) { + this.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); + } + + var element = this.getElement(); + if (element != null) { + /* Draw the velocity vector */ + element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`); + + /* Set dead/alive flag */ + element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive); + + + /* Set altitude and speed */ + if (element.querySelector(".unit-altitude")) + (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.#position.alt as number) / 100)); + if (element.querySelector(".unit-speed")) + (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS"; + + /* Rotate elements according to heading */ + element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { + const headingDeg = rad2deg(this.#heading); + let currentStyle = el.getAttribute("style") || ""; + el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); + }); + } + + /* Set vertical offset for altitude stacking */ + var pos = getMap().latLngToLayerPoint(this.getLatLng()).round(); + this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y); + } + } +} + +export class Missile extends Weapon { + constructor(ID: number) { + super(ID); + } + + getCategory() { + return "Missile"; + } + + getMarkerCategory() { + if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)) + return "missile"; + else + return "aircraft"; + } + + getIconOptions() { + return { + showState: false, + showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), + showHotgroup: false, + showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showShortLabel: false, + showFuel: false, + showAmmo: false, + showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), + showCallsign: false, + rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC) + }; + } +} + +export class Bomb extends Weapon { + constructor(ID: number) { + super(ID); + } + + getCategory() { + return "Bomb"; + } + + getMarkerCategory() { + if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)) + return "bomb"; + else + return "aircraft"; + } + + getIconOptions() { + return { + showState: false, + showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), + showHotgroup: false, + showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showShortLabel: false, + showFuel: false, + showAmmo: false, + showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), + showCallsign: false, + rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC) + }; + } +} diff --git a/client/src/weapon/weaponsmanager.ts b/client/src/weapon/weaponsmanager.ts new file mode 100644 index 00000000..3a3a9b2e --- /dev/null +++ b/client/src/weapon/weaponsmanager.ts @@ -0,0 +1,84 @@ +import { getMissionHandler, getUnitsManager } from ".."; +import { Weapon } from "./weapon"; +import { DataIndexes, GAME_MASTER } from "../constants/constants"; +import { DataExtractor } from "../server/dataextractor"; +import { Contact } from "../@types/unit"; + +export class WeaponsManager { + #weapons: { [ID: number]: Weapon }; + #requestDetectionUpdate: boolean = false; + + constructor() { + this.#weapons = {}; + + document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#weapons).forEach((weapon: Weapon) => weapon.updateVisibility())}); + document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true}); + } + + getWeapons() { + return this.#weapons; + } + + getWeaponByID(ID: number) { + if (ID in this.#weapons) + return this.#weapons[ID]; + else + return null; + } + + addWeapon(ID: number, category: string) { + if (category){ + /* The name of the weapon category is exactly the same as the constructor name */ + var constructor = Weapon.getConstructor(category); + if (constructor != undefined) { + this.#weapons[ID] = new constructor(ID); + } + } + } + + update(buffer: ArrayBuffer) { + var dataExtractor = new DataExtractor(buffer); + var updateTime = Number(dataExtractor.extractUInt64()); + var requestRefresh = false; + while (dataExtractor.getSeekPosition() < buffer.byteLength) { + const ID = dataExtractor.extractUInt32(); + if (!(ID in this.#weapons)) { + const datumIndex = dataExtractor.extractUInt8(); + if (datumIndex == DataIndexes.category) { + const category = dataExtractor.extractString(); + this.addWeapon(ID, category); + } + else { + requestRefresh = true; + } + } + this.#weapons[ID]?.setData(dataExtractor); + } + + if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) { + for (let ID in this.#weapons) { + var weapon = this.#weapons[ID]; + if (!weapon.belongsToCommandedCoalition()) + weapon.setDetectionMethods(this.getWeaponDetectedMethods(weapon)); + } + this.#requestDetectionUpdate = false; + } + + return updateTime; + } + + getWeaponDetectedMethods(weapon: Weapon) { + var detectionMethods: number[] = []; + var units = getUnitsManager().getUnits(); + for (let idx in units) { + if (units[idx].getAlive() && units[idx].getIsLeader() && units[idx].getCoalition() !== "neutral" && units[idx].getCoalition() != weapon.getCoalition()) + { + units[idx].getContacts().forEach((contact: Contact) => { + if (contact.ID == weapon.ID && !detectionMethods.includes(contact.detectionMethod)) + detectionMethods.push(contact.detectionMethod); + }); + } + } + return detectionMethods; + } +} \ No newline at end of file diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index e8fdd894..183f8ff2 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -2,19 +2,26 @@ local version = "v0.4.1-alpha" local debug = true -Olympus.unitCounter = 1 -Olympus.payloadRegistry = {} -Olympus.unitIndex = 0 -Olympus.unitStep = 50 Olympus.OlympusDLL = nil Olympus.DLLsloaded = false Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\' Olympus.log = mist.Logger:new("Olympus", 'info') +Olympus.unitCounter = 1 +Olympus.payloadRegistry = {} + Olympus.missionData = {} -Olympus.units = {} Olympus.unitsData = {} +Olympus.weaponsData = {} + +Olympus.unitIndex = 0 +Olympus.unitStep = 50 +Olympus.units = {} + +Olympus.weaponIndex = 0 +Olympus.weaponStep = 50 +Olympus.weapons = {} Olympus.missionStartTime = DCS.getRealTime() @@ -645,7 +652,6 @@ function Olympus.setOnOff(groupName, onOff) end end - function Olympus.setUnitsData(arg, time) -- Units data local units = {} @@ -672,14 +678,6 @@ function Olympus.setUnitsData(arg, time) elseif unit:getDesc().category == Unit.Category.SHIP then table["category"] = "NavyUnit" end - elseif objectCategory == Object.Category.WEAPON then - if unit:getDesc().category == Weapon.Category.MISSILE then - table["category"] = "Missile" - elseif unit:getDesc().category == Weapon.Category.ROCKET then - table["category"] = "Missile" - elseif unit:getDesc().category == Weapon.Category.BOMB then - table["category"] = "Bomb" - end else units[ID] = {isAlive = false} Olympus.units[ID] = nil @@ -703,32 +701,30 @@ function Olympus.setUnitsData(arg, time) table["heading"] = heading table["isAlive"] = unit:isExist() - -- Data for real units only - if objectCategory == Object.Category.UNIT then - -- Get the targets detected by the group controller - local group = unit:getGroup() - local controller = group:getController() - local controllerTargets = controller:getDetectedTargets() - local contacts = {} - for i, target in ipairs(controllerTargets) do - for det, enum in pairs(Controller.Detection) do - if target.object ~= nil then - target["detectionMethod"] = det - contacts[#contacts + 1] = target - end + -- Get the targets detected by the group controller + local group = unit:getGroup() + local controller = group:getController() + local controllerTargets = controller:getDetectedTargets() + local contacts = {} + for i, target in ipairs(controllerTargets) do + for det, enum in pairs(Controller.Detection) do + if target.object ~= nil then + target["detectionMethod"] = det + contacts[#contacts + 1] = target end end - - table["country"] = unit:getCountry() - table["unitName"] = unit:getName() - table["groupName"] = group:getName() - table["isHuman"] = (unit:getPlayerName() ~= nil) - table["hasTask"] = controller:hasTask() - table["ammo"] = unit:getAmmo() --TODO remove a lot of stuff we don't really need - table["fuel"] = unit:getFuel() - table["life"] = unit:getLife() / unit:getLife0() - table["contacts"] = contacts end + + table["country"] = unit:getCountry() + table["unitName"] = unit:getName() + table["groupName"] = group:getName() + table["isHuman"] = (unit:getPlayerName() ~= nil) + table["hasTask"] = controller:hasTask() + table["ammo"] = unit:getAmmo() --TODO remove a lot of stuff we don't really need + table["fuel"] = unit:getFuel() + table["life"] = unit:getLife() / unit:getLife0() + table["contacts"] = contacts + units[ID] = table end else @@ -753,6 +749,77 @@ function Olympus.setUnitsData(arg, time) return time + 0.05 end +function Olympus.setWeaponsData(arg, time) + -- Weapons data + local weapons = {} + + local startIndex = Olympus.weaponIndex + local endIndex = startIndex + Olympus.weaponStep + local index = 0 + for ID, unit in pairs(Olympus.weapons) do + index = index + 1 + if index > startIndex then + if weapon ~= nil then + local table = {} + table["category"] = "None" + + -- Get the object category in Olympus name + local objectCategory = weapon:getCategory() + if objectCategory == Object.Category.WEAPON then + if weapon:getDesc().category == Weapon.Category.MISSILE then + table["category"] = "Missile" + elseif weapon:getDesc().category == Weapon.Category.ROCKET then + table["category"] = "Missile" + elseif weapon:getDesc().category == Weapon.Category.BOMB then + table["category"] = "Bomb" + end + else + weapons[ID] = {isAlive = false} + Olympus.weapons[ID] = nil + end + + -- If the category is handled by Olympus, get the data + if table["category"] ~= "None" then + -- Compute weapon position and heading + local lat, lng, alt = coord.LOtoLL(weapon:getPoint()) + local position = weapon:getPosition() + local heading = math.atan2( position.x.z, position.x.x ) + + -- Fill the data table + table["name"] = weapon:getTypeName() + table["coalitionID"] = weapon:getCoalition() + table["position"] = {} + table["position"]["lat"] = lat + table["position"]["lng"] = lng + table["position"]["alt"] = alt + table["speed"] = mist.vec.mag(weapon:getVelocity()) + table["heading"] = heading + table["isAlive"] = weapon:isExist() + + weapons[ID] = table + end + else + weapons[ID] = {isAlive = false} + Olympus.weapons[ID] = nil + end + end + if index >= endIndex then + break + end + end + if index ~= endIndex then + Olympus.weaponIndex = 0 + else + Olympus.weaponIndex = endIndex + end + + -- Assemble weaponsData table + Olympus.weaponsData["weapons"] = weapons + + Olympus.OlympusDLL.setWeaponsData() + return time + 0.05 +end + function Olympus.setMissionData(arg, time) -- Bullseye data local bullseyes = {} @@ -898,7 +965,7 @@ function handler:onEvent(event) Olympus.debug(Olympus.serializeTable(event), 2) if event.id == 1 then local weapon = event.weapon - Olympus.units[weapon["id_"]] = weapon + Olympus.weapons[weapon["id_"]] = weapon Olympus.debug("New weapon created " .. weapon["id_"], 2) elseif event.id == 15 then local unit = event.initiator diff --git a/src/core/include/datatypes.h b/src/core/include/datatypes.h index 14eb91b8..f33a8a3f 100644 --- a/src/core/include/datatypes.h +++ b/src/core/include/datatypes.h @@ -1,6 +1,73 @@ #pragma once #include "framework.h" + +namespace DataIndex { + enum DataIndexes { + startOfData = 0, + category, + alive, + human, + controlled, + coalition, + country, + name, + unitName, + groupName, + state, + task, + hasTask, + position, + speed, + heading, + isTanker, + isAWACS, + onOff, + followRoads, + fuel, + desiredSpeed, + desiredSpeedType, + desiredAltitude, + desiredAltitudeType, + leaderID, + formationOffset, + targetID, + targetPosition, + ROE, + reactionToThreat, + emissionsCountermeasures, + TACAN, + radio, + generalSettings, + ammo, + contacts, + activePath, + isLeader, + lastIndex, + endOfData = 255 + }; +} + +namespace State +{ + enum States + { + NONE = 0, + IDLE, + REACH_DESTINATION, + ATTACK, + FOLLOW, + LAND, + REFUEL, + AWACS, + TANKER, + BOMB_POINT, + CARPET_BOMB, + BOMB_BUILDING, + FIRE_AT_AREA + }; +}; + #pragma pack(push, 1) namespace DataTypes { struct TACAN diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 4617ed57..4f287ed8 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -13,72 +13,6 @@ using namespace std::chrono; #define TASK_CHECK_INIT_VALUE 10 -namespace DataIndex { - enum DataIndexes { - startOfData = 0, - category, - alive, - human, - controlled, - coalition, - country, - name, - unitName, - groupName, - state, - task, - hasTask, - position, - speed, - heading, - isTanker, - isAWACS, - onOff, - followRoads, - fuel, - desiredSpeed, - desiredSpeedType, - desiredAltitude, - desiredAltitudeType, - leaderID, - formationOffset, - targetID, - targetPosition, - ROE, - reactionToThreat, - emissionsCountermeasures, - TACAN, - radio, - generalSettings, - ammo, - contacts, - activePath, - isLeader, - lastIndex, - endOfData = 255 - }; -} - -namespace State -{ - enum States - { - NONE = 0, - IDLE, - REACH_DESTINATION, - ATTACK, - FOLLOW, - LAND, - REFUEL, - AWACS, - TANKER, - BOMB_POINT, - CARPET_BOMB, - BOMB_BUILDING, - FIRE_AT_AREA - }; -}; - class Unit { public: diff --git a/src/core/include/weapon.h b/src/core/include/weapon.h index 96744423..a387a111 100644 --- a/src/core/include/weapon.h +++ b/src/core/include/weapon.h @@ -1,13 +1,107 @@ #pragma once -#include "unit.h" +#include "framework.h" +#include "utils.h" +#include "dcstools.h" +#include "luatools.h" +#include "measure.h" +#include "logger.h" +#include "commands.h" +#include "datatypes.h" -class Weapon : public Unit +#include +using namespace std::chrono; + +class Weapon { public: Weapon(json::value json, unsigned int ID); + ~Weapon(); + + /********** Methods **********/ + void initialize(json::value json); + void update(json::value json, double dt); + unsigned int getID() { return ID; } + void getData(stringstream& ss, unsigned long long time); + void triggerUpdate(unsigned char datumIndex); + bool hasFreshData(unsigned long long time); + bool checkFreshness(unsigned char datumIndex, unsigned long long time); + + /********** Setters **********/ + virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); } + virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); } + virtual void setCoalition(unsigned char newValue) { updateValue(coalition, newValue, DataIndex::coalition); } + virtual void setName(string newValue) { updateValue(name, newValue, DataIndex::name); } + virtual void setPosition(Coords newValue) { updateValue(position, newValue, DataIndex::position); } + virtual void setSpeed(double newValue) { updateValue(speed, newValue, DataIndex::speed); } + virtual void setHeading(double newValue) { updateValue(heading, newValue, DataIndex::heading); } + + /********** Getters **********/ + virtual string getCategory() { return category; }; + virtual bool getAlive() { return alive; } + virtual unsigned char getCoalition() { return coalition; } + virtual string getName() { return name; } + virtual Coords getPosition() { return position; } + virtual double getSpeed() { return speed; } + virtual double getHeading() { return heading; } + protected: - /* Weapons are not controllable and have no AIloop */ - virtual void AIloop() {}; + unsigned int ID; + + string category; + bool alive = false; + unsigned char coalition = NULL; + string name = ""; + Coords position = Coords(NULL); + double speed = NULL; + double heading = NULL; + + /********** Other **********/ + map updateTimeMap; + + /********** Private methods **********/ + void appendString(stringstream& ss, const unsigned char& datumIndex, const string& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + ss << datumValue; + } + + /********** Template methods **********/ + template + void updateValue(T& value, T& newValue, unsigned char datumIndex) + { + if (newValue != value) + { + triggerUpdate(datumIndex); + value = newValue; + } + } + + template + void appendNumeric(stringstream& ss, const unsigned char& datumIndex, T& datumValue) { + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&datumValue, sizeof(T)); + } + + template + void appendVector(stringstream& ss, const unsigned char& datumIndex, vector& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + + for (auto& el : datumValue) + ss.write((const char*)&el, sizeof(T)); + } + + template + void appendList(stringstream& ss, const unsigned char& datumIndex, list& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + + for (auto& el : datumValue) + ss.write((const char*)&el, sizeof(T)); + } }; class Missile : public Weapon diff --git a/src/core/include/weaponsmanager.h b/src/core/include/weaponsmanager.h new file mode 100644 index 00000000..10f678f5 --- /dev/null +++ b/src/core/include/weaponsmanager.h @@ -0,0 +1,21 @@ +#pragma once +#include "framework.h" +#include "dcstools.h" + +class Weapon; + +class WeaponsManager +{ +public: + WeaponsManager(lua_State* L); + ~WeaponsManager(); + + map& getWeapons() { return weapons; }; + Weapon* getWeapon(unsigned int ID); + void update(json::value& missionData, double dt); + void getWeaponData(stringstream& ss, unsigned long long time); + +private: + map weapons; +}; + diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 15948906..d8fefd44 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -2,6 +2,7 @@ #include "logger.h" #include "defines.h" #include "unitsManager.h" +#include "weaponsManager.h" #include "server.h" #include "scheduler.h" #include "scriptLoader.h" @@ -9,11 +10,13 @@ #include using namespace std::chrono; -auto lastUpdate = std::chrono::system_clock::now(); +auto lastUnitsUpdate = std::chrono::system_clock::now(); +auto lastWeaponsUpdate = std::chrono::system_clock::now(); auto lastExecution = std::chrono::system_clock::now(); /* Singleton objects */ UnitsManager* unitsManager = nullptr; +WeaponsManager* weaponsManager = nullptr; Server* server = nullptr; Scheduler* scheduler = nullptr; @@ -38,6 +41,7 @@ extern "C" DllExport int coreDeinit(lua_State* L) server->stop(L); delete unitsManager; + delete weaponsManager; delete server; delete scheduler; @@ -51,6 +55,7 @@ extern "C" DllExport int coreInit(lua_State* L) { sessionHash = random_string(16); unitsManager = new UnitsManager(L); + weaponsManager = new WeaponsManager(L); server = new Server(L); scheduler = new Scheduler(L); @@ -101,15 +106,36 @@ extern "C" DllExport int coreUnitsData(lua_State * L) lua_getfield(L, -1, "unitsData"); luaTableToJSON(L, -1, unitsData); - const std::chrono::duration updateDuration = std::chrono::system_clock::now() - lastUpdate; + const std::chrono::duration updateDuration = std::chrono::system_clock::now() - lastUnitsUpdate; if (unitsData.has_object_field(L"units")) { unitsManager->update(unitsData[L"units"], updateDuration.count()); } - lastUpdate = std::chrono::system_clock::now(); + lastUnitsUpdate = std::chrono::system_clock::now(); return(0); } +extern "C" DllExport int coreWeaponssData(lua_State * L) +{ + if (!initialized) + return (0); + + /* Lock for thread safety */ + lock_guard guard(mutexLock); + + json::value weaponsData = json::value::object(); + lua_getglobal(L, "Olympus"); + lua_getfield(L, -1, "weaponsData"); + luaTableToJSON(L, -1, weaponsData); + + const std::chrono::duration updateDuration = std::chrono::system_clock::now() - lastWeaponsUpdate; + if (weaponsData.has_object_field(L"weapons")) { + weaponsManager->update(weaponsData[L"weapons"], updateDuration.count()); + } + lastWeaponsUpdate = std::chrono::system_clock::now(); + + return(0); +} extern "C" DllExport int coreMissionData(lua_State * L) { diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index 36cce0e1..b707919f 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -2,6 +2,7 @@ #include "logger.h" #include "defines.h" #include "unitsManager.h" +#include "weaponsManager.h" #include "scheduler.h" #include "luatools.h" #include @@ -13,6 +14,7 @@ using namespace std::chrono; using namespace base64; extern UnitsManager* unitsManager; +extern WeaponsManager* weaponsManager; extern Scheduler* scheduler; extern json::value missionData; extern mutex mutexLock; @@ -94,7 +96,7 @@ void Server::handle_get(http_request request) if (path.size() > 0) { string URI = to_string(path[0]); - /* Units data. This is the only binary format data transmitted, all others are transmitted as text json for simplicity */ + /* Units data */ if (URI.compare(UNITS_URI) == 0) { unsigned long long updateTime = ms.count(); @@ -103,8 +105,16 @@ void Server::handle_get(http_request request) unitsManager->getUnitData(ss, time); response.set_body(concurrency::streams::bytestream::open_istream(ss.str())); } + else if (URI.compare(WEAPONS_URI) == 0) + { + unsigned long long updateTime = ms.count(); + stringstream ss; + ss.write((char*)&updateTime, sizeof(updateTime)); + weaponsManager->getWeaponData(ss, time); + response.set_body(concurrency::streams::bytestream::open_istream(ss.str())); + } else { - /* Logs data*/ + /* Logs data */ if (URI.compare(LOGS_URI) == 0) { auto logs = json::value::object(); diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index af87f591..068abccc 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -17,7 +17,7 @@ extern Scheduler* scheduler; UnitsManager::UnitsManager(lua_State* L) { - LogInfo(L, "Units Factory constructor called successfully"); + LogInfo(L, "Units Manager constructor called successfully"); } UnitsManager::~UnitsManager() diff --git a/src/core/src/weapon.cpp b/src/core/src/weapon.cpp index 64a1850a..597c9108 100644 --- a/src/core/src/weapon.cpp +++ b/src/core/src/weapon.cpp @@ -4,19 +4,94 @@ #include "commands.h" #include "scheduler.h" #include "defines.h" -#include "unitsmanager.h" -#include -using namespace GeographicLib; +#include +using namespace std::chrono; -extern Scheduler* scheduler; -extern UnitsManager* unitsManager; +Weapon::Weapon(json::value json, unsigned int ID) : + ID(ID) +{ + log("Creating weapon with ID: " + to_string(ID)); +} -/* Weapon */ -Weapon::Weapon(json::value json, unsigned int ID) : Unit(json, ID) +Weapon::~Weapon() { -}; +} + +void Weapon::initialize(json::value json) +{ + if (json.has_string_field(L"name")) + setName(to_string(json[L"name"])); + + + if (json.has_number_field(L"coalitionID")) + setCoalition(json[L"coalitionID"].as_number().to_int32()); + + update(json, 0); +} + + +void Weapon::update(json::value json, double dt) +{ + if (json.has_object_field(L"position")) + { + setPosition({ + json[L"position"][L"lat"].as_number().to_double(), + json[L"position"][L"lng"].as_number().to_double(), + json[L"position"][L"alt"].as_number().to_double() + }); + } + + if (json.has_number_field(L"heading")) + setHeading(json[L"heading"].as_number().to_double()); + + if (json.has_number_field(L"speed")) + setSpeed(json[L"speed"].as_number().to_double()); + + if (json.has_boolean_field(L"isAlive")) + setAlive(json[L"isAlive"].as_bool()); +} + +bool Weapon::checkFreshness(unsigned char datumIndex, unsigned long long time) { + auto it = updateTimeMap.find(datumIndex); + if (it == updateTimeMap.end()) + return false; + else + return it->second > time; +} + +bool Weapon::hasFreshData(unsigned long long time) { + for (auto it : updateTimeMap) + if (it.second > time) + return true; + return false; +} + +void Weapon::getData(stringstream& ss, unsigned long long time) +{ + const unsigned char endOfData = DataIndex::endOfData; + ss.write((const char*)&ID, sizeof(ID)); + for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++) + { + if (checkFreshness(datumIndex, time)) { + switch (datumIndex) { + case DataIndex::category: appendString(ss, datumIndex, category); break; + case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break; + case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break; + case DataIndex::name: appendString(ss, datumIndex, name); break; + case DataIndex::position: appendNumeric(ss, datumIndex, position); break; + case DataIndex::speed: appendNumeric(ss, datumIndex, speed); break; + case DataIndex::heading: appendNumeric(ss, datumIndex, heading); break; + } + } + } + ss.write((const char*)&endOfData, sizeof(endOfData)); +} + +void Weapon::triggerUpdate(unsigned char datumIndex) { + updateTimeMap[datumIndex] = duration_cast(system_clock::now().time_since_epoch()).count(); +} /* Missile */ Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID) diff --git a/src/core/src/weaponsmanager.cpp b/src/core/src/weaponsmanager.cpp new file mode 100644 index 00000000..cc0c21c3 --- /dev/null +++ b/src/core/src/weaponsmanager.cpp @@ -0,0 +1,65 @@ +#include "framework.h" +#include "weaponsManager.h" +#include "logger.h" +#include "weapon.h" +#include "scheduler.h" + +#include "base64.hpp" +using namespace base64; + +WeaponsManager::WeaponsManager(lua_State* L) +{ + LogInfo(L, "Weapons Manager constructor called successfully"); +} + +WeaponsManager::~WeaponsManager() +{ + +} + +Weapon* WeaponsManager::getWeapon(unsigned int ID) +{ + if (weapons.find(ID) == weapons.end()) { + return nullptr; + } + else { + return weapons[ID]; + } +} + +void WeaponsManager::update(json::value& json, double dt) +{ + for (auto const& p : json.as_object()) + { + unsigned int ID = std::stoi(p.first); + if (weapons.count(ID) == 0) + { + json::value value = p.second; + if (value.has_string_field(L"category")) { + string category = to_string(value[L"category"].as_string()); + if (category.compare("Missile") == 0) + weapons[ID] = dynamic_cast(new Missile(p.second, ID)); + else if (category.compare("Bomb") == 0) + weapons[ID] = dynamic_cast(new Bomb(p.second, ID)); + + /* Initialize the weapon if creation was successfull */ + if (weapons.count(ID) != 0) { + weapons[ID]->update(p.second, dt); + weapons[ID]->initialize(p.second); + } + } + } + else { + /* Update the weapon if present*/ + if (weapons.count(ID) != 0) + weapons[ID]->update(p.second, dt); + } + } +} + +void WeaponsManager::getWeaponData(stringstream& ss, unsigned long long time) +{ + for (auto const& p : weapons) + p.second->getData(ss, time); +} + diff --git a/src/olympus/src/olympus.cpp b/src/olympus/src/olympus.cpp index d91ccfb8..3a8de24e 100644 --- a/src/olympus/src/olympus.cpp +++ b/src/olympus/src/olympus.cpp @@ -9,11 +9,13 @@ typedef int(__stdcall* f_coreInit)(lua_State* L); typedef int(__stdcall* f_coreDeinit)(lua_State* L); typedef int(__stdcall* f_coreFrame)(lua_State* L); typedef int(__stdcall* f_coreUnitsData)(lua_State* L); +typedef int(__stdcall* f_coreWeaponsData)(lua_State* L); typedef int(__stdcall* f_coreMissionData)(lua_State* L); f_coreInit coreInit = nullptr; f_coreDeinit coreDeinit = nullptr; f_coreFrame coreFrame = nullptr; f_coreUnitsData coreUnitsData = nullptr; +f_coreWeaponsData coreWeaponsData = nullptr; f_coreMissionData coreMissionData = nullptr; static int onSimulationStart(lua_State* L) @@ -74,6 +76,13 @@ static int onSimulationStart(lua_State* L) goto error; } + coreWeaponsData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreWeaponsData"); + if (!coreWeaponsData) + { + LogError(L, "Error getting coreWeaponsData ProcAddress from DLL"); + goto error; + } + coreMissionData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreMissionData"); if (!coreMissionData) { @@ -126,6 +135,7 @@ static int onSimulationStop(lua_State* L) coreDeinit = nullptr; coreFrame = nullptr; coreUnitsData = nullptr; + coreWeaponsData = nullptr; coreMissionData = nullptr; } @@ -147,6 +157,15 @@ static int setUnitsData(lua_State* L) return 0; } +static int setWeaponsData(lua_State* L) +{ + if (coreWeaponsData) + { + coreWeaponsData(L); + } + return 0; +} + static int setMissionData(lua_State* L) { if (coreMissionData) @@ -161,6 +180,7 @@ static const luaL_Reg Map[] = { {"onSimulationFrame", onSimulationFrame}, {"onSimulationStop", onSimulationStop}, {"setUnitsData", setUnitsData }, + {"setWeaponsData", setWeaponsData }, {"setMissionData", setMissionData }, {NULL, NULL} }; diff --git a/src/shared/include/defines.h b/src/shared/include/defines.h index f061cd1c..13608f69 100644 --- a/src/shared/include/defines.h +++ b/src/shared/include/defines.h @@ -5,6 +5,7 @@ #define REST_ADDRESS "http://localhost:30000" #define REST_URI "olympus" #define UNITS_URI "units" +#define WEAPONS_URI "weapons" #define LOGS_URI "logs" #define AIRBASES_URI "airbases" #define BULLSEYE_URI "bullseyes" From 6d434e48a149719890f30741424a6fe3f16e62eb Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 27 Jul 2023 17:16:21 +0200 Subject: [PATCH 3/4] Added missing files --- src/core/core.vcxproj | 2 ++ src/core/core.vcxproj.filters | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 2b70e07f..7c46020d 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -47,6 +47,7 @@ + @@ -64,6 +65,7 @@ + 16.0 diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 50797e65..56fade36 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -51,6 +51,9 @@ Header Files + + Header Files + @@ -98,5 +101,8 @@ Source Files + + Source Files + \ No newline at end of file From 80ed675cbcb69e6c29f3a4eaed884921ad4a3f09 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 27 Jul 2023 19:21:34 +0200 Subject: [PATCH 4/4] Completed transition to weapons handler --- client/src/unit/unitsmanager.ts | 8 ++++---- scripts/OlympusCommand.lua | 20 ++++++++++---------- src/core/src/core.cpp | 2 +- src/core/src/unit.cpp | 4 ++-- src/core/src/unitsmanager.cpp | 4 ---- src/olympus/src/olympus.cpp | 6 +++--- 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index 28652824..c0c30a5b 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -664,28 +664,28 @@ export class UnitsManager { spawnUnits(category: string, units: any, coalition: string = "blue", immediate: boolean = true, airbase: string = "") { var spawnPoints = 0; if (category === "Aircraft") { - if (airbase == "" && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { + if (airbase == "" && getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { getInfoPopup().setText("Aircrafts can be air spawned during the SETUP phase only"); return false; } spawnPoints = units.reduce((points: number, unit: any) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0); spawnAircrafts(units, coalition, airbase, immediate, spawnPoints); } else if (category === "Helicopter") { - if (airbase == "" && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { + if (airbase == "" && getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { getInfoPopup().setText("Helicopters can be air spawned during the SETUP phase only"); return false; } spawnPoints = units.reduce((points: number, unit: any) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0); spawnHelicopters(units, coalition, airbase, immediate, spawnPoints); } else if (category === "GroundUnit") { - if (getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { + if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { getInfoPopup().setText("Ground units can be spawned during the SETUP phase only"); return false; } spawnPoints = units.reduce((points: number, unit: any) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0); spawnGroundUnits(units, coalition, immediate, spawnPoints); } else if (category === "NavyUnit") { - if (getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { + if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { getInfoPopup().setText("Navy units can be spawned during the SETUP phase only"); return false; } diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 183f8ff2..28abaddb 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -704,17 +704,18 @@ function Olympus.setUnitsData(arg, time) -- Get the targets detected by the group controller local group = unit:getGroup() local controller = group:getController() - local controllerTargets = controller:getDetectedTargets() + local contacts = {} - for i, target in ipairs(controllerTargets) do - for det, enum in pairs(Controller.Detection) do - if target.object ~= nil then + for det, enum in pairs(Controller.Detection) do + local controllerTargets = unit:getController():getDetectedTargets(enum) + for i, target in ipairs(controllerTargets) do + if target.object ~= nil and target.visible then target["detectionMethod"] = det contacts[#contacts + 1] = target end end end - + table["country"] = unit:getCountry() table["unitName"] = unit:getName() table["groupName"] = group:getName() @@ -756,7 +757,7 @@ function Olympus.setWeaponsData(arg, time) local startIndex = Olympus.weaponIndex local endIndex = startIndex + Olympus.weaponStep local index = 0 - for ID, unit in pairs(Olympus.weapons) do + for ID, weapon in pairs(Olympus.weapons) do index = index + 1 if index > startIndex then if weapon ~= nil then @@ -817,7 +818,7 @@ function Olympus.setWeaponsData(arg, time) Olympus.weaponsData["weapons"] = weapons Olympus.OlympusDLL.setWeaponsData() - return time + 0.05 + return time + 0.25 end function Olympus.setMissionData(arg, time) @@ -875,7 +876,7 @@ function Olympus.initializeUnits() Olympus.units[unit["id_"]] = unit end end - Olympus.debug("Olympus units table initialized", 2) + Olympus.notify("Olympus units table initialized", 2) else Olympus.debug("MIST DBs not ready", 2) timer.scheduleFunction(Olympus.initializeUnits, {}, timer.getTime() + 1) @@ -946,7 +947,6 @@ end ------------------------------------------------------------------------------------------------------ -- Olympus startup script ------------------------------------------------------------------------------------------------------ - local OlympusName = 'Olympus ' .. version .. ' C++ module'; Olympus.DLLsloaded = Olympus.loadDLLs() if Olympus.DLLsloaded then @@ -962,7 +962,6 @@ if handler ~= nil then end handler = {} function handler:onEvent(event) - Olympus.debug(Olympus.serializeTable(event), 2) if event.id == 1 then local weapon = event.weapon Olympus.weapons[weapon["id_"]] = weapon @@ -977,6 +976,7 @@ world.addEventHandler(handler) -- Start the periodic functions timer.scheduleFunction(Olympus.setUnitsData, {}, timer.getTime() + 0.05) +timer.scheduleFunction(Olympus.setWeaponsData, {}, timer.getTime() + 0.25) timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1) -- Initialize the ME units diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index d8fefd44..3fc42753 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -115,7 +115,7 @@ extern "C" DllExport int coreUnitsData(lua_State * L) return(0); } -extern "C" DllExport int coreWeaponssData(lua_State * L) +extern "C" DllExport int coreWeaponsData(lua_State * L) { if (!initialized) return (0); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 7859fae9..3edd5625 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -72,7 +72,7 @@ void Unit::update(json::value json, double dt) if (json.has_boolean_field(L"isAlive")) setAlive(json[L"isAlive"].as_bool()); - if (json.has_object_field(L"isHuman")) + if (json.has_boolean_field(L"isHuman")) setHuman(json[L"isHuman"].as_bool()); if (json.has_number_field(L"fuel")) { @@ -109,7 +109,7 @@ void Unit::update(json::value json, double dt) contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); string detectionMethod = to_string(contactJson[L"detectionMethod"]); - if (detectionMethod.compare("VISUAL") == 0) contactItem.detectionMethod = 1; + if (detectionMethod.compare("VISUAL") == 0) contactItem.detectionMethod = 1; else if (detectionMethod.compare("OPTIC") == 0) contactItem.detectionMethod = 2; else if (detectionMethod.compare("RADAR") == 0) contactItem.detectionMethod = 4; else if (detectionMethod.compare("IRST") == 0) contactItem.detectionMethod = 8; diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 068abccc..f9de0af0 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -110,10 +110,6 @@ void UnitsManager::update(json::value& json, double dt) units[ID] = dynamic_cast(new GroundUnit(p.second, ID)); else if (category.compare("NavyUnit") == 0) units[ID] = dynamic_cast(new NavyUnit(p.second, ID)); - else if (category.compare("Missile") == 0) - units[ID] = dynamic_cast(new Missile(p.second, ID)); - else if (category.compare("Bomb") == 0) - units[ID] = dynamic_cast(new Bomb(p.second, ID)); /* Initialize the unit if creation was successfull */ if (units.count(ID) != 0) { diff --git a/src/olympus/src/olympus.cpp b/src/olympus/src/olympus.cpp index 3a8de24e..601a3ebb 100644 --- a/src/olympus/src/olympus.cpp +++ b/src/olympus/src/olympus.cpp @@ -69,21 +69,21 @@ static int onSimulationStart(lua_State* L) goto error; } - coreUnitsData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreUnitsData"); + coreUnitsData = (f_coreUnitsData)GetProcAddress(hGetProcIDDLL, "coreUnitsData"); if (!coreUnitsData) { LogError(L, "Error getting coreUnitsData ProcAddress from DLL"); goto error; } - coreWeaponsData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreWeaponsData"); + coreWeaponsData = (f_coreWeaponsData)GetProcAddress(hGetProcIDDLL, "coreWeaponsData"); if (!coreWeaponsData) { LogError(L, "Error getting coreWeaponsData ProcAddress from DLL"); goto error; } - coreMissionData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreMissionData"); + coreMissionData = (f_coreMissionData)GetProcAddress(hGetProcIDDLL, "coreMissionData"); if (!coreMissionData) { LogError(L, "Error getting coreMissionData ProcAddress from DLL");