DCSOlympus/scripts/lua/backend/OlympusCommand.lua

1706 lines
54 KiB
Lua

local version = "{{OLYMPUS_VERSION_NUMBER}}.{{OLYMPUS_COMMIT_HASH}}"
local debug = false -- True enables debug printing using DCS messages
-- .dll related variables
Olympus.OlympusDLL = nil
Olympus.DLLsloaded = false
-- Logger reference
Olympus.log = mist.Logger:new("Olympus", 'info')
-- Data structures for transfer to .dll
Olympus.missionData = {}
Olympus.unitsData = {}
Olympus.weaponsData = {}
Olympus.drawingsByLayer = {}
-- Units data structures
Olympus.unitCounter = 1 -- Counter to generate unique names
Olympus.cloneDatabase = {} -- Database of spawn options, used for units cloning
Olympus.unitIndex = 0 -- Counter used to spread the computational load of data retrievial from DCS
Olympus.unitStep = 50 -- Max number of units that get updated each cycle
Olympus.units = {} -- Table holding references to all the currently existing units
Olympus.unitsInitialLife = {} -- getLife0 returns 0 for ships, so we need to store the initial life of units
Olympus.weaponIndex = 0 -- Counter used to spread the computational load of data retrievial from DCS
Olympus.weaponStep = 50 -- Max number of weapons that get updated each cycle
Olympus.weapons = {} -- Table holding references to all the currently existing weapons
-- Spots (laser/IR) data
Olympus.spots = {}
Olympus.spotsCounter = 1
-- Miscellaneous initializations
Olympus.missionStartTime = DCS.getRealTime()
Olympus.napalmCounter = 1
Olympus.fireCounter = 1
-- Load the lua file system
local lfs = require('lfs')
------------------------------------------------------------------------------------------------------
-- Olympus functions
------------------------------------------------------------------------------------------------------
-- Print a debug message if the debug option is true
function Olympus.debug(message, displayFor)
if debug == true then
Olympus.log:info(message)
trigger.action.outText(message, displayFor)
end
end
-- Print a notify message
function Olympus.notify(message, displayFor)
Olympus.log:info(message)
trigger.action.outText(message, displayFor)
end
-- Loads the olympus .dll
function Olympus.loadDLLs()
-- Add the .dll paths
package.cpath = package.cpath..';'..Olympus.instancePath..'?.dll;'
local status
status, Olympus.OlympusDLL = pcall(require, 'olympus')
if status then
return true
else
return false
end
end
-- Gets a unit class reference from a given ObjectID (the ID used by Olympus for unit referencing)
function Olympus.getUnitByID(ID)
return Olympus.units[ID];
end
-- Gets the ID of the first country that belongs to a coalition
function Olympus.getCountryIDByCoalition(coalitionString)
for countryName, countryId in pairs(country.id) do
if coalition.getCountryCoalition(countryId) == Olympus.getCoalitionIDByCoalition(coalitionString) then
return countryId
end
end
return 0
end
-- Gets the coalition ID of a coalition
function Olympus.getCoalitionIDByCoalition(coalitionString)
local coalitionID = 0
if coalitionString == "red" then
coalitionID = 1
elseif coalitionString == "blue" then
coalitionID = 2
end
return coalitionID
end
-- Gets the coalition name from the coalition ID
function Olympus.getCoalitionByCoalitionID(coalitionID)
local coalitionString = "neutral"
if coalitionID == 1 then
coalitionString = "red"
elseif coalitionID == 2 then
coalitionString = "blue"
end
return coalitionString
end
-- Builds a valid enroute task depending on the provided options
function Olympus.buildEnrouteTask(options)
local task = nil
-- Engage specific target by ID. Checks if target exists.
if options['id'] == 'EngageUnit' and options['targetID'] then
local target = Olympus.getUnitByID(options['targetID'])
if target and target:isExist() then
task = {
id = 'EngageUnit',
params = {
unitId = options['targetID'],
}
}
end
-- Start being an active tanker
elseif options['id'] == 'Tanker' then
task = {
id = 'Tanker',
params = {},
}
-- Start being an active AWACS
elseif options['id'] == 'AWACS' then
task = {
id = 'AWACS',
params = {},
}
end
return task
end
-- Builds a valid main task depending on the provided options
function Olympus.buildTask(groupName, options)
local task = nil
local group = Group.getByName(groupName)
-- Combo tasks require nested tables
if (Olympus.isArray(options)) then
local tasks = {}
for idx, subOptions in pairs(options) do
tasks[idx] = Olympus.buildTask(groupName, subOptions) or Olympus.buildEnrouteTask(subOptions)
end
task = {
id = 'ComboTask',
params = {
tasks = tasks
}
}
Olympus.debug(Olympus.serializeTable(task), 30)
else
-- Follow a unit in formation with a given offset
if options['id'] == 'FollowUnit' and options['leaderID'] and options['offset'] then
local leader = Olympus.getUnitByID(options['leaderID'])
if leader and leader:isExist() then
task = {
id = 'Follow',
params = {
groupId = leader:getGroup():getID(),
pos = options['offset'],
lastWptIndexFlag = false,
lastWptIndex = 1
}
}
end
-- Go refuel to the nearest tanker. If the unit can't refuel it will RTB
elseif options['id'] == 'Refuel' then
task = {
id = 'Refueling',
params = {}
}
-- Orbit in place at a given altitude and with a given pattern
elseif options['id'] == 'Orbit' then
task = {
id = 'Orbit',
params = {
pattern = options['pattern'] or "Circle"
}
}
if options['pattern'] == 'Race-Track' then
local heading = options['heading'] or 0
local length = options['length'] or 20000
if group ~= nil then
local groupPos = mist.getLeadPos(group)
task['params']['point'] = {x = groupPos.x, y = groupPos.z}
task['params']['point2'] = {x = groupPos.x + math.cos(heading) * length, y = groupPos.z + math.sin(heading) * length}
end
end
-- Compute the altitude depending on the altitude type
if options['altitude'] then
if options ['altitudeType'] then
if options ['altitudeType'] == "AGL" then
local groundHeight = 0
if group ~= nil then
local groupPos = mist.getLeadPos(group)
groundHeight = land.getHeight({x = groupPos.x, y = groupPos.z})
end
task['params']['altitude'] = groundHeight + options['altitude']
else
task['params']['altitude'] = options['altitude']
end
else
task['params']['altitude'] = options['altitude']
end
end
-- Compute the speed depending on the speed type. CAS calculation is only available if the altitude is also available
if options['speed'] then
-- Simplified formula to compute CAS from GS
local speed = options['speed']
if options['speedType'] and options['speedType'] == "CAS" and task['params']['altitude'] then
speed = speed * (1 + 0.02 * task['params']['altitude'] / 0.3048 / 1000)
end
task['params']['speed'] = speed
end
-- Bomb a specific location
elseif options['id'] == 'Bombing' and options['lat'] and options['lng'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'Bombing',
params = {
point = {x = point.x, y = point.z},
attackQty = 1
}
}
-- Perform carpet bombing at a specific location
elseif options['id'] == 'CarpetBombing' and options['lat'] and options['lng'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'CarpetBombing',
params = {
x = point.x,
y = point.z,
carpetLength = 1000,
attackType = 'Carpet',
expend = "All",
attackQty = 1,
attackQtyLimit = true
}
}
-- Fire at a specific point
elseif options['id'] == 'FireAtPoint' and options['lat'] and options['lng'] and options['radius'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
local expendQtyEnabled = false
local expendQty = 0
if options['expendQty'] then
expendQtyEnabled = true
expendQty = options['expendQty']
end
if options['alt'] then
task = {
id = 'FireAtPoint',
params = {
point = {x = point.x, y = point.z},
radius = options['radius'],
altitude = options['alt'],
alt_type = 0, -- ASL
expendQtyEnabled = expendQtyEnabled,
expendQty = expendQty
}
}
else
task = {
id = 'FireAtPoint',
params = {
point = {x = point.x, y = point.z},
radius = options['radius'],
expendQtyEnabled = expendQtyEnabled,
expendQty = expendQty
}
}
end
-- Land at a specific point
elseif options['id'] == 'LandAtPoint' then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'Land',
params = {
point = {x = point.x, y = point.z},
}
}
-- Attack unit
elseif options['id'] == 'AttackUnit' and options['unitID'] then
task = {
id = 'AttackUnit',
params = {
unitId = options['unitID'],
}
}
end
end
return task
end
-- Move a unit. Since many tasks in DCS are Enroute tasks, this function is an important way to control the unit AI
function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedType, category, taskOptions)
Olympus.debug("Olympus.move " .. groupName .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. altitudeType .. " ".. speed .. "m/s " .. category .. " " .. Olympus.serializeTable(taskOptions), 2)
local group = Group.getByName(groupName)
if group ~= nil then
if category == "Aircraft" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
-- 'AGL' mode does not appear to work in the buildWP function. This is a crude approximation
if altitudeType == "AGL" then
altitude = land.getHeight({x = endPoint.x, y = endPoint.z}) + altitude
end
-- Simplified formula to compute CAS from GS
if speedType == "CAS" then
speed = speed * (1 + 0.02 * altitude / 0.3048 / 1000)
end
-- Create the path
local path = {
[1] = mist.fixedWing.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
-- If a task exists assign it to the controller
if taskOptions then
local task = Olympus.buildEnrouteTask(taskOptions)
if task then
path[1].task = task
path[2].task = task
end
end
-- Assign the mission task to the controller
local missionTask = {
id = 'Mission',
params = {
route = {
points = mist.utils.deepCopy(path),
},
},
}
local groupCon = group:getController()
if groupCon then
groupCon:setTask(missionTask)
end
Olympus.debug("Olympus.move executed successfully on Aircraft", 2)
elseif category == "Helicopter" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
-- 'AGL' mode does not appear to work in the buildWP function. This is a crude approximation
if altitudeType == "AGL" then
altitude = land.getHeight({x = endPoint.x, y = endPoint.z}) + altitude
end
-- Simplified formula to compute CAS from GS
if speedType == "CAS" then
speed = speed * (1 + 0.02 * altitude / 0.3048 / 1000)
end
-- Create the path
local path = {
[1] = mist.heli.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.heli.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
-- If a task exists assign it to the controller
if taskOptions then
local task = Olympus.buildEnrouteTask(taskOptions)
if task then
path[1].task = task
path[2].task = task
end
end
-- Assign the mission task to the controller
local missionTask = {
id = 'Mission',
params = {
route = {
points = mist.utils.deepCopy(path),
},
},
}
local groupCon = group:getController()
if groupCon then
groupCon:setTask(missionTask)
end
Olympus.debug("Olympus.move executed successfully on Helicopter", 2)
elseif category == "GroundUnit" then
local endPoint = coord.LLtoLO(lat, lng, 0)
local action = "Off Road"
local disableRoads = true
if taskOptions and taskOptions['id'] == 'FollowRoads' and taskOptions['value'] == true then
action = "On Road"
disableRoads = false
end
missionTask = {
id = 'Mission',
params = {
route = {
points = {
[1] = {
type = "Turning Point",
action = action,
disableRoads = disableRoads,
x = endPoint.x,
y = endPoint.z,
speed = speed,
speed_locked = false,
ETA_locked = false,
name = 'Mission1',
},
[2] = {
type = "Turning Point",
action = action,
disableRoads = disableRoads,
x = endPoint.x,
y = endPoint.z,
speed = speed,
speed_locked = false,
ETA_locked = false,
name = 'Mission1',
},
}
},
}
}
local groupCon = group:getController()
if groupCon then
groupCon:setTask(missionTask)
end
Olympus.debug("Olympus.move executed successfully on GroundUnit", 2)
elseif category == "NavyUnit" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
local bearing = math.atan2(endPoint.z - startPoint.z, endPoint.x - startPoint.x)
vars = {
group = group,
point = endPoint,
heading = bearing,
speed = speed
}
mist.groupToRandomPoint(vars)
Olympus.debug("Olympus.move executed successfully on NavyUnit", 2)
else
Olympus.debug("Olympus.move not implemented yet for " .. category, 2)
end
else
Olympus.debug("Error in Olympus.move " .. groupName, 2)
end
end
-- Creates a simple smoke on the ground
function Olympus.smoke(color, lat, lng)
Olympus.debug("Olympus.smoke " .. color .. " (" .. lat .. ", " .. lng ..")", 2)
local colorEnum = nil
if color == "green" then
colorEnum = trigger.smokeColor.Green
elseif color == "red" then
colorEnum = trigger.smokeColor.Red
elseif color == "white" then
colorEnum = trigger.smokeColor.White
elseif color == "orange" then
colorEnum = trigger.smokeColor.Orange
elseif color == "blue" then
colorEnum = trigger.smokeColor.Blue
end
trigger.action.smoke(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)), colorEnum)
end
-- Creates an explosion on the ground
function Olympus.explosion(intensity, explosionType, lat, lng, alt)
Olympus.debug("Olympus.explosion " .. explosionType .. " " .. intensity .. " (" .. lat .. ", " .. lng .. ")", 2)
local vec3 = nil
if alt ~= nil then
vec3 = coord.LLtoLO(lat, lng, alt)
else
vec3 = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng))
end
if explosionType == "normal" then
trigger.action.explosion(vec3, intensity)
elseif explosionType == "phosphorous" then
Olympus.phosphorous(vec3)
elseif explosionType == "napalm" then
Olympus.napalm(vec3)
elseif explosionType == "secondary" then
Olympus.secondaries(vec3)
elseif explosionType == "fire" then
Olympus.createFire(vec3)
elseif explosionType == "depthCharge" then
end
end
function Olympus.phosphorous(vec3)
trigger.action.explosion(vec3, 1)
for i = 1,math.random(3, 10) do
angle = mist.utils.toRadian((math.random(1, 360)))
local randVec = mist.utils.makeVec3GL((mist.getRandPointInCircle(vec3, 5, 1, 0, 360)))
trigger.action.signalFlare(randVec, 2, angle)
end
end
function Olympus.napalm(vec3)
local napeName = "napalmStrike" .. Olympus.napalmCounter
Olympus.napalmCounter = Olympus.napalmCounter + 1
mist.dynAddStatic(
{
country = 20,
category = 'Fortifications',
hidden = true,
name = napeName,
type ="Fuel tank",
x = vec3.x,
y = vec3.z,
heading = 0,
} -- end of function
)
timer.scheduleFunction(Olympus.explodeNapalm, vec3, timer.getTime() + 0.1)
timer.scheduleFunction(Olympus.removeNapalm, napeName, timer.getTime() + 0.12)
end
function Olympus.explodeNapalm(vec3)
trigger.action.explosion(vec3, 10)
end
function Olympus.removeNapalm(staticName)
StaticObject.getByName(staticName):destroy()
end
function Olympus.createFire(vec3)
local smokeName = "smokeName" .. Olympus.fireCounter
Olympus.fireCounter = Olympus.fireCounter + 1
trigger.action.effectSmokeBig(vec3, 2 , 1, smokeName)
trigger.action.explosion(vec3, 1) -- looks wierd to spawn in on flat land without this
timer.scheduleFunction(Olympus.removeFire, smokeName, timer.getTime() + 20)
end
function Olympus.removeFire (smokeName)
trigger.action.effectSmokeStop(smokeName)
end
function Olympus.secondaries(vec3)
Olympus.randomDebries(vec3)
--trigger.action.explosion(vec3, 1)
--for i = 1, 10 do
-- timer.scheduleFunction(Olympus.randomDebries, vec3, timer.getTime() + math.random(0, 180))
--end
end
function Olympus.randomDebries(vec3)
trigger.action.explosion(vec3, 1)
for i = 1,math.random(3, 10) do
angle = mist.utils.toRadian((math.random(1, 360)))
local randVec = mist.utils.makeVec3GL((mist.getRandPointInCircle(vec3, 5, 1, 0, 360)))
trigger.action.signalFlare(randVec, 3, angle)
end
end
-- Shines a laser from a unit to a point
function Olympus.fireLaser(ID, code, lat, lng)
Olympus.debug("Olympus.fireLaser " .. ID .. " -> (" .. lat .. ", " .. lng .. ") code " .. code, 2)
local vec3 = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng))
local unit = Olympus.getUnitByID(ID)
if unit ~= nil and unit:isExist() then
local spot = Spot.createLaser(unit, {x = 0, y = 1, z = 0}, vec3, code)
Olympus.spotsCounter = Olympus.spotsCounter + 1
Olympus.spots[Olympus.spotsCounter] = {
type = "laser",
object = spot,
sourceUnitID = ID,
targetPosition = {
lat = lat,
lng = lng
},
active = true,
code = code
}
end
end
-- Shines a infrared light from a unit to a point
function Olympus.fireInfrared(ID, lat, lng)
Olympus.debug("Olympus.fireInfrared " .. ID .. " -> (" .. lat .. ", " .. lng .. ")", 2)
local vec3 = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng))
local unit = Olympus.getUnitByID(ID)
if unit ~= nil and unit:isExist() then
local spot = Spot.createInfraRed(unit, {x = 0, y = 1, z = 0}, vec3)
Olympus.spotsCounter = Olympus.spotsCounter + 1
Olympus.spots[Olympus.spotsCounter] = {
type = "infrared",
object = spot,
sourceUnitID = ID,
targetPosition = {
lat = lat,
lng = lng
},
active = true
}
end
end
-- Set new laser code
function Olympus.setLaserCode(spotID, code)
Olympus.debug("Olympus.setLaserCode " .. spotID .. " -> " .. code, 2)
local spot = Olympus.spots[spotID]
if spot ~= nil and spot.type == "laser" then
spot.object:setCode(code)
spot.code = code
end
end
-- Move spot to a new location
function Olympus.moveSpot(spotID, lat, lng)
Olympus.debug("Olympus.moveSpot " .. spotID .. " -> (" .. lat .. ", " .. lng .. ")", 2)
local spot = Olympus.spots[spotID]
if spot ~= nil then
spot.object:setPoint(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)))
spot.targetPosition = {lat = lat, lng = lng}
end
end
-- Remove the spot
function Olympus.deleteSpot(spotID)
Olympus.debug("Olympus.deleteSpot " .. spotID, 2)
local spot = Olympus.spots[spotID]
if spot ~= nil then
spot.object:destroy()
Olympus.spots[spotID]["active"] = false
end
end
-- Spawns a new unit or group
-- Spawn table contains the following parameters
-- category: (string), either Aircraft, Helicopter, GroundUnit or NavyUnit
-- coalition: (string)
-- country: (string)
-- airbaseName: (string, optional) only for air units
-- units: (array) Array of units to spawn. All units will be in the same group. Each unit element must contain:
-- unitType: (string) DCS Name of the unit
-- lat: (number)
-- lng: (number)
-- alt: (number, optional) only for air units
-- loadout: (string, optional) only for air units, must be one of the loadouts defined in unitPayloads.lua or mods.lua
-- payload: (table, optional) overrides loadout, specifies directly the loadout of the unit
-- liveryID: (string, optional)
function Olympus.spawnUnits(spawnTable)
Olympus.debug("Olympus.spawnUnits " .. Olympus.serializeTable(spawnTable), 2)
local unitsTable = nil
local route = nil
local category = nil
-- Generate the units table and route as per DCS requirements
if spawnTable.category == 'Aircraft' then
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
route = Olympus.generateAirUnitsRoute(spawnTable)
category = 'plane'
elseif spawnTable.category == 'Helicopter' then
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
route = Olympus.generateAirUnitsRoute(spawnTable)
category = 'helicopter'
elseif spawnTable.category == 'GroundUnit' then
unitsTable = Olympus.generateGroundUnitsTable(spawnTable.units)
category = 'vehicle'
elseif spawnTable.category == 'NavyUnit' then
unitsTable = Olympus.generateNavyUnitsTable(spawnTable.units)
category = 'ship'
end
-- It the unit country is not specified, get a country that belongs to the coalition
local countryID = 0
if spawnTable.country == nil or spawnTable.country == "" then
countryID = Olympus.getCountryIDByCoalition(spawnTable.coalition)
else
countryID = country.id[spawnTable.country]
end
-- Save the units in the database, for cloning
for idx, unitTable in pairs(unitsTable) do
Olympus.addToDatabase(unitTable)
end
-- Spawn the new group
local vars =
{
units = unitsTable,
country = countryID,
category = category,
route = route,
name = "Olympus-" .. Olympus.unitCounter,
task = 'CAP'
}
Olympus.debug(Olympus.serializeTable(vars), 2)
mist.dynAdd(vars)
Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.debug("Olympus.spawnUnits completed succesfully", 2)
end
-- Generates unit table for air units
function Olympus.generateAirUnitsTable(units)
local unitsTable = {}
for idx, unit in pairs(units) do
local loadout = unit.loadout -- loadout: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType
local payload = unit.payload -- payload: a table, if present the unit will receive this specific payload. Overrides loadout
if unit.heading == nil then
unit.heading = 0
end
-- Define the loadout
if payload == nil then
if loadout ~= nil and loadout ~= "" and Olympus.unitPayloads[unit.unitType] and Olympus.unitPayloads[unit.unitType][loadout] then
payload = { ["pylons"] = Olympus.unitPayloads[unit.unitType][loadout], ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100 }
elseif loadout ~= nil and loadout ~= "" and Olympus.modsUnitPayloads ~= nil and Olympus.modsUnitPayloads[unit.unitType] and Olympus.modsUnitPayloads[unit.unitType][loadout] then
payload = { ["pylons"] = Olympus.modsUnitPayloads[unit.unitType][loadout], ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100 }
else
payload = { ["pylons"] = {}, ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100 }
end
end
-- Generate the unit table
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
unitsTable[#unitsTable + 1] =
{
["type"] = unit.unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["alt"] = unit.alt,
["alt_type"] = "BARO",
["skill"] = unit.skill,
["payload"] = payload,
["heading"] = unit.heading,
["callsign"] = { [1] = 1, [2] = 1, [3] = 1, ["name"] = "Olympus" .. Olympus.unitCounter.. "-" .. #unitsTable + 1 },
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
["livery_id"] = unit.liveryID
}
end
return unitsTable
end
function Olympus.generateAirUnitsRoute(spawnTable)
local airbaseName = spawnTable.airbaseName -- airbaseName: a string, if present the aircraft will spawn on the ground of the selected airbase
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(spawnTable.units[1].lat, spawnTable.units[1].lng, 0))
-- If a airbase is provided the first waypoint is set as a From runway takeoff.
local route = {}
if airbaseName and airbaseName ~= "" then
local airbase = Airbase.getByName(airbaseName)
if airbase then
local airbaseID = airbase:getID()
route =
{
["points"] =
{
[1] =
{
["action"] = "From Parking Area Hot",
["tasks"] = {
[1] = {["number"] = 1, ["auto"] = true, ["id"] = "WrappedAction", ["enabled"] = true, ["params"] = {["action"] = {["id"] = "EPLRS", ["params"] = {["value"] = true}, }, }, },
[2] = {["number"] = 2, ["auto"] = false, ["id"] = "Orbit", ["enabled"] = true, ["params"] = {["pattern"] = "Circle"}, },
},
["type"] = "TakeOffParkingHot",
["ETA"] = 0,
["ETA_locked"] = true,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["alt_type"] = "BARO",
["formation_template"] = "",
["airdromeId"] = airbaseID,
["speed_locked"] = true,
},
},
}
end
else
route = {
["points"] =
{
[1] =
{
["alt"] = alt,
["alt_type"] = "BARO",
["tasks"] = {
[1] = {["number"] = 1, ["auto"] = true, ["id"] = "WrappedAction", ["enabled"] = true, ["params"] = {["action"] = {["id"] = "EPLRS", ["params"] = {["value"] = true}, }, }, },
[2] = {["number"] = 2, ["auto"] = false, ["id"] = "Orbit", ["enabled"] = true, ["params"] = {["pattern"] = "Circle"}, },
},
["type"] = "Turning Point",
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
},
},
}
end
return route
end
-- Generates ground units table, either single or from template
function Olympus.generateGroundUnitsTable(units)
local unitsTable = {}
for idx, unit in pairs(units) do
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
if Olympus.hasKey(templates, unit.unitType) then
for idx, value in pairs(templates[unit.unitType].units) do
unitsTable[#unitsTable + 1] =
{
["type"] = value.name,
["x"] = spawnLocation.x + value.dx,
["y"] = spawnLocation.z + value.dy,
["heading"] = 0,
["skill"] = unit.skill,
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1
}
end
else
unitsTable[#unitsTable + 1] =
{
["type"] = unit.unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["heading"] = unit.heading,
["skill"] = unit.skill,
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
["livery_id"] = unit.liveryID
}
end
end
return unitsTable
end
-- Generates navy units table, either single or from template
function Olympus.generateNavyUnitsTable(units)
local unitsTable = {}
for idx, unit in pairs(units) do
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
if Olympus.hasKey(templates, unit.unitType) then
for idx, value in pairs(templates[unit.unitType].units) do
unitsTable[#unitsTable + 1] =
{
["type"] = value.name,
["x"] = spawnLocation.x + value.dx,
["y"] = spawnLocation.z + value.dy,
["heading"] = 0,
["skill"] = unit.skill,
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
["transportable"] = { ["randomTransportable"] = false }
}
end
else
unitsTable[#unitsTable + 1] =
{
["type"] = unit.unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["heading"] = unit.heading,
["skill"] = unit.skill,
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
["transportable"] = { ["randomTransportable"] = false },
["livery_id"] = unit.liveryID
}
end
end
return unitsTable
end
-- Add the unit data to the database, used for unit cloning
function Olympus.addToDatabase(unitTable)
Olympus.cloneDatabase[unitTable.name] = unitTable
end
-- Find a database entry by ID
function Olympus.findInDatabase(ID)
for idx, unitRecord in pairs(Olympus.cloneDatabase) do
if unitRecord ~= nil and unitRecord["ID"] == ID then
return unitRecord
end
end
return nil
end
-- Clones a unit by ID. Will clone the unit with the same original payload as the source unit. TODO: only works on Olympus unit not ME units (TO BE VERIFIED).
-- cloneTable is an array of element, each of which contains
-- ID: (number) ID of the unit to clone
-- lat: (number)
-- lng: (number)
function Olympus.clone(cloneTable, deleteOriginal)
Olympus.debug("Olympus.clone " .. Olympus.serializeTable(cloneTable), 2)
local unitsTable = {}
local countryID = nil
local category = nil
local route = {}
-- All the units in the table will be cloned in a single group
for idx, cloneData in pairs(cloneTable) do
local ID = cloneData.ID
local unitRecord = Olympus.findInDatabase(ID)
if unitRecord ~= nil then
-- Update the data of the cloned unit
local unitTable = mist.utils.deepCopy(unitRecord)
local point = coord.LLtoLO(cloneData['lat'], cloneData['lng'], 0)
if unitTable then
unitTable["x"] = point.x
unitTable["y"] = point.z
unitTable["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1
end
if countryID == nil and category == nil then
countryID = unitRecord["country"]
if unitRecord["category"] == Unit.Category.AIRPLANE then
category = 'plane'
route = {
["points"] =
{
[1] =
{
["alt"] = alt,
["alt_type"] = "BARO",
["tasks"] = {
[1] = {["number"] = 1, ["auto"] = true, ["id"] = "WrappedAction", ["enabled"] = true, ["params"] = {["action"] = {["id"] = "EPLRS", ["params"] = {["value"] = true}, }, }, },
[2] = {["number"] = 2, ["auto"] = false, ["id"] = "Orbit", ["enabled"] = true, ["params"] = {["pattern"] = "Circle"}, },
},
["type"] = "Turning Point",
["x"] = point.x,
["y"] = point.z,
},
},
}
elseif unitRecord["category"] == Unit.Category.HELICOPTER then
category = 'helicopter'
route = {
["points"] =
{
[1] =
{
["alt"] = alt,
["alt_type"] = "BARO",
["tasks"] = {
[1] = {["number"] = 1, ["auto"] = true, ["id"] = "WrappedAction", ["enabled"] = true, ["params"] = {["action"] = {["id"] = "EPLRS", ["params"] = {["value"] = true}, }, }, },
[2] = {["number"] = 2, ["auto"] = false, ["id"] = "Orbit", ["enabled"] = true, ["params"] = {["pattern"] = "Circle"}, },
},
["type"] = "Turning Point",
["x"] = point.x,
["y"] = point.z,
},
},
}
elseif unitRecord["category"] == Unit.Category.GROUND_UNIT then
category = 'vehicle'
elseif unitRecord["category"] == Unit.Category.SHIP then
category = 'ship'
end
end
unitsTable[#unitsTable + 1] = mist.utils.deepCopy(unitTable)
end
if deleteOriginal then
Olympus.delete(ID, false)
end
end
local vars =
{
units = unitsTable,
country = countryID,
category = category,
route = route,
name = "Olympus-" .. Olympus.unitCounter,
task = 'CAP'
}
Olympus.debug(Olympus.serializeTable(vars), 1)
-- Save the units in the database, for cloning
for idx, unitTable in pairs(unitsTable) do
Olympus.addToDatabase(unitTable)
end
Olympus.debug(Olympus.serializeTable(vars), 2)
mist.dynAdd(vars)
Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.debug("Olympus.clone completed successfully", 2)
end
-- Delete a unit by ID, optionally use an explosion
function Olympus.delete(ID, explosion, explosionType)
Olympus.debug("Olympus.delete " .. ID .. " " .. tostring(explosion), 2)
local unit = Olympus.getUnitByID(ID)
if unit ~= nil and unit:isExist() then
if unit:getPlayerName() or explosion then
if explosionType == nil then
explosionType = "normal"
end
local lat, lng, alt = coord.LOtoLL(unit:getPoint())
Olympus.explosion(250, explosionType, lat, lng, alt)
Olympus.debug("Olympus.delete completed successfully", 2)
else
unit:destroy(); --works for AI units not players
Olympus.debug("Olympus.delete completed successfully", 2)
end
end
end
-- Set a DCS main task to a group
function Olympus.setTask(groupName, taskOptions)
Olympus.debug("Olympus.setTask " .. groupName .. " " .. Olympus.serializeTable(taskOptions), 2)
local group = Group.getByName(groupName)
if group ~= nil then
local task = Olympus.buildTask(groupName, taskOptions);
Olympus.debug("Olympus.setTask " .. Olympus.serializeTable(task), 20)
if task then
group:getController():setTask(task)
Olympus.debug("Olympus.setTask completed successfully", 2)
end
end
end
-- Reset the task of a group
function Olympus.resetTask(groupName)
Olympus.debug("Olympus.resetTask " .. groupName, 2)
local group = Group.getByName(groupName)
if group ~= nil then
group:getController():resetTask()
Olympus.debug("Olympus.resetTask completed successfully", 2)
end
end
-- Give a group a command
function Olympus.setCommand(groupName, command)
Olympus.debug("Olympus.setCommand " .. groupName .. " " .. Olympus.serializeTable(command), 2)
local group = Group.getByName(groupName)
if group ~= nil then
group:getController():setCommand(command)
Olympus.debug("Olympus.setCommand completed successfully", 2)
end
end
-- Set an option of a group
function Olympus.setOption(groupName, optionID, optionValue)
Olympus.debug("Olympus.setOption " .. groupName .. " " .. optionID .. " " .. tostring(optionValue), 2)
local group = Group.getByName(groupName)
if group ~= nil then
group:getController():setOption(optionID, optionValue)
Olympus.debug("Olympus.setOption completed successfully", 2)
end
end
-- Disable the AI of a group on or off entirely
function Olympus.setOnOff(groupName, onOff)
Olympus.debug("Olympus.setOnOff " .. groupName .. " " .. tostring(onOff), 2)
local group = Group.getByName(groupName)
if group ~= nil then
group:getController():setOnOff(onOff)
Olympus.debug("Olympus.setOnOff completed successfully", 2)
end
end
function getUnitDescription(unit)
return unit:getDescr()
end
-- This function gets the navpoints from the DCS mission
function Olympus.getNavPoints()
local function extract_tag(str)
return str:match("^%[(.-)%]")
end
local navpoints = {}
if mist.DBs.navPoints ~= nil then
for coalitionName, coalitionNavpoints in pairs(mist.DBs.navPoints) do
if navpoints[coalitionName] == nil then
navpoints[coalitionName] = {}
end
for index, navpointDrawingData in pairs(coalitionNavpoints) do
local navpointCustomLayer = extract_tag(navpointDrawingData['callsignStr']);
-- Let's convert DCS coords to lat lon
local vec3 = { x = navpointDrawingData['x'], y = 0, z = navpointDrawingData['y'] }
local lat, lng = coord.LOtoLL(vec3)
navpointDrawingData['lat'] = lat
navpointDrawingData['lng'] = lng
navpointDrawingData['coalition'] = coalitionName
navpointDrawingData['tag'] = navpointCustomLayer
if navpointCustomLayer ~= nil then
if navpoints[coalitionName][navpointCustomLayer] == nil then
navpoints[coalitionName][navpointCustomLayer] = {}
end
navpoints[coalitionName][navpointCustomLayer][navpointDrawingData['callsignStr']] = navpointDrawingData
else
navpoints[coalitionName][navpointDrawingData['callsignStr']] = navpointDrawingData
end
end
end
end
return navpoints
end
-- This function is periodically called to collect the data of all the existing drawings in the mission to be transmitted to the olympus.dll
function Olympus.initializeDrawings()
local function extract_custom_layer_name(str)
if str:match("^%[LYR:(.-)%]") then
return str:match("^%[LYR:(.-)%]")
elseif str:match("^%[(.-)%]") then
return str:match("^%[(.-)%]")
end
return nil
end
local drawings = {}
if mist.DBs.drawingByName ~= nil then
for drawingName, drawingData in pairs(mist.DBs.drawingByName) do
local customLayer = extract_custom_layer_name(drawingName)
-- Let's convert DCS coords to lat lon
local vec3 = { x = drawingData['mapX'], y = 0, z = drawingData['mapY'] }
local lat, lng = coord.LOtoLL(vec3)
drawingData['lat'] = lat
drawingData['lng'] = lng
-- If the drawing has points, we have to convert those too
if drawingData['points'] ~= nil then
if drawingData['points']['x'] ~= nil then
-- In this case we have only one point
local point = { x = drawingData['points']['x'], y = 0, z = drawingData['points']['y'] }
local pointLat, pointLng = coord.LOtoLL(point)
drawingData['points'][0] = { lat = pointLat, lng = pointLng }
else
-- In this case we have multiple points indexed by number
for pointNumber, pointLOCoords in pairs(drawingData['points']) do
local point = { x = pointLOCoords['x'], y = 0, z = pointLOCoords['y'] }
local pointLat, pointLng = coord.LOtoLL(point)
drawingData['points'][pointNumber] = { lat = pointLat, lng = pointLng }
end
end
end
-- Let's initialize the layers
if drawings[drawingData.layerName] == nil then
drawings[drawingData.layerName] = {}
end
if customLayer and drawings[drawingData.layerName][customLayer] == nil then
drawings[drawingData.layerName][customLayer] = {}
end
-- Let's put the drawing in the correct layer
if customLayer then
-- Let's remove the m from the drawing name
local cleanDrawingName = string.match(drawingName, "%] (.+)")
drawingData.name = cleanDrawingName
-- The drawing has the custom layer m
drawings[drawingData.layerName][customLayer][cleanDrawingName] = drawingData
else
-- The drawing is a standard drawing
drawings[drawingData.layerName][drawingName] = drawingData
end
end
local navpoints = Olympus.getNavPoints()
drawings['navpoints'] = navpoints
Olympus.drawingsByLayer["drawings"] = drawings
-- Send the drawings to the DLL
Olympus.OlympusDLL.setDrawingsData()
Olympus.notify("Olympus drawings initialized", 2)
else
Olympus.debug("MIST DBs not ready", 2)
timer.scheduleFunction(Olympus.initializeDrawings, {}, timer.getTime() + 1)
end
end
-- This function is periodically called to collect the data of all the existing units in the mission to be transmitted to the olympus.dll
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
-- Only the indexes between startIndex and endIndex are handled. This is a simple way to spread the update load over many cycles
if index > startIndex then
if unit ~= nil and unit:isExist() then
local table = {}
-- Get the object category in Olympus name
local objectCategory = Object.getCategory(unit)
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"
elseif Olympus.modsList ~= nil and Olympus.modsList[unit:getDesc().typeName] ~= nil then
table["category"] = Olympus.modsList[unit:getDesc().typeName]
end
else
local status, description = pcall(getUnitDescription, unit)
if status and Olympus.modsList ~= nil and Olympus.modsList[description.typeName] ~= nil then
table["category"] = Olympus.modsList[description.typeName]
else
units[ID] = {isAlive = false}
Olympus.units[ID] = nil
end
end
-- If the category is handled by Olympus, get the data
if table["category"] ~= nil 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 )
local velocity = unit:getVelocity();
local airborne = unit:inAir()
-- 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(velocity)
table["horizontalVelocity"] = math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z)
table["verticalVelocity"] = velocity.y
table["heading"] = heading
table["airborne"] = airborne
-- Track angles are wrong because of weird reference systems, approximate it using latitude and longitude differences
if (table["horizontalVelocity"] > 1) then
if Olympus.unitsData["units"] ~= nil and Olympus.unitsData["units"][ID] ~= nil and Olympus.unitsData["units"][ID]["position"] ~= nil and Olympus.unitsData["units"][ID]["position"]["lat"] ~= nil and Olympus.unitsData["units"][ID]["position"]["lng"] ~= nil then
local latDifference = lat - Olympus.unitsData["units"][ID]["position"]["lat"]
local lngDifference = lng - Olympus.unitsData["units"][ID]["position"]["lng"]
table["track"] = math.atan2(lngDifference * math.cos(lat / 57.29577), latDifference)
else
table["track"] = math.atan2(velocity.z, velocity.x)
end
else
table["track"] = table["heading"]
end
table["isAlive"] = unit:isExist() and unit:isActive() and unit:getLife() >= 1
if unit:isActive() and unit:hasSensors(Unit.SensorType.RADAR) then
if unit:getRadar() then
table["radarState"] = true
else
table["radarState"] = false
end
end
local group = unit:getGroup()
if group ~= nil then
local controller = group:getController()
if controller ~= nil then
-- Get the targets detected by the unit controller
local contacts = {}
local unitController = unit:getController()
if unitController ~= nil then
for det, enum in pairs(Controller.Detection) do
local controllerTargets = unitController:getDetectedTargets(enum)
for i, target in ipairs(controllerTargets) do
if target ~= nil and target.object ~= nil and target.visible then
target["detectionMethod"] = det
contacts[#contacts + 1] = target
end
end
end
end
-- getLife0 does not seem to work for ships, so we need to keep a reference to the initial life of the unit
if Olympus.unitsInitialLife[ID] == nil then
Olympus.unitsInitialLife[ID] = unit:getLife()
end
-- Get the initial life of the unit to compute the current health
local initialLife = 1
if Olympus.unitsInitialLife[ID] ~= nil then
initialLife = Olympus.unitsInitialLife[ID]
end
table["country"] = unit:getCountry()
if unit:getPlayerName() ~= nil then
table["unitName"] = unit:getPlayerName()
else
table["unitName"] = unit:getName()
end
-- In case of AI units the callSign and the unitName will be the same
table["callsign"] = 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["health"] = unit:getLife() / initialLife * 100
table["contacts"] = contacts
local name = unit:getName()
-- If the unit is not in the clone database it means it was not spawned by Olympus. Let's try and recover it using mist
if Olympus.cloneDatabase[name] == nil then
if mist.DBs ~= nil and mist.DBs.unitsByName ~= nil and mist.DBs.unitsByName[name] ~= nil then
-- Payloads can be copied from ME units only TODO: can we fix this?
local payload = {}
if mist.DBs.MEunitsByName[name] then
payload = mist.getPayload(name)
end
-- Create a mock spawn table to generate the database
local unitsTable = nil
local spawnTable = {}
spawnTable.units = {
[1] = {
["unitType"] = table["name"],
["lat"] = table["position"]["lat"],
["lng"] = table["position"]["lng"],
["alt"] = table["position"]["alt"],
["payload"] = payload,
["liveryID"] = mist.DBs.unitsByName[name]["livery_id"]
}
}
-- Generate the units table as per DCS requirements
if table["category"] == 'Aircraft' then
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
elseif table["category"] == 'Helicopter' then
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
elseif table["category"] == 'GroundUnit' then
unitsTable = Olympus.generateGroundUnitsTable(spawnTable.units)
elseif table["category"] == 'NavyUnit' then
unitsTable = Olympus.generateNavyUnitsTable(spawnTable.units)
end
-- Save the units in the database, for cloning
for idx, unitTable in pairs(unitsTable) do
-- Force the name of the unit to be equal to the original name
unitTable["name"] = name
Olympus.addToDatabase(mist.utils.deepCopy(unitTable))
end
end
end
-- Update the database used for unit cloning
if Olympus.cloneDatabase[name] ~= nil then
Olympus.cloneDatabase[name]["ID"] = ID
Olympus.cloneDatabase[name]["category"] = unit:getDesc().category
Olympus.cloneDatabase[name]["heading"] = table["heading"]
Olympus.cloneDatabase[name]["alt"] = alt
Olympus.cloneDatabase[name]["country"] = unit:getCountry()
end
units[ID] = table
end
else
-- If the unit reference is nil it means the unit no longer exits
units[ID] = {isAlive = false}
Olympus.units[ID] = nil
end
end
else
-- If the unit reference is nil it means the unit no longer exits
units[ID] = {isAlive = false}
Olympus.units[ID] = nil
end
end
if index >= endIndex then
break
end
end
-- Reset the counter
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
-- This function is periodically called to collect the data of all the existing weapons in the mission to be transmitted to the olympus.dll
function Olympus.setWeaponsData(arg, time)
-- Weapons data
local weapons = {}
local startIndex = Olympus.weaponIndex
local endIndex = startIndex + Olympus.weaponStep
local index = 0
for ID, weapon in pairs(Olympus.weapons) do
index = index + 1
-- Only the indexes between startIndex and endIndex are handled. This is a simple way to spread the update load over many cycles
if index > startIndex then
if weapon ~= nil and weapon:isExist() then
local table = {}
-- Get the object category in Olympus name
local objectCategory = Object.getCategory(weapon)
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"
--elseif weapon:getDesc().category == Weapon.Category.SHELL then
-- table["category"] = "Shell" -- Useful for debugging but has no real use and has big impact on performance
end
else
weapons[ID] = {isAlive = false}
Olympus.weapons[ID] = nil
end
-- If the category is handled by Olympus, get the data
if table["category"] ~= nil 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
-- If the weapon reference is nil it means the unit no longer exits
weapons[ID] = {isAlive = false}
Olympus.weapons[ID] = nil
end
end
if index >= endIndex then
break
end
end
-- Reset the counter
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.25
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
}
mission.coalitions = {
["red"] = {},
["blue"] = {},
["neutral"] = {}
}
for countryName, countryId in pairs(country["id"]) do
local coalitionName = Olympus.getCoalitionByCoalitionID(coalition.getCountryCoalition(countryId))
mission.coalitions[coalitionName][#mission.coalitions[coalitionName] + 1] = countryName
end
-- Spots
-- Initialize an empty table to store spots
local spots = {}
-- Iterate over each spot in Olympus.spots
for ID, spot in pairs(Olympus.spots) do
-- Create a new entry in the spots table with the same ID
spots[ID] = {
type = spot.type,
sourceUnitID = spot.sourceUnitID,
targetPosition = spot.targetPosition,
active = spot.active,
}
-- If the spot type is "laser", add the code to the spot entry
if spot.type == "laser" then
spots[ID]["code"] = spot.code
end
end
-- Assemble table
Olympus.missionData["bullseyes"] = bullseyes
Olympus.missionData["airbases"] = airbases
Olympus.missionData["mission"] = mission
Olympus.missionData["spots"] = spots
Olympus.OlympusDLL.setMissionData()
return time + 1 -- For perfomance reasons mission data is updated once every second
end
-- Initializes the units table with all the existing ME units
function Olympus.initializeUnits()
if mist and mist.DBs and mist.DBs.MEunitsById then
for id, unitsTable in pairs(mist.DBs.MEunitsById) do
local unit = Unit.getByName(unitsTable["unitName"])
if unit ~= nil and unit:isExist() then
Olympus.units[unit["id_"]] = unit
end
end
Olympus.notify("Olympus units table initialized", 2)
else
Olympus.debug("MIST DBs not ready", 2)
timer.scheduleFunction(Olympus.initializeUnits, {}, timer.getTime() + 1)
end
end
------------------------------------------------------------------------------------------------------
-- Olympus utility functions
------------------------------------------------------------------------------------------------------
function Olympus.serializeTable(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0
local tmp = string.rep(" ", depth)
if name then
if type(name) == "number" then
tmp = tmp .. "[" .. name .. "]" .. " = "
else
tmp = tmp .. name .. " = "
end
end
if type(val) == "table" then
tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")
for k, v in pairs(val) do
tmp = tmp .. Olympus.serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "")
end
tmp = tmp .. string.rep(" ", depth) .. "}"
elseif type(val) == "number" then
tmp = tmp .. tostring(val)
elseif type(val) == "string" then
tmp = tmp .. string.format("%q", val)
elseif type(val) == "boolean" then
tmp = tmp .. (val and "true" or "false")
else
tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\""
end
return tmp
end
function Olympus.isArray(t)
local i = 0
for _ in pairs(t) do
i = i + 1
if t[i] == nil then return false end
end
return true
end
function Olympus.hasValue(tab, val)
for index, value in ipairs(tab) do
if value == val then
return true
end
end
return false
end
function Olympus.hasKey(tab, key)
for k, value in pairs(tab) do
if k == key then
return true
end
end
return false
end
------------------------------------------------------------------------------------------------------
-- Olympus startup script
------------------------------------------------------------------------------------------------------
Olympus.instancePath = lfs.writedir().."Mods\\Services\\Olympus\\bin\\"
Olympus.notify("Starting DCS Olympus backend session in "..Olympus.instancePath, 2)
local OlympusName = 'Olympus ' .. version .. ' C++ module';
Olympus.DLLsloaded = Olympus.loadDLLs()
if Olympus.DLLsloaded then
Olympus.notify(OlympusName..' successfully loaded.', 20)
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)
if event.id == 1 then
local weapon = event.weapon
if Olympus ~= nil and Olympus.weapons ~= nil then
Olympus.weapons[weapon["id_"]] = weapon
Olympus.debug("New weapon created " .. weapon["id_"], 2)
end
elseif event.id == 15 then
local unit = event.initiator
if Olympus ~= nil and Olympus.units ~= nil then
Olympus.units[unit["id_"]] = unit
Olympus.debug("New unit created " .. unit["id_"], 2)
end
end
end
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
Olympus.initializeUnits()
-- Initialize the Drawings
Olympus.initializeDrawings()
Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true)