DCS-CTLD/CTLD.lua
2015-05-09 17:09:56 +01:00

1701 lines
49 KiB
Lua

--[[
Combat Troop and Logistics Drop
Allows Huey, Mi-8 and C130 to transport troops internally and Helicopters to transport Logistic / Vehicle units to the field via slingloads
without requiring external mods via sling loads
Once a crate is dropped in the field, it must be unpacked by landing next to it and using the radio menu to start the unpack
Some vehicles may require more than one crate in close proximity
Troop
Supports usual CTTS functions such as spawn group
Huey max carry weight = 4000lb / 1814.37 kg
Mi-8 Max carry weight = 6614lb / 3000 kg
C-130 Max Carry Weight - 26,634 lb
- Two HMMWV (ATGM + MG ) and 10 troops
- It can actually hold 92 ground troops, 64 fully equipped paratroopers, or 74 litter patients
- Source http://fas.org/man/dod-101/sys/ac/c-130.htm
]]
ctld = {}
-- ************************************************************************
-- ********************* USER CONFIGURATION ******************************
-- ************************************************************************
ctld.disableAllSmoke = false -- if true, all smoke is diabled regardless of settings below. Leave false to respect settings below
ctld.enableCrates = true -- if false, Helis will not be able to spawn or unpack crates so will be normal CTTS
ctld.enableSmokeDrop = true -- if false, helis and c-130 will not be able to drop smoke
ctld.maxExtractDistance = 125 -- max distance from vehicle to troops to allow a group extraction
ctld.maximumDistanceLogistic = 200 -- max distance from vehicle to logistics to allow a loading or spawning operation
ctld.maximumSearchDistance = 4000 -- max distance for troops to search for enemy
ctld.maximumMoveDistance = 1000 -- max distance for troops to move from drop point if no enemy is nearby
ctld.numberOfTroops = 10 -- default number of troops to load on a transport heli or C-130
ctld.vehiclesForTransport = { "M1045 HMMWV TOW", "M1043 HMMWV Armament" } -- vehicles to load onto c130 or hercules
ctld.spawnRPGWithCoalition = true --spawns a friendly RPG unit with Coalition forces
-- ***************** Pickup and dropoff zones *****************
-- Available colors (anything else like "none" disables smoke): "green", "red", "white", "orange", "blue", "none",
-- Use any of the predefined names or set your own ones
ctld.pickupZones = {
{ "pickzone1", "blue" },
{ "pickzone2", "blue" },
{ "pickzone3", "none" },
{ "pickzone4", "none" },
{ "pickzone5", "none" },
{ "pickzone6", "none" },
{ "pickzone7", "none" },
{ "pickzone8", "none" },
{ "pickzone9", "none" },
{ "pickzone10", "none" },
}
ctld.dropOffZones = {
{ "dropzone1", "red" },
{ "dropzone2", "blue" },
{ "dropzone3", "none" },
{ "dropzone4", "none" },
{ "dropzone5", "none" },
{ "dropzone6", "none" },
{ "dropzone7", "none" },
{ "dropzone8", "none" },
{ "dropzone9", "none" },
{ "dropzone10", "none" },
}
-- ******************** Transports names **********************
-- Use any of the predefined names or set your own ones
ctld.transportPilotNames = {
"helicargo1",
"helicargo2",
"helicargo3",
"helicargo4",
"helicargo5",
"helicargo6",
"helicargo7",
"helicargo8",
"helicargo9",
"helicargo10",
"helicargo11",
"helicargo12",
"helicargo13",
"helicargo14",
"helicargo15",
"helicargo16",
"helicargo17",
"helicargo18",
"helicargo19",
"helicargo20",
"helicargo21",
"helicargo22",
"helicargo23",
"helicargo24",
"helicargo25",
-- *** AI transports names (different names only to ease identification in mission) ***
-- Use any of the predefined names or set your own ones
"transport1",
"transport2",
"transport3",
"transport4",
"transport5",
"transport6",
"transport7",
"transport8",
"transport9",
"transport10",
"transport11",
"transport12",
"transport13",
"transport14",
"transport15",
"transport16",
"transport17",
"transport18",
"transport19",
"transport20",
"transport21",
"transport22",
"transport23",
"transport24",
"transport25",
}
-- *************** Optional Extractable GROUPS *****************
-- Use any of the predefined names or set your own ones
ctld.extractableGroups = {
"extract1",
"extract2",
"extract3",
"extract4",
"extract5",
"extract6",
"extract7",
"extract8",
"extract9",
"extract10",
"extract11",
"extract12",
"extract13",
"extract14",
"extract15",
"extract16",
"extract17",
"extract18",
"extract19",
"extract20",
"extract21",
"extract22",
"extract23",
"extract24",
"extract25",
}
-- ************** Logistics UNITS FOR CRATE SPAWNING ******************
-- Use any of the predefined names or set your own ones
ctld.logisticUnits = {
"logistic1",
"logistic2",
"logistic3",
"logistic4",
"logistic5",
"logistic6",
"logistic7",
"logistic8",
"logistic9",
"logistic10",
}
-- ************** UNITS ABLE TO TRANSPORT VEHICLES ******************
ctld.vehicleTransportEnabled = {
"C-130",
}
-- ***************************************************************
-- **************** BE CAREFUL BELOW HERE ************************
-- ***************************************************************
assert(mist ~= nil, "\n\n** HEY MISSION-DESIGNER! **\n\nMiST has not been loaded!\n\nMake sure MiST 3.6 or higher is running\n*before* running this script!\n")
ctld.addedTo = {}
ctld.spawnedCratesRED = {} -- use to store crates that have been spawned
ctld.spawnedCratesBLUE = {} -- use to store crates that have been spawned
ctld.droppedTroopsRED = {} -- stores dropped troop groups
ctld.droppedTroopsBLUE = {} -- stores dropped troop groups
ctld.droppedVehiclesRED = {} -- stores vehicle groups for c-130 / hercules
ctld.droppedVehiclesBLUE = {} -- stores vehicle groups for c-130 / hercules
ctld.inTransitTroops = {}
ctld.completeHawkSystems = {} -- stores complete spawned groups from multiple crates
-- Weights must be unique as we use the weight to change the cargo to the correct unit
-- when we unpack
ctld.spawnableCrates = {
["M1045 HMMWV TOW"] = { weight = 1400, desc = "HMMWV - TOW", unit = "M1045 HMMWV TOW" },
["M1043 HMMWV Armament"] = { weight = 1200, desc = "HMMWV - MG", unit = "M1043 HMMWV Armament" },
["2B11 mortar"] = { weight = 200, desc = "2B11 Mortar", unit = "2B11 mortar" },
["Stinger manpad"] = { weight = 210, desc = "MANPAD", unit = "Stinger manpad" },
["Hawk ln"] = { weight = 1000, desc = "HAWK Launcher", unit = "Hawk ln" },
["Hawk sr"] = { weight = 1010, desc = "HAWK Search Radar", unit = "Hawk sr" },
["Hawk tr"] = { weight = 1020, desc = "HAWK Track Radar", unit = "Hawk tr" },
}
--used to lookup what the crate will contain
ctld.crateLookupTable = {}
for _name, _crate in pairs(ctld.spawnableCrates) do
-- convert number to string otherwise we'll have a pointless giant
-- table. String means 'hashmap' so it will only contain the right number of elements
ctld.crateLookupTable[tostring(_crate.weight)] = _name
end
--sort out pickup zones
for _, _zone in pairs(ctld.pickupZones) do
local _zoneName = _zone[1]
local _zoneColor = _zone[2]
if _zoneColor == "green" then
_zone[2] = trigger.smokeColor.Green
elseif _zoneColor == "red" then
_zone[2] = trigger.smokeColor.Red
elseif _zoneColor == "white" then
_zone[2] = trigger.smokeColor.White
elseif _zoneColor == "orange" then
_zone[2] = trigger.smokeColor.Orange
elseif _zoneColor == "blue" then
_zone[2] = trigger.smokeColor.Blue
else
_zone[2] = -1 -- no smoke colour
end
end
--- - Sort out extractable groups
for _, _groupName in pairs(ctld.extractableGroups) do
local _group = Group.getByName(_groupName)
if _group ~= nil then
if _group:getCoalition() == 1 then
table.insert(ctld.droppedTroopsRED, _group:getName())
else
table.insert(ctld.droppedTroopsBLUE, _group:getName())
end
end
end
------------ EXTERNAL FUNCTIONS FOR MISSION EDITOR -----------
-----------------------------------------------------------------
-- Spawn group at a trigger and sets them as extractable. Usage:
-- ctld.spawnGroupAtTrigger("groupside", number, "triggerName", radius)
-- Variables:
-- "groupSide" = "red" for Russia "blue" for USA
-- _number = number of groups to spawn
-- "triggerName" = trigger name in mission editor between commas
-- _searchRadius = random distance for units to move from spawn zone (0 will leave troops at the spawn position - no search for enemy)
--
-- Example: ctld.spawnGroupAtTrigger("red", 2, "spawn1", 1000)
--
-- This example will spawn 2 groups of russians at trigger "spawn1"
-- and they will search for enemy or move randomly withing 1000m
function ctld.spawnGroupAtTrigger(_groupSide, _number, _triggerName, _searchRadius)
local _spawnTrigger = trigger.misc.getZone(_triggerName) -- trigger to use as reference position
if _spawnTrigger == nil then
trigger.action.outText("CTLD.lua ERROR: Cant find trigger called " .. _triggerName, 10)
return
end
if _groupSide == "red" then
_groupSide = 1
else
_groupSide = 2
end
if _number < 1 then
_number = 1
end
if _searchRadius < 0 then
_searchRadius = 0
end
local _pos2 = { x = _spawnTrigger.point.x, y = _spawnTrigger.point.z }
local _alt = land.getHeight(_pos2)
local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
local _types = ctld.generateTroopTypes(_groupSide,_number)
local _droppedTroops = ctld.spawnDroppedGroup(_groupSide,_pos3, _types, false,_searchRadius);
if _groupSide == 1 then
table.insert(ctld.droppedTroopsRED, _droppedTroops:getName())
else
table.insert(ctld.droppedTroopsBLUE, _droppedTroops:getName())
end
end
-- Preloads a transport with troops or vehicles
-- replaces any troops currently on board
function ctld.preLoadTransport(_unitName, _number,_troops)
local _unit = ctld.getTransportUnit(_unitName)
if _unit ~= nil then
-- will replace any units currently on board
-- if not ctld.troopsOnboard(_unit,_troops) then
ctld.loadTroops(_unit,_troops,_number)
-- end
end
end
---------------- INTERNAL FUNCTIONS ----------------
-- Remove intransit troops when heli / cargo plane dies
ctld.eventHandler = {}
function ctld.eventHandler:onEvent(_event)
if _event == nil or _event.initiator == nil then
env.info("CTLD null event")
elseif _event.id == 9 then
-- Pilot dead
ctld.inTransitTroops[_event.initiator:getName()] = nil
elseif world.event.S_EVENT_EJECTION == _event.id or _event.id == 8 then
-- env.info("Event unit - Pilot Ejected or Unit Dead")
ctld.inTransitTroops[_event.initiator:getName()] = nil
-- env.info(_event.initiator:getName())
end
end
function ctld.getTransportUnit(_unitName)
if _unitName == nil then
return nil
end
local _heli = Unit.getByName(_unitName)
if _heli ~= nil and _heli:isActive() and _heli:getLife() > 0 then
return _heli
end
return nil
end
function ctld.spawnCrateStatic(_side,_unitId,_point,_name,_weight)
local _crate = {
["category"] = "Cargo",
["shape_name"] = "ab-212_cargo",
["type"] = "Cargo1",
["unitId"] = _unitId,
["y"] = _point.z ,
["x"] = _point.x ,
["mass"] = _weight,
["name"] = _name,
["canCargo"] = true,
["heading"] = 0,
-- ["displayName"] = "name 2", -- getCargoDisplayName function exists but no way to set the variable
-- ["DisplayName"] = "name 2",
-- ["cargoDisplayName"] = "cargo123",
-- ["CargoDisplayName"] = "cargo123",
}
local _spawnedCrate
if _side == 1 then
_spawnedCrate = coalition.addStaticObject(_side, _crate)
else
_spawnedCrate = coalition.addStaticObject(_side, _crate)
end
return _spawnedCrate
end
function ctld.spawnCrate(_args)
-- use the cargo weight to guess the type of unit as no way to add description :(
local _crateType = ctld.spawnableCrates[_args[2]]
local _heli = ctld.getTransportUnit(_args[1])
if _crateType ~= nil and _heli ~= nil and _heli:inAir() == false then
if ctld.inLogisticsZone(_heli) == false then
ctld.displayMessageToGroup(_heli, "You are not close enough to friendly logistics to get a crate!", 10)
return
end
local _position = _heli:getPosition()
--try to spawn at 12 oclock to us
local _angle = math.atan2(_position.x.z, _position.x.x)
local _xOffset = math.cos(_angle) * 30
local _yOffset = math.sin(_angle) * 30
-- trigger.action.outText("Spawn Crate".._args[1].." ".._args[2],10)
local _heli = ctld.getTransportUnit(_args[1])
local _point = _heli:getPoint()
local _unitId = mist.getNextUnitId()
local _side = _heli:getCoalition()
local _name = string.format("%s #%i", _crateType.desc, _unitId)
local _spawnedCrate = ctld.spawnCrateStatic(_side,_unitId,{x=_point.x+_xOffset,z=_point.z + _yOffset},_name,_crateType.weight)
if _side == 1 then
-- _spawnedCrate = coalition.addStaticObject(_side, _spawnedCrate)
ctld.spawnedCratesRED[_name] = _crateType
else
-- _spawnedCrate = coalition.addStaticObject(_side, _spawnedCrate)
ctld.spawnedCratesBLUE[_name] = _crateType
end
ctld.displayMessageToGroup(_heli, string.format("A %s crate weighing %s kg has been brought out and is at your 12 o'clock ", _crateType.desc, _crateType.weight), 20)
else
env.info("Couldn't find crate item to spawn")
end
end
function ctld.troopsOnboard(_heli,_troops)
if ctld.inTransitTroops[_heli:getName()] ~= nil then
local _onboard = ctld.inTransitTroops[_heli:getName()]
if _troops then
if _onboard.troops ~= nil and #_onboard.troops > 0 then
return true
else
return false
end
else
if _onboard.vehicles ~= nil and #_onboard.vehicles > 0 then
return true
else
return false
end
end
else
return false
end
end
-- if its dropped by AI then there is no player name so return the type of unit
function ctld.getPlayerNameOrType(_heli)
if _heli:getPlayerName() == nil then
return _heli:getTypeName()
else
return _heli:getPlayerName()
end
end
function ctld.deployTroops(_heli,_troops)
local _onboard = ctld.inTransitTroops[_heli:getName()]
-- deloy troops
if _troops then
if _onboard.troops ~= nil and #_onboard.troops > 0 then
local _droppedTroops = ctld.spawnDroppedGroup(_heli:getCoalition(),_heli:getPoint(), _onboard.troops, false)
if _heli:getCoalition() == 1 then
table.insert(ctld.droppedTroopsRED, _droppedTroops:getName())
else
table.insert(ctld.droppedTroopsBLUE, _droppedTroops:getName())
end
ctld.inTransitTroops[_heli:getName()].troops = {}
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " dropped troops from " .. _heli:getTypeName() .. " into combat", 10)
end
else
if _onboard.vehicles ~= nil and #_onboard.vehicles > 0 then
local _droppedVehicles = ctld.spawnDroppedGroup(_heli:getCoalition(),_heli:getPoint(), _onboard.vehicles, true)
if _heli:getCoalition() == 1 then
table.insert(ctld.droppedVehiclesRED, _droppedVehicles:getName())
else
table.insert(ctld.droppedVehiclesBLUE, _droppedVehicles:getName())
end
ctld.inTransitTroops[_heli:getName()].vehicles = {}
trigger.action.outTextForCoalition(_heli:getCoalition(),ctld.getPlayerNameOrType(_heli) .. " dropped vehicles from " .. _heli:getTypeName() .. " into combat", 10)
end
end
end
function ctld.generateTroopTypes(_side,_count)
local _troops = {}
for _i = 1,_count do
local _unitType = "Soldier AK"
if _side == 2 then
_unitType = "Soldier M4"
if _i <= 4 and ctld.spawnRPGWithCoalition then
_unitType = "Paratrooper RPG-16"
end
if _i <= 2 then
_unitType = "Soldier M249"
end
else
_unitType = "Infantry AK"
if _i <= 4 then
_unitType = "Paratrooper RPG-16"
end
if _i <= 2 then
_unitType = "Paratrooper AKS-74"
end
end
_troops[_i] = _unitType
end
return _troops
end
-- load troops onto vehicle
function ctld.loadTroops(_heli,_troops, _number)
-- load troops + vehicles if c130 or herc
-- "M1045 HMMWV TOW"
-- "M1043 HMMWV Armament"
local _onboard = ctld.inTransitTroops[_heli:getName()]
--number doesnt apply to vehicles
if _number == nil then
_number = ctld.numberOfTroops
end
if _onboard == nil then
_onboard = { troops = {}, vehicles = {} }
end
if _troops then
_onboard.troops = ctld.generateTroopTypes(_heli:getCoalition(),_number)
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " loaded ".._number.." troops into " .. _heli:getTypeName(), 10)
else
for _i, _type in ipairs(ctld.vehiclesForTransport) do
_onboard.vehicles[_i] = _type
end
local _count = #ctld.vehiclesForTransport
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " loaded ".._count.." vehicles into " .. _heli:getTypeName(), 10)
end
ctld.inTransitTroops[_heli:getName()] = _onboard
end
function ctld.loadUnloadTroops(_args)
local _heli = ctld.getTransportUnit(_args[1])
local _troops = _args[2]
if _heli == nil then
return
end
local _inZone = ctld.inPickupZone(_heli)
if _inZone == true and ctld.troopsOnboard(_heli,_troops) then
if _troops then
ctld.displayMessageToGroup(_heli, "Dropped troops back to base", 20)
ctld.inTransitTroops[_heli:getName()].troops = {}
else
ctld.displayMessageToGroup(_heli, "Dropped vehicles back to base", 20)
ctld.inTransitTroops[_heli:getName()].vehicles = {}
end
elseif _inZone == false and ctld.troopsOnboard(_heli,_troops) then
ctld.deployTroops(_heli,_troops)
elseif _inZone == true and not ctld.troopsOnboard(_heli,_troops) then
ctld.loadTroops(_heli,_troops)
else
-- search for nearest troops to pickup
ctld.extractTroops(_heli,_troops)
end
end
function ctld.extractTroops(_heli,_troops)
local _onboard = ctld.inTransitTroops[_heli:getName()]
if _onboard == nil then
_onboard = { troops = {}, vehicles = {} }
end
if _troops then
local _extractTroops
if _heli:getCoalition() == 1 then
_extractTroops = ctld.findNearestGroup(_heli, ctld.droppedTroopsRED)
else
_extractTroops = ctld.findNearestGroup(_heli, ctld.droppedTroopsBLUE)
end
if _extractTroops ~= nil then
_onboard.troops = _extractTroops.types
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " extracted troops in " .. _heli:getTypeName() .. " from combat", 10)
if _heli:getCoalition() == 1 then
ctld.droppedTroopsRED[_extractTroops.group:getName()] = nil
else
ctld.droppedTroopsBLUE[_extractTroops.group:getName()] = nil
end
--remove
_extractTroops.group:destroy()
else
_onboard.troops = {}
ctld.displayMessageToGroup(_heli, "No extractable troops nearby and not in a pickup zone", 20)
end
else
local _extractVehicles
if _heli:getCoalition() == 1 then
_extractVehicles = ctld.findNearestGroup(_heli, ctld.droppedVehiclesRED)
else
_extractVehicles = ctld.findNearestGroup(_heli, ctld.droppedVehiclesBLUE)
end
if _extractVehicles ~= nil then
_onboard.vehicles = _extractVehicles.types
if _heli:getCoalition() == 1 then
ctld.droppedVehiclesRED[_extractVehicles.group:getName()] = nil
else
ctld.droppedVehiclesBLUE[_extractVehicles.group:getName()] = nil
end
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " extracted vehicles in " .. _heli:getTypeName() .. " from combat", 10)
--remove
_extractVehicles.group:destroy()
else
_onboard.vehicles = {}
ctld.displayMessageToGroup(_heli, "No extractable vehicles nearby and not in a pickup zone", 20)
end
end
ctld.inTransitTroops[_heli:getName()] = _onboard
end
function ctld.checkTroopStatus(_args)
--list onboard troops, if c130
-- trigger.action.outText("Troop Status".._args[1],10)
local _heli = ctld.getTransportUnit(_args[1])
if _heli == nil then
return
end
local _onboard = ctld.inTransitTroops[_heli:getName()]
if _onboard == nil then
ctld.displayMessageToGroup(_heli, "No troops onboard", 10)
else
local _troops = #_onboard.troops
local _vehicles = #_onboard.vehicles
local _txt = ""
if _troops > 0 then
_txt = _txt .. " " .. _troops .. " troops onboard\n"
end
if _vehicles > 0 then
_txt = _txt .. " " .. _vehicles .. " vehicles onboard\n"
end
if _txt ~= "" then
ctld.displayMessageToGroup(_heli, _txt, 10)
else
ctld.displayMessageToGroup(_heli, "No troops onboard", 10)
end
end
end
function ctld.listNearbyCrates(_args)
--trigger.action.outText("Nearby Crates" .. _args[1], 10)
local _message = ""
local _heli = ctld.getTransportUnit(_args[1])
if _heli ~= nil then
local _crates = ctld.getCratesAndDistance(_heli)
for _, _crate in pairs(_crates) do
if _crate.dist < 1000 then
_message = string.format("%s\n%s crate - kg %i - %i m", _message, _crate.details.desc, _crate.details.weight, _crate.dist)
end
end
end
if _message ~= "" then
local _txt = "Nearby Crates:\n" .. _message
ctld.displayMessageToGroup(_heli, _txt, 20)
else
--no crates nearby
local _txt = "No Nearby Crates"
ctld.displayMessageToGroup(_heli, _txt, 20)
end
end
function ctld.displayMessageToGroup(_unit, _text, _time)
trigger.action.outTextForGroup(_unit:getGroup():getID(), _text, _time)
end
function ctld.getCratesAndDistance(_heli)
local _crates = {}
local _allCrates
if _heli:getCoalition() == 1 then
_allCrates = ctld.spawnedCratesRED
else
_allCrates = ctld.spawnedCratesBLUE
end
for _crateName, _details in pairs(_allCrates) do
--get crate
local _crate = StaticObject.getByName(_crateName)
--in air seems buggy with crates so if in air is true, get the height above ground and the speed magnitude
if _crate ~= nil and _crate:getLife() > 0
and (_crate:inAir() == false or (land.getHeight(_crate:getPoint()) < 200 and mist.vec.mag(_crate:getVelocity()) < 1.0 )) then
local _dist = ctld.getDistance(_crate:getPoint(), _heli:getPoint())
local _crateDetails = { crateUnit = _crate, dist = _dist, details = _details }
table.insert(_crates, _crateDetails)
end
end
return _crates
end
function ctld.getClosestCrate(_heli, _crates)
local _closetCrate = nil
local _shortestDistance = -1
local _distance = 0
for _, _crate in pairs(_crates) do
_distance = _crate.dist
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
_shortestDistance = _distance
_closetCrate = _crate
end
end
return _closetCrate
end
function ctld.findNearestHawk(_heli)
local _closestHawkGroup = nil
local _shortestDistance = -1
local _distance = 0
for _, _groupName in pairs(ctld.completeHawkSystems) do
local _hawkGroup = Group.getByName(_groupName)
if _hawkGroup ~= nil and _hawkGroup:getCoalition() == _heli:getCoalition() then
local _leader = _hawkGroup:getUnit(1)
if _leader ~= nil and _leader:getLife() > 0 then
_distance = ctld.getDistance(_leader:getPoint(), _heli:getPoint())
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
_shortestDistance = _distance
_closestHawkGroup = _hawkGroup
end
end
end
end
if _closestHawkGroup ~= nil then
return {group = _closestHawkGroup, dist = _distance}
end
return nil
end
function ctld.unpackCrates(_args)
-- trigger.action.outText("Unpack Crates".._args[1],10)
local _heli = ctld.getTransportUnit(_args[1])
if _heli ~= nil and _heli:inAir() == false then
local _crates = ctld.getCratesAndDistance(_heli)
local _crate = ctld.getClosestCrate(_heli, _crates)
if _crate ~= nil and _crate.dist < 200 then
if ctld.inLogisticsZone(_heli) == true then
ctld.displayMessageToGroup(_heli, "You can't unpack that here! Take it to where it's needed!", 20)
return
end
-- is multi crate?
if string.match(_crate.details.desc, "HAWK") then
-- multicrate
-- are we adding to existing hawk system?
if _crate.details.unit == "Hawk ln" then
-- find nearest COMPLETE hawk system
local _nearestHawk = ctld.findNearestHawk(_heli)
if _nearestHawk ~=nil and _nearestHawk.dist < 300 then
if _heli:getCoalition() == 1 then
ctld.spawnedCratesRED[_crate.crateUnit:getName()] = nil
else
ctld.spawnedCratesBLUE[_crate.crateUnit:getName()] = nil
end
local _types = {}
local _points = {}
local _units = _nearestHawk.group:getUnits()
if _units ~= nil and #_units > 0 then
for x = 1, #_units do
if _units[x]:getLife() > 0 then
table.insert(_types,_units[x]:getTypeName())
table.insert(_points,_units[x]:getPoint())
end
end
end
if #_types == 3 and #_points == 3 then
-- rearm hawk
-- destroy old group
ctld.completeHawkSystems[_nearestHawk.group:getName()] = nil
_nearestHawk.group:destroy()
local _spawnedGroup = ctld.spawnCrateGroup(_heli, _points, _types)
ctld.completeHawkSystems[_spawnedGroup:getName()] = _spawnedGroup:getName()
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " successfully rearmed a full HAWK AA System in the field", 10)
return -- all done so quit
end
end
end
-- are there all the pieces close enough together
local _hawkParts = { ["Hawk ln"] = false, ["Hawk tr"] = false, ["Hawk sr"] = false }
for _, _nearbyCrate in pairs(_crates) do
if _nearbyCrate.dist < 300 then
if _nearbyCrate.details.unit == "Hawk ln" or _nearbyCrate.details.unit == "Hawk sr" or _nearbyCrate.details.unit == "Hawk tr" then
_hawkParts[_nearbyCrate.details.unit] = _nearbyCrate
else
-- not part of hawk
end
end
end
local _count = 0
local _txt = ""
local _posArray = {}
local _typeArray = {}
for _name, _hawkPart in pairs(_hawkParts) do
if _hawkPart == false then
if _name == "Hawk ln" then
_txt = "Missing HAWK Launcher\n"
elseif _name == "Hawk sr" then
_txt = _txt .. "Missing HAWK Search Radar\n"
else
_txt = _txt .. "Missing HAWK Track Radar\n"
end
else
table.insert(_posArray, _hawkPart.crateUnit:getPoint())
table.insert(_typeArray, _name)
end
end
if _txt ~= "" then
ctld.displayMessageToGroup(_heli, "Cannot build Hawk\n" .. _txt .. "\n\nOr the crates are not close enough together", 20)
return
else
-- destroy crates
for _name, _hawkPart in pairs(_hawkParts) do
if _heli:getCoalition() == 1 then
ctld.spawnedCratesRED[_hawkPart.crateUnit:getName()] = nil
else
ctld.spawnedCratesBLUE[_hawkPart.crateUnit:getName()] = nil
end
--destroy
_hawkPart.crateUnit:destroy()
end
-- HAWK READY!
local _spawnedGroup = ctld.spawnCrateGroup(_heli, _posArray, _typeArray)
ctld.completeHawkSystems[_spawnedGroup:getName()] = _spawnedGroup:getName()
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " successfully deployed a full HAWK AA System to the field", 10)
end
else
-- single crate
local _cratePoint = _crate.crateUnit:getPoint()
local _crateName = _crate.crateUnit:getName();
-- ctld.spawnCrateStatic( _heli:getCoalition(),mist.getNextUnitId(),{x=100,z=100},_crateName,100)
--remove crate
_crate.crateUnit:destroy()
ctld.spawnCrateGroup(_heli, { _cratePoint }, { _crate.details.unit })
if _heli:getCoalition() == 1 then
ctld.spawnedCratesRED[_crateName] = nil
else
ctld.spawnedCratesBLUE[_crateName] = nil
end
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " successfully deployed " .. _crate.details.desc .. " to the field", 10)
end
else
ctld.displayMessageToGroup(_heli, "No friendly crates close enough to unpack", 20)
end
end
end
function ctld.spawnCrateGroup(_heli, _positions, _types)
local _id = mist.getNextGroupId()
local _groupName = _types[1] .. " #" .. _id
local _side = _heli:getCoalition()
local _group = {
["visible"] = false,
["groupId"] = _id,
["hidden"] = false,
["units"] = {},
-- ["y"] = _positions[1].z,
-- ["x"] = _positions[1].x,
["name"] = _groupName,
["task"] = {},
}
if #_positions == 1 then
_group.units[1] = ctld.createUnit(_positions[1].x + 5, _positions[1].z + 5, 120, _types[1])
else
for _i, _pos in ipairs(_positions) do
_group.units[_i] = ctld.createUnit(_pos.x + 5, _pos.z + 5, 120, _types[_i])
end
end
local _spawnedGroup = coalition.addGroup(_side, Group.Category.GROUND, _group)
--activate by moving and so we can set ROE and Alarm state
local _dest = _spawnedGroup:getUnit(1):getPoint()
_dest = { x = _dest.x + 5, _y = _dest.y + 5, z = _dest.z + 5 }
ctld.orderGroupToMoveToPoint(_spawnedGroup:getUnit(1), _dest)
return _spawnedGroup
end
-- spawn normal group
function ctld.spawnDroppedGroup(_side,_point, _types, _spawnBehind,_maxSearch)
local _id = mist.getNextGroupId()
local _groupName = "Dropped Group #" .. _id
local _group = {
["visible"] = false,
["groupId"] = _id,
["hidden"] = false,
["units"] = {},
-- ["y"] = _positions[1].z,
-- ["x"] = _positions[1].x,
["name"] = _groupName,
["task"] = {},
}
if _spawnBehind == false then
-- spawn in circle around heli
local _pos = _point
for _i, _type in ipairs(_types) do
local _angle = math.pi * 2 * (_i - 1) / #_types
local _xOffset = math.cos(_angle) * 30
local _yOffset = math.sin(_angle) * 30
_group.units[_i] = ctld.createUnit(_pos.x + _xOffset, _pos.z + _yOffset, _angle, _type)
end
else
local _pos = _point
--try to spawn at 6 oclock to us
local _angle = math.atan2(_pos.z, _pos.x)
local _xOffset = math.cos(_angle) * 30
local _yOffset = math.sin(_angle) * 30
for _i, _type in ipairs(_types) do
_group.units[_i] = ctld.createUnit(_pos.x - (_xOffset + 10 * _i), _pos.z - (_yOffset + 10 * _i), _angle, _type)
end
end
local _spawnedGroup = coalition.addGroup(_side, Group.Category.GROUND, _group)
-- find nearest enemy and head there
if _maxSearch == nil then
_maxSearch = ctld.maximumSearchDistance
end
local _enemyPos = ctld.findNearestEnemy(_side,_point,_maxSearch)
ctld.orderGroupToMoveToPoint(_spawnedGroup:getUnit(1), _enemyPos)
return _spawnedGroup
end
function ctld.findNearestEnemy(_side,_point,_searchDistance)
local _closestEnemy = nil
local _groups
local _closestEnemyDist = _searchDistance
local _heliPoint = _point
if _side == 2 then
_groups = coalition.getGroups(1, Group.Category.GROUND)
else
_groups = coalition.getGroups(2, Group.Category.GROUND)
end
for _, _group in pairs(_groups) do
if _group ~= nil then
local _units = _group:getUnits()
if _units ~= nil and #_units > 0 then
local _leader = nil
-- find alive leader
for x = 1, #_units do
if _units[x]:getLife() > 0 then
_leader = _units[x]
break
end
end
if _leader ~= nil then
local _leaderPos = _leader:getPoint()
local _dist = ctld.getDistance(_heliPoint, _leaderPos)
if _dist < _closestEnemyDist then
_closestEnemyDist = _dist
_closestEnemy = _leaderPos
end
end
end
end
end
-- no enemy - move to random point
if _closestEnemy ~= nil then
return _closestEnemy
else
local _x = _heliPoint.x + math.random(0, ctld.maximumMoveDistance) - math.random(0, ctld.maximumMoveDistance)
local _z = _heliPoint.z + math.random(0, ctld.maximumMoveDistance) - math.random(0, ctld.maximumMoveDistance)
return { x = _x, z = _z }
end
end
function ctld.findNearestGroup(_heli, _groups)
local _closestGroupTypes = {}
local _closestGroup = nil
local _closestGroupDist = ctld.maxExtractDistance
local _heliPoint = _heli:getPoint()
for _, _groupName in pairs(_groups) do
local _group = Group.getByName(_groupName)
if _group ~= nil then
local _units = _group:getUnits()
if _units ~= nil and #_units > 0 then
local _leader = nil
local _unitTypes = {}
-- find alive leader
for x = 1, #_units do
if _units[x]:getLife() > 0 then
if _leader == nil then
_leader = _units[x]
end
table.insert(_unitTypes, _units[x]:getTypeName())
end
end
if _leader ~= nil then
local _leaderPos = _leader:getPoint()
local _dist = ctld.getDistance(_heliPoint, _leaderPos)
if _dist < _closestGroupDist then
_closestGroupDist = _dist
_closestGroupTypes = _unitTypes
_closestGroup = _group
end
end
end
end
end
if _closestGroup ~= nil then
return { group = _closestGroup, types = _closestGroupTypes }
else
return nil
end
end
function ctld.createUnit(_x, _y, _angle, _type)
local _id = mist.getNextUnitId();
local _name = string.format("%s #%s", _type, _id)
local _newUnit = {
["y"] = _y,
["type"] = _type,
["name"] = _name,
["unitId"] = _id,
["heading"] = _angle,
["playerCanDrive"] = true,
["skill"] = "Excellent",
["x"] = _x,
}
return _newUnit
end
function ctld.orderGroupToMoveToPoint(_leader, _destination)
local _group = _leader:getGroup()
local _mission = {
id = 'Mission',
params = {
route = {
points = {
[1] = {
action = 0,
x = _leader:getPoint().x,
y = _leader:getPoint().z,
speed = 0,
ETA = 100,
ETA_locked = false,
name = "Starting point",
task = nil
},
[2] = {
action = 0,
x = _destination.x,
y = _destination.z,
speed = 100,
ETA = 100,
ETA_locked = false,
name = "End Point",
task = nil
},
}
},
}
}
local _controller = _group:getController();
Controller.setOption(_controller, AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO)
Controller.setOption(_controller, AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE)
_controller:setTask(_mission)
end
-- are we in pickup zone
function ctld.inPickupZone(_heli)
if _heli:inAir() then
return false
end
local _heliPoint = _heli:getPoint()
for _, _zoneDetails in pairs(ctld.pickupZones) do
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
if _triggerZone ~= nil then
--get distance to center
local _dist = ctld.getDistance(_heliPoint, _triggerZone.point)
if _dist <= _triggerZone.radius then
return true
end
end
end
return false
end
-- are we in a dropoff zone
function ctld.inDropoffZone(_heli)
if _heli:inAir() then
return false
end
local _heliPoint = _heli:getPoint()
for _, _zoneDetails in pairs(ctld.dropOffZones) do
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
if _triggerZone ~= nil then
--get distance to center
local _dist = ctld.getDistance(_heliPoint, _triggerZone.point)
if _dist <= _triggerZone.radius then
return true
end
end
end
return false
end
-- are we near friendly logistics zone
function ctld.inLogisticsZone(_heli)
if _heli:inAir() then
return false
end
local _heliPoint = _heli:getPoint()
for _, _name in pairs(ctld.logisticUnits) do
local _logistic = StaticObject.getByName(_name)
if _logistic ~= nil and _logistic:getCoalition() == _heli:getCoalition() then
--get distance
local _dist = ctld.getDistance(_heliPoint, _logistic:getPoint())
if _dist <= ctld.maximumDistanceLogistic then
return true
end
end
end
return false
end
function ctld.refreshSmoke()
if ctld.disableAllSmoke == true then
return
end
for _, _zoneDetails in pairs(ctld.pickupZones) do
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
if _triggerZone ~= nil and _zoneDetails[2] >= 0 then
-- Trigger smoke markers
local _pos2 = { x = _triggerZone.point.x, y = _triggerZone.point.z }
local _alt = land.getHeight(_pos2)
local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
trigger.action.smoke(_pos3, _zoneDetails[2])
end
end
--refresh in 5 minutes
timer.scheduleFunction(ctld.refreshSmoke, nil, timer.getTime() + 300)
end
function ctld.dropSmoke(_args)
local _heli = ctld.getTransportUnit(_args[1])
if _heli ~= nil then
local _colour = ""
if _args[2] == trigger.smokeColor.Red then
_colour = "RED"
elseif _args[2] == trigger.smokeColor.Blue then
_colour = "BLUE"
elseif _args[2] == trigger.smokeColor.Green then
_colour = "GREEN"
elseif _args[2] == trigger.smokeColor.Orange then
_colour = "ORANGE"
end
local _point = _heli:getPoint()
local _pos2 = { x = _point.x, y = _point.z }
local _alt = land.getHeight(_pos2)
local _pos3 = { x = _point.x, y = _alt, z = _point.z }
trigger.action.smoke(_pos3, _args[2])
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " dropped " .._colour .." smoke ", 10)
end
end
function ctld.unitCanCarryVehicles(_unit)
local _type = _unit:getTypeName()
for _,_name in pairs(ctld.vehicleTransportEnabled) do
if string.match(_type, _name) then
return true
end
end
return false
end
-- checks the status of all AI troop carriers and auto loads and unloads troops
function ctld.checkAIStatus()
timer.scheduleFunction(ctld.checkAIStatus,nil,timer.getTime()+5)
for _, _unitName in pairs(ctld.transportPilotNames) do
local _unit = ctld.getTransportUnit(_unitName)
if _unit ~= nil and _unit:getPlayerName() == nil then
-- no player name means AI!
if ctld.inPickupZone(_unit) and not ctld.troopsOnboard(_unit,true) then
ctld.loadTroops(_unit,true)
elseif ctld.inDropoffZone(_unit) and ctld.troopsOnboard(_unit,true) then
ctld.deployTroops(_unit,true)
end
if ctld.unitCanCarryVehicles(_unit) then
if ctld.inPickupZone(_unit) and not ctld.troopsOnboard(_unit,false) then
ctld.loadTroops(_unit,false)
elseif ctld.inDropoffZone(_unit) and ctld.troopsOnboard(_unit,false) then
ctld.deployTroops(_unit,false)
end
end
end
end
end
-- Adds menuitem to all heli units that are active
function addTransportMenuItem()
-- Loop through all Heli units
timer.scheduleFunction(addTransportMenuItem, nil, timer.getTime() + 5)
for _, _unitName in pairs(ctld.transportPilotNames) do
local _unit = ctld.getTransportUnit(_unitName)
if _unit ~= nil then
local _groupId = _unit:getGroup():getID()
if ctld.addedTo[_groupId] == nil and _unit:getPlayerName() ~= nil then
missionCommands.addSubMenuForGroup(_groupId, "Troop Transport")
missionCommands.addCommandForGroup(_groupId, "Load / Unload Troops", { "Troop Transport" }, ctld.loadUnloadTroops, { _unitName,true })
if ctld.unitCanCarryVehicles(_unit) then
missionCommands.addCommandForGroup(_groupId, "Load / Unload Vehicles", { "Troop Transport" }, ctld.loadUnloadTroops, { _unitName,false })
end
missionCommands.addCommandForGroup(_groupId, "Check Status", { "Troop Transport" }, ctld.checkTroopStatus, { _unitName })
if ctld.enableCrates then
if ctld.unitCanCarryVehicles(_unit) == false then
missionCommands.addSubMenuForGroup(_groupId, "Ground Forces")
missionCommands.addCommandForGroup(_groupId, "HMMWV - TOW", { "Ground Forces" }, ctld.spawnCrate, { _unitName, "M1045 HMMWV TOW" })
missionCommands.addCommandForGroup(_groupId, "HMMWV - MG", { "Ground Forces" }, ctld.spawnCrate, { _unitName, "M1043 HMMWV Armament" })
missionCommands.addCommandForGroup(_groupId, "2B11 Mortar", { "Ground Forces" }, ctld.spawnCrate, { _unitName, "2B11 mortar" })
missionCommands.addSubMenuForGroup(_groupId, "AA Crates")
missionCommands.addCommandForGroup(_groupId, "MANPAD", { "AA Crates" }, ctld.spawnCrate, { _unitName, "Stinger manpad" })
missionCommands.addCommandForGroup(_groupId, "HAWK Launcher", { "AA Crates" }, ctld.spawnCrate, { _unitName, "Hawk ln" })
missionCommands.addCommandForGroup(_groupId, "HAWK Search Radar", { "AA Crates" }, ctld.spawnCrate, { _unitName, "Hawk sr" })
missionCommands.addCommandForGroup(_groupId, "HAWK Track Radar", { "AA Crates" }, ctld.spawnCrate, { _unitName, "Hawk tr" })
end
missionCommands.addSubMenuForGroup(_groupId, "Crate Commands")
missionCommands.addCommandForGroup(_groupId, "List Nearby Crates", { "Crate Commands" }, ctld.listNearbyCrates, { _unitName })
missionCommands.addCommandForGroup(_groupId, "Unpack Crate", { "Crate Commands" }, ctld.unpackCrates, { _unitName })
if ctld.enableSmokeDrop then
missionCommands.addCommandForGroup(_groupId, "Drop Red Smoke", { "Crate Commands" }, ctld.dropSmoke, { _unitName, trigger.smokeColor.Red })
missionCommands.addCommandForGroup(_groupId, "Drop Blue Smoke", { "Crate Commands" }, ctld.dropSmoke, { _unitName, trigger.smokeColor.Blue })
-- missionCommands.addCommandForGroup(_groupId, "Drop Orange Smoke", { "Crate Commands" }, ctld.dropSmoke, { _unitName, trigger.smokeColor.Orange })
missionCommands.addCommandForGroup(_groupId, "Drop Green Smoke", { "Crate Commands" }, ctld.dropSmoke, { _unitName, trigger.smokeColor.Green })
end
else
if ctld.enableSmokeDrop then
missionCommands.addSubMenuForGroup(_groupId, "Smoke Markers")
missionCommands.addCommandForGroup(_groupId, "Drop Red Smoke", { "Smoke Markers" }, ctld.dropSmoke, { _unitName, trigger.smokeColor.Red })
missionCommands.addCommandForGroup(_groupId, "Drop Blue Smoke", { "Smoke Markers" }, ctld.dropSmoke, { _unitName, trigger.smokeColor.Blue })
missionCommands.addCommandForGroup(_groupId, "Drop Orange Smoke", { "Smoke Markers"}, ctld.dropSmoke, { _unitName, trigger.smokeColor.Orange })
missionCommands.addCommandForGroup(_groupId, "Drop Green Smoke", { "Smoke Markers" }, ctld.dropSmoke, { _unitName, trigger.smokeColor.Green })
end
end
ctld.addedTo[_groupId] = true
end
else
-- env.info(string.format("unit nil %s",_unitName))
end
end
return
end
--get distance in meters assuming a Flat world
function ctld.getDistance(_point1, _point2)
local xUnit = _point1.x
local yUnit = _point1.z
local xZone = _point2.x
local yZone = _point2.z
local xDiff = xUnit - xZone
local yDiff = yUnit - yZone
return math.sqrt(xDiff * xDiff + yDiff * yDiff)
end
-- Scheduled functions (run cyclically)
timer.scheduleFunction(ctld.refreshSmoke, nil, timer.getTime() + 5)
timer.scheduleFunction(addTransportMenuItem, nil, timer.getTime() + 5)
timer.scheduleFunction(ctld.checkAIStatus,nil,timer.getTime() + 5)
--event handler for deaths
world.addEventHandler(ctld.eventHandler)
env.info("CTLD event handler added")
--DEBUG FUNCTION
-- for key, value in pairs(getmetatable(_spawnedCrate)) do
-- env.info(tostring(key))
-- env.info(tostring(value))
-- end