mirror of
https://github.com/akaAgar/the-universal-mission-for-dcs-world.git
synced 2025-11-25 19:31:01 +00:00
Initial commit
This commit is contained in:
453
Script/DCS extensions/UnitGroupMaker.lua
Normal file
453
Script/DCS extensions/UnitGroupMaker.lua
Normal file
@@ -0,0 +1,453 @@
|
||||
-- ====================================================================================
|
||||
-- DCSEX.UNITGROUMAKER - CREATES AND ADDS GROUPS TO THE GAME WORLD
|
||||
--
|
||||
-- (local) createGroupTable(groupID, groupCategory, options)
|
||||
-- (local) getDefaultUnitSpread(groupCategory)
|
||||
-- (local) getNextGroupID()
|
||||
-- (local) getNextUnitID()
|
||||
-- (local) setAircraftTaskAwacs(groupTable)
|
||||
-- (local) setAircraftTaskOrbit(groupTable, options)
|
||||
-- (local) setCommand(groupTable, actionID, actionValue)
|
||||
-- (local) setOption(groupTable, optionID, optionValue)
|
||||
-- DCSEx.unitGroupMaker.create(coalitionID, groupCategory, vec2, unitTypes, options)
|
||||
-- DCSEx.unitGroupMaker.initialize()
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.unitGroupMaker = {}
|
||||
|
||||
do
|
||||
local nextGroupID = 1 -- ID of the next generated group
|
||||
local nextUnitID = 1 -- ID of the next generated unit
|
||||
|
||||
local function createGroupTable(groupID, groupCategory, options)
|
||||
local groupTable = {
|
||||
groupId = groupID,
|
||||
hidden = options.hidden,
|
||||
name = options.name,
|
||||
route = {points = {}},
|
||||
start_time = 0,
|
||||
taskSelected = true,
|
||||
uncontrollable = true,
|
||||
visible = false,
|
||||
units = {},
|
||||
}
|
||||
|
||||
if groupCategory == Group.Category.GROUND then
|
||||
groupTable.task = "Ground Nothing"
|
||||
groupTable.tasks = {}
|
||||
elseif groupCategory == Group.Category.AIRPLANE or groupCategory == Group.Category.HELICOPTER then
|
||||
groupTable.uncontrolled = options.uncontrolled or false
|
||||
end
|
||||
|
||||
return groupTable
|
||||
end
|
||||
|
||||
local function getDefaultUnitSpread(groupCategory)
|
||||
if groupCategory == Group.Category.AIRPLANE or groupCategory == Group.Category.HELICOPTER then
|
||||
return 150 -- TODO: improve
|
||||
elseif groupCategory == Group.Category.SHIP then
|
||||
return math.random(250, 400)
|
||||
else
|
||||
return math.random(20, 30)
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns the next available group ID and increment the number
|
||||
local function getNextGroupID()
|
||||
nextGroupID = nextGroupID + 1
|
||||
return nextGroupID - 1
|
||||
end
|
||||
|
||||
-- Returns the next available unit ID and increment the number
|
||||
local function getNextUnitID()
|
||||
nextUnitID = nextUnitID + 1
|
||||
return nextUnitID - 1
|
||||
end
|
||||
|
||||
local function setAircraftTaskAwacs(groupTable)
|
||||
groupTable.frequency = 251000000
|
||||
groupTable.task = "AWACS"
|
||||
|
||||
table.insert(groupTable.route.points[1].task.params.tasks,
|
||||
{
|
||||
["enabled"] = true,
|
||||
["auto"] = true,
|
||||
["id"] = "AWACS",
|
||||
["number"] = #groupTable.route.points[1].task.params.tasks + 1,
|
||||
["params"] = { },
|
||||
})
|
||||
|
||||
return groupTable
|
||||
end
|
||||
|
||||
local function setAircraftTaskCAP(groupTable)
|
||||
groupTable.task = "CAP"
|
||||
|
||||
table.insert(groupTable.route.points[1].task.params.tasks,
|
||||
{
|
||||
["enabled"] = true,
|
||||
["auto"] = true,
|
||||
["id"] = "EngageTargets",
|
||||
["number"] = #groupTable.route.points[1].task.params.tasks + 1,
|
||||
["params"] = {
|
||||
maxDist = DCSEx.converter.nmToMeters(60),
|
||||
maxDistEnabled = false,
|
||||
-- targetTypes = { "Planes", "Helicopters" },
|
||||
targetTypes = { "Fighters", "Interceptors", "Multirole fighters" },
|
||||
priority = 0
|
||||
},
|
||||
})
|
||||
|
||||
return groupTable
|
||||
end
|
||||
|
||||
local function setAircraftTaskOrbit(groupTable, options)
|
||||
-- TODO: oval orbit
|
||||
table.insert(groupTable.route.points[#groupTable.route.points].task.params.tasks,
|
||||
{
|
||||
["enabled"] = true,
|
||||
["auto"] = false,
|
||||
["id"] = "Orbit",
|
||||
["number"] = #groupTable.route.points[#groupTable.route.points].task.params.tasks + 1,
|
||||
["params"] =
|
||||
{
|
||||
["altitude"] = options.altitude,
|
||||
["pattern"] = "Circle",
|
||||
["speed"] = options.speed,
|
||||
},
|
||||
})
|
||||
|
||||
return groupTable
|
||||
end
|
||||
|
||||
local function setCommand(groupTable, actionID, actionValue)
|
||||
table.insert(
|
||||
groupTable.route.points[1].task.params.tasks,
|
||||
{
|
||||
["auto"] = false,
|
||||
["enabled"] = true,
|
||||
["id"] = "WrappedAction",
|
||||
["number"] = #groupTable.route.points[1].task.params.tasks + 1,
|
||||
["params"] = { ["action"] = { ["id"] = actionID, ["params"] = { ["value"] = actionValue, }, }, },
|
||||
})
|
||||
end
|
||||
|
||||
local function setOption(groupTable, optionID, optionValue)
|
||||
table.insert(
|
||||
groupTable.route.points[1].task.params.tasks,
|
||||
{
|
||||
["auto"] = false,
|
||||
["enabled"] = true,
|
||||
["id"] = "WrappedAction",
|
||||
["number"] = #groupTable.route.points[1].task.params.tasks + 1,
|
||||
["params"] = { ["action"] = { ["id"] = "Option", ["params"] = { ["name"] = optionID, ["value"] = optionValue, }, }, },
|
||||
})
|
||||
end
|
||||
|
||||
function DCSEx.unitGroupMaker.createStatic(side, point2, typeName, shapeName, heading, dead)
|
||||
heading = heading or DCSEx.converter.degreesToRadians(math.random(0, 359))
|
||||
dead = dead or false
|
||||
local unitID = getNextUnitID()
|
||||
|
||||
local staticObj = {
|
||||
["heading"] = 0,
|
||||
["groupId"] = getNextGroupID(),
|
||||
["shape_name"] = shapeName,
|
||||
["type"] = typeName,
|
||||
["unitId"] = unitID,
|
||||
["rate"] = 100,
|
||||
["name"] = "Static #"..tostring(unitID),
|
||||
["category"] = "Fortifications",
|
||||
["y"] = point2.y,
|
||||
["x"] = point2.x,
|
||||
["dead"] = dead,
|
||||
}
|
||||
|
||||
coalition.addStaticObject(DCSEx.dcs.getCJTFForCoalition(side), staticObj)
|
||||
|
||||
return unitID
|
||||
end
|
||||
|
||||
function DCSEx.unitGroupMaker.create(coalitionID, groupCategory, vec2, unitTypes, options)
|
||||
if not unitTypes or #unitTypes == 0 then return nil end -- No unit types provided
|
||||
if type(unitTypes) == "string" then unitTypes = { unitTypes } end -- Single unit type provided, make it a table of size 1
|
||||
|
||||
local aircraftDB = nil -- Aircraft entry in the DB for airplane/helicopter units
|
||||
local destVec2 = nil -- Destination point (for moving units)
|
||||
local groupID = getNextGroupID() -- Get a new unique ID for the group
|
||||
local isAirUnit = false
|
||||
|
||||
local hidden = false
|
||||
if not TUM.DEBUG_MODE and coalitionID ~= TUM.settings.getPlayerCoalition() then
|
||||
hidden = true
|
||||
end
|
||||
|
||||
-- Setup options
|
||||
options = options or {}
|
||||
--options.heading = nil
|
||||
options.altitude = options.altitude or 0
|
||||
options.altitudeType = options.altitudeType or "BARO"
|
||||
options.hidden = hidden
|
||||
options.isMoving = false
|
||||
options.livery = options.livery or "default"
|
||||
options.name = DCSEx.unitNamesMaker.getName(groupID, unitTypes)
|
||||
options.pointAction = "Turning Point"
|
||||
options.skill = options.skill or "Average"
|
||||
options.speed = 5.5555556
|
||||
options.spreadDistance = options.spreadDistance or getDefaultUnitSpread(groupCategory)
|
||||
options.spreadOffset = options.spreadOffset or 0
|
||||
|
||||
-- Movement point (for units with a second WP)
|
||||
if options.moveTo then
|
||||
options.isMoving = true
|
||||
destVec2 = DCSEx.table.deepCopy(options.moveTo)
|
||||
elseif options.moveBy then
|
||||
options.isMoving = true
|
||||
local angle = DCSEx.converter.degreesToRadians(math.random(0, 359))
|
||||
destVec2 = { x = vec2.x + math.cos(angle) * options.moveBy, y = vec2.y + math.sin(angle) * options.moveBy }
|
||||
end
|
||||
|
||||
-- Category specific options
|
||||
if groupCategory == Group.Category.GROUND then
|
||||
options.pointAction = "Off Road"
|
||||
|
||||
local desc = Unit.getDescByName(unitTypes[1])
|
||||
if desc and desc.attributes and desc.attributes.Infantry then options.speed = 1.66667 end
|
||||
|
||||
-- Check position and formation for moving ground units
|
||||
if options.isMoving then
|
||||
if options.onRoad then
|
||||
options.pointAction = "On Road"
|
||||
vec2 = DCSEx.world.getClosestPointOnRoadsVec2(vec2)
|
||||
destVec2 = DCSEx.world.getClosestPointOnRoadsVec2(destVec2)
|
||||
else
|
||||
options.pointAction = options.formation or DCSEx.table.getRandom({"Rank", "Cone", "Vee", "Diamond", "EchelonL", "EchelonR"})
|
||||
end
|
||||
end
|
||||
|
||||
options.livery = "default" -- TODO: getSeasonalLivery()
|
||||
elseif groupCategory == Group.Category.AIRPLANE or groupCategory == Group.Category.HELICOPTER then
|
||||
isAirUnit = true
|
||||
|
||||
-- Plane/helicopter groups always use a single unit type
|
||||
for i=1,#unitTypes do
|
||||
unitTypes[i] = unitTypes[1]
|
||||
end
|
||||
|
||||
-- Get unit info from aircraft database
|
||||
aircraftDB = Library.aircraft[unitTypes[1]]
|
||||
if not aircraftDB then return nil end -- Unit wasn't found in the database, abort group creation
|
||||
|
||||
-- Pick a random livery if available
|
||||
-- if aircraftDB.liveries and aircraftDB.liveries[coalitionID] then
|
||||
-- options.livery = table.getRandom(aircraftDB.liveries[coalitionID])
|
||||
-- end
|
||||
|
||||
options.altitude = aircraftDB.altitude or DCSEx.converter.feetToMeters(15000)
|
||||
options.altitudeType = "BARO"
|
||||
options.speed = DCSEx.math.randomFloat(0.9, 1.1) * (aircraftDB.speed or 250)
|
||||
end
|
||||
|
||||
-- First unit of the group is a template, use a group template instead of enumerating the types in unitTypes
|
||||
local groupTemplate = nil
|
||||
if Library.groupTemplates[unitTypes[1]] then
|
||||
groupTemplate = DCSEx.table.deepCopy(Library.groupTemplates[unitTypes[1]])
|
||||
unitTypes = {}
|
||||
for i=1,#groupTemplate do
|
||||
table.insert(unitTypes, groupTemplate[i].name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Create group table
|
||||
local groupTable = createGroupTable(groupID, groupCategory, options)
|
||||
groupTable.x = vec2.x
|
||||
groupTable.y = vec2.y
|
||||
|
||||
-- Initial waypoint
|
||||
table.insert(
|
||||
groupTable.route.points,
|
||||
{
|
||||
action = options.pointAction,
|
||||
alt = options.altitude,
|
||||
alt_type = options.altitudeType,
|
||||
ETA = 0.0,
|
||||
ETA_locked = false,
|
||||
formation_template = "",
|
||||
name = "WP1",
|
||||
speed = options.speed,
|
||||
speed_locked = true,
|
||||
task = {id = "ComboTask", params = {tasks = {}}},
|
||||
type = "Turning Point",
|
||||
x = vec2.x,
|
||||
y = vec2.y
|
||||
}
|
||||
)
|
||||
|
||||
if options.takeOff and (groupCategory == Group.Category.AIRPLANE or groupCategory == Group.Category.HELICOPTER) then
|
||||
groupTable.route.points[1].alt = 250
|
||||
groupTable.route.points[1].alt_type = "RADIO"
|
||||
end
|
||||
|
||||
if destVec2 then -- There's a destination, add a second waypoint
|
||||
local destPoint = {
|
||||
action = options.pointAction,
|
||||
alt_type = options.altitudeType,
|
||||
alt = options.altitude,
|
||||
ETA = 0.0,
|
||||
ETA_locked = false,
|
||||
formation_template = "",
|
||||
name = "WP2",
|
||||
speed = options.speed,
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = { tasks = { } }
|
||||
},
|
||||
type = "Turning Point",
|
||||
x = destVec2.x,
|
||||
y = destVec2.y,
|
||||
speed_locked = true
|
||||
}
|
||||
|
||||
-- Ground/ship groups loop between their waypoints
|
||||
if groupCategory == Group.Category.GROUND or groupCategory == Group.Category.SHIP then
|
||||
if not options.noLoop then
|
||||
table.insert(
|
||||
destPoint.task.params.tasks,
|
||||
{
|
||||
enabled = true,
|
||||
auto = false,
|
||||
id = "GoToWaypoint",
|
||||
number = 1,
|
||||
params = {
|
||||
fromWaypointIndex = 2,
|
||||
nWaypointIndx = 1
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(groupTable.route.points, destPoint)
|
||||
end
|
||||
|
||||
-- Add various options/commands
|
||||
if options.disableWeapons then setOption(groupTable, AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD) end -- Values are from the AI.Option.Ground tables, but they're the same for all (ROE=0, WEAPON_HOLD=4)
|
||||
if options.immortal then setCommand(groupTable, "SetImmortal", true) end
|
||||
if options.invisible then setCommand(groupTable, "SetInvisible", true) end
|
||||
if options.silenced then setOption(groupTable, AI.Option.Air.id.SILENCE, true) end
|
||||
if options.unlimitedFuel then setCommand(groupTable, "SetUnlimitedFuel", true) end
|
||||
|
||||
local groupCallsign = nil
|
||||
|
||||
if isAirUnit then
|
||||
if options.taskAwacs then setAircraftTaskAwacs(groupTable) end
|
||||
if options.taskCAP then setAircraftTaskCAP(groupTable) end
|
||||
setAircraftTaskOrbit(groupTable, options)
|
||||
groupCallsign = DCSEx.unitCallsignMaker.getCallsign(unitTypes[1])
|
||||
groupTable.name = groupCallsign.name
|
||||
end
|
||||
|
||||
local unitsID = {}
|
||||
|
||||
-----------------------
|
||||
-- Create units
|
||||
-----------------------
|
||||
for i=1,#unitTypes do
|
||||
local unitID = getNextUnitID()
|
||||
table.insert(unitsID, unitID)
|
||||
|
||||
local unitHeading, unitOffset
|
||||
|
||||
if groupTemplate then -- Use offset and heading from the group
|
||||
unitHeading = groupTemplate[i].heading
|
||||
unitOffset = { x = groupTemplate[i].dx, y = groupTemplate[i].dy }
|
||||
else -- Create offset and heading
|
||||
unitHeading = options.heading or DCSEx.math.randomFloat(0, DCSEx.math.TWO_PI)
|
||||
unitOffset =
|
||||
DCSEx.math.multVec2(
|
||||
DCSEx.math.getVec2FromAngle(unitHeading),
|
||||
options.spreadDistance * (options.spreadOffset + i - 1)
|
||||
)
|
||||
end
|
||||
|
||||
local unitType = unitTypes[i]
|
||||
|
||||
local unitTable = {
|
||||
coldAtStart = false,
|
||||
heading = unitHeading,
|
||||
livery = options.livery,
|
||||
name = options.name.." #"..tostring(i),
|
||||
playerCanDrive = false,
|
||||
skill = options.skill,
|
||||
transportable = {randomTransportable = false},
|
||||
type = unitType,
|
||||
unitId = unitID,
|
||||
x = vec2.x + unitOffset.x,
|
||||
y = vec2.y + unitOffset.y
|
||||
}
|
||||
|
||||
if isAirUnit and aircraftDB then
|
||||
unitTable.hardpoint_racks = true
|
||||
unitTable.psi = 1.7703702498393
|
||||
|
||||
unitTable.callsign = DCSEx.table.deepCopy(groupCallsign)
|
||||
unitTable.callsign.name = unitTable.callsign.name..tostring(i)
|
||||
unitTable.callsign[3] = i
|
||||
unitTable.name = unitTable.callsign.name
|
||||
|
||||
-- Special properties for unit
|
||||
if aircraftDB.properties then
|
||||
unitTable.AddPropAircraft = DCSEx.table.deepCopy(aircraftDB.properties)
|
||||
end
|
||||
|
||||
-- Common payload (fuel, gun ammo, etc)
|
||||
if aircraftDB.payload then
|
||||
unitTable.payload = DCSEx.table.deepCopy(aircraftDB.payload)
|
||||
end
|
||||
|
||||
if aircraftDB.pylons then
|
||||
-- TODO: pylons according to task
|
||||
if options.taskAttack and aircraftDB.pylons.attack then
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.attack)
|
||||
elseif options.taskCAP and aircraftDB.pylons.cap then
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.cap)
|
||||
elseif options.taskSEAD and aircraftDB.pylons.sead then
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.sead)
|
||||
elseif options.taskStrike and aircraftDB.pylons.strike then
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.strike)
|
||||
else
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(groupTable.units, unitTable)
|
||||
end
|
||||
|
||||
coalition.addGroup(DCSEx.dcs.getCJTFForCoalition(coalitionID), groupCategory, groupTable)
|
||||
|
||||
return {
|
||||
callsign = groupCallsign,
|
||||
groupID = groupID,
|
||||
unitsID = unitsID,
|
||||
unitTypeNames = DCSEx.table.deepCopy(unitTypes) -- Not always the same as the unitTypes input parameter, because of group templates
|
||||
}
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
-- INITIALIZE THE GROUP MAKER
|
||||
-- Look for maximum groupID and unitID already in use to make sure we don't use an existing ID when spawning units
|
||||
------------------------------------------------------
|
||||
local groupData = DCSEx.envMission.getGroups()
|
||||
|
||||
for _,g in ipairs(groupData) do
|
||||
if g.groupId >= nextGroupID then
|
||||
nextGroupID = g.groupId + 1
|
||||
end
|
||||
|
||||
for __,u in ipairs(g.units) do
|
||||
if u.unitId >= nextUnitID then
|
||||
nextUnitID = u.unitId + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user