DCSOlympus/scripts/OlympusCommand.lua

768 lines
21 KiB
Lua

local version = "v0.3.0-alpha"
local debug = true
Olympus.unitCounter = 1
Olympus.payloadRegistry = {}
Olympus.groupIndex = 0
Olympus.groupStep = 40
Olympus.OlympusDLL = nil
Olympus.DLLsloaded = false
Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\'
function Olympus.debug(message, displayFor)
if debug == true then
trigger.action.outText(message, displayFor)
end
end
function Olympus.notify(message, displayFor)
trigger.action.outText(message, displayFor)
end
function Olympus.loadDLLs()
-- Add the .dll paths
package.cpath = package.cpath..';'..Olympus.OlympusModPath..'?.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)
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
end
function Olympus.getCountryIDByCoalition(coalition)
local countryID = 0
if coalition == 'red' then
countryID = country.id.CJTF_RED
elseif coalition == 'blue' then
countryID = country.id.CJTF_BLUE
else
countryID = country.id.UN_PEACEKEEPERS
end
return countryID
end
function Olympus.getCoalitionByCoalitionID(coalitionID)
local coalition = "neutral"
if coalitionID == 1 then
coalition = "red"
elseif coalitionID == 2 then
coalition = "blue"
end
return coalition
end
-- Builds a valid 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 task depending on the provided options
function Olympus.buildTask(groupName, options)
local task = nil
local group = Group.getByName(groupName)
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
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
elseif options['id'] == 'Refuel' then
task = {
id = 'Refueling',
params = {}
}
elseif options['id'] == 'Orbit' then
task = {
id = 'Orbit',
params = {
pattern = options['pattern'] or "Circle"
}
}
if options['altitude'] then
if options ['altitudeType'] then
if options ['altitudeType'] == "AGL" then
local groundHeight = 0
if group 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
if options['speed'] then
task['params']['speed'] = options['speed']
end
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
}
}
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
}
}
elseif options['id'] == 'AttackMapObject' and options['lat'] and options['lng'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'AttackMapObject',
params = {
point = {x = point.x, y = point.z}
}
}
elseif options['id'] == 'FireAtPoint' and options['lat'] and options['lng'] and options['radius'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'FireAtPoint',
params = {
point = {x = point.x, y = point.z},
radius = options['radius']
}
}
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 then
if category == "Aircraft" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
if altitudeType == "AGL" then
altitude = land.getHeight({x = endPoint.x, y = endPoint.z}) + altitude
end
local path = {}
if taskOptions and taskOptions['id'] == 'Land' then
path = {
[1] = mist.fixedWing.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, landing, speed, 0, 'AGL')
}
else
path = {
[1] = mist.fixedWing.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
end
-- 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 a Aircraft", 2)
elseif category == "GroundUnit" then
vars =
{
group = group,
point = coord.LLtoLO(lat, lng, 0),
heading = 0,
speed = speed
}
if taskOptions and taskOptions['id'] == 'FollowRoads' and taskOptions['value'] == true then
vars["disableRoads"] = false
else
vars["form"] = "Off Road"
vars["disableRoads"] = true
end
mist.groupToRandomPoint(vars)
Olympus.debug("Olympus.move executed succesfully on a ground unit", 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, lat, lng)
Olympus.debug("Olympus.explosion " .. intensity .. " (" .. lat .. ", " .. lng ..")", 2)
trigger.action.explosion(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)), intensity)
end
-- Spawns a single ground unit
function Olympus.spawnGroundUnit(coalition, unitType, lat, lng)
Olympus.debug("Olympus.spawnGroundUnit " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..")", 2)
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
local unitTable = {}
if Olympus.hasKey(templates, unitType) then
for idx, value in pairs(templates[unitType].units) do
unitTable[#unitTable + 1] = {
["type"] = value.name,
["x"] = spawnLocation.x + value.dx,
["y"] = spawnLocation.z + value.dy,
["playerCanDrive"] = true,
["heading"] = 0,
["skill"] = "High"
}
end
else
unitTable =
{
[1] =
{
["type"] = unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["playerCanDrive"] = true,
["heading"] = 0,
["skill"] = "High"
},
}
end
local countryID = Olympus.getCountryIDByCoalition(coalition)
local vars =
{
units = unitTable,
country = countryID,
category = 'vehicle',
name = "Olympus-" .. Olympus.unitCounter,
}
mist.dynAdd(vars)
Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.debug("Olympus.spawnGround completed succesfully", 2)
end
-- Spawns a single aircraft. Spawn options are:
-- payloadName: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType
-- airbaseName: a string, if present the aircraft will spawn on the ground of the selected airbase
-- payload: a table, if present the unit will receive this specific payload. Overrides payloadName
function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions)
local payloadName = spawnOptions["payloadName"]
local airbaseName = spawnOptions["airbaseName"]
local payload = spawnOptions["payload"]
Olympus.debug("Olympus.spawnAircraft " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..", " .. alt .. ")", 2)
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
if payload == nil then
if payloadName and payloadName ~= "" and Olympus.unitPayloads[unitType][payloadName] then
payload = Olympus.unitPayloads[unitType][payloadName]
else
payload = {}
end
end
local countryID = Olympus.getCountryIDByCoalition(coalition)
local unitTable =
{
[1] =
{
["type"] = unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["alt"] = alt,
["alt_type"] = "BARO",
["skill"] = "Excellent",
["payload"] =
{
["pylons"] = payload,
["fuel"] = 999999,
["flare"] = 60,
["ammo_type"] = 1,
["chaff"] = 60,
["gun"] = 100,
},
["heading"] = 0,
["callsign"] =
{
[1] = 1,
[2] = 1,
[3] = 1,
["name"] = "Olympus" .. Olympus.unitCounter,
},
["name"] = "Olympus-" .. Olympus.unitCounter
},
}
-- 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",
["task"] =
{
["id"] = "ComboTask",
["params"] = {["tasks"] = {},},
},
["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",
["task"] =
{
["id"] = "ComboTask",
["params"] =
{
["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 of [1]
}, -- end of ["points"]
} -- end of ["route"]
end
local vars =
{
units = unitTable,
country = countryID,
category = 'airplane',
name = "Olympus-" .. Olympus.unitCounter,
route = route,
task = 'CAP',
}
local newGroup = mist.dynAdd(vars)
-- Save the payload to be reused in case the unit is cloned. TODO: save by ID not by name (it works but I like consistency)
Olympus.payloadRegistry[vars.name] = payload
Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.debug("Olympus.spawnAir completed successfully", 2)
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.
function Olympus.clone(ID, lat, lng, category)
Olympus.debug("Olympus.clone " .. ID .. ", " .. category, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition())
if category == "Aircraft" then
local spawnOptions = {
payload = Olympus.payloadRegistry[unit:getName()]
}
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, unit:getPoint().y, spawnOptions)
elseif category == "GroundUnit" then
Olympus.spawnGroundUnit(coalition, unit:getTypeName(), lat, lng)
end
end
Olympus.debug("Olympus.clone completed successfully", 2)
end
function Olympus.delete(ID, explosion)
Olympus.debug("Olympus.delete " .. ID .. " " .. tostring(explosion), 2)
local unit = Olympus.getUnitByID(ID)
if unit then
if unit:getPlayerName() or explosion then
trigger.action.explosion(unit:getPoint() , 250 ) --consider replacing with forcibly deslotting the player, however this will work for now
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
function Olympus.setTask(groupName, taskOptions)
Olympus.debug("Olympus.setTask " .. groupName .. " " .. Olympus.serializeTable(taskOptions), 2)
local group = Group.getByName(groupName)
if group 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
function Olympus.resetTask(groupName)
Olympus.debug("Olympus.resetTask " .. groupName, 2)
local group = Group.getByName(groupName)
if group then
group:getController():resetTask()
Olympus.debug("Olympus.resetTask completed successfully", 2)
end
end
function Olympus.setCommand(groupName, command)
Olympus.debug("Olympus.setCommand " .. groupName .. " " .. Olympus.serializeTable(command), 2)
local group = Group.getByName(groupName)
if group then
group:getController():setCommand(command)
Olympus.debug("Olympus.setCommand completed successfully", 2)
end
end
function Olympus.setOption(groupName, optionID, optionValue)
Olympus.debug("Olympus.setOption " .. groupName .. " " .. optionID .. " " .. tostring(optionValue), 2)
local group = Group.getByName(groupName)
if group then
group:getController():setOption(optionID, optionValue)
Olympus.debug("Olympus.setOption completed successfully", 2)
end
end
function Olympus.setOnOff(groupName, onOff)
Olympus.debug("Olympus.setOnOff " .. groupName .. " " .. tostring(onOff), 2)
local group = Group.getByName(groupName)
if group then
group:getController():setOnOff(onOff)
Olympus.debug("Olympus.setOnOff completed successfully", 2)
end
end
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
function Olympus.setMissionData(arg, time)
local missionData = {}
-- 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
-- Units tactical data
local unitsData = {}
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()
for index, unit in pairs(group:getUnits()) do
local unitController = unit:getController()
local table = {}
table["contacts"] = {}
for i, target in ipairs(controllerTargets) do
for det, enum in pairs(Controller.Detection) do
if target.object ~= nil then
local detected = unitController:isTargetDetected(target.object, enum)
if detected then
target["detectionMethod"] = det
table["contacts"][#table["contacts"] + 1] = target
end
end
end
end
table["hasTask"] = controller:hasTask()
table["ammo"] = unit:getAmmo()
table["fuel"] = unit:getFuel()
table["life"] = unit:getLife() / unit:getLife0()
unitsData[unit:getObjectID()] = table
end
end
end
end
if index >= endIndex then
break
end
end
if index ~= endIndex then
Olympus.groupIndex = 0
else
Olympus.groupIndex = endIndex
end
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
local mission = {}
mission.theatre = env.mission.theatre
mission.elapsedTime = DCS.getRealTime()
mission.time = mist.time.getDHMS(timer.getAbsTime())
mission.startTime = env.mission.start_time
mission.date = env.mission.date
-- Assemble missionData table
missionData["bullseyes"] = bullseyes
missionData["unitsData"] = unitsData
missionData["airbases"] = airbases
missionData["mission"] = mission
Olympus.missionData = missionData
Olympus.OlympusDLL.setMissionData()
return time + 1
end
local OlympusName = 'Olympus ' .. version .. ' C++ module';
isOlympusModuleInitialized=true;
Olympus.DLLsloaded = Olympus.loadDLLs()
if Olympus.DLLsloaded then
Olympus.notify(OlympusName..' successfully loaded.', 20)
else
Olympus.notify('Failed to load '..OlympusName, 20)
end
timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1)
Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true)