mirror of
https://github.com/ciribob/DCS-CTLD.git
synced 2025-08-15 06:17:22 +00:00
Ability to build FOB IL-76 able to transport vehicles Vehicle transport config changed to be separate for RED and BLUE Crate config changed to allow easy customisation of RED and BLUE
3100 lines
89 KiB
Lua
3100 lines
89 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 sling-loads
|
|
without requiring external mods.
|
|
|
|
Supports some of the original CTTS functionality such as AI auto troop load and unload as well as group spawning and preloading of troops into units.
|
|
|
|
Supports deployment of Auto Lasing JTAC to the field
|
|
|
|
See https://github.com/ciribob/DCS-CTLD for a user manual and the latest version
|
|
|
|
Version: 1.05 - 24/05/2015 - Event Bug Fix from Latest DCS Patch
|
|
|
|
]]
|
|
|
|
ctld = {} -- DONT REMOVE!
|
|
|
|
-- ************************************************************************
|
|
-- ********************* USER CONFIGURATION ******************************
|
|
-- ************************************************************************
|
|
ctld.disableAllSmoke = false -- if true, all smoke is diabled at pickup and drop off zones 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.vehiclesForTransportRED = { "BRDM-2", "BTR_D" } -- vehicles to load onto Il-76
|
|
ctld.vehiclesForTransportBLUE = { "M1045 HMMWV TOW", "M1043 HMMWV Armament" } -- vehicles to load onto c130
|
|
|
|
ctld.spawnRPGWithCoalition = true --spawns a friendly RPG unit with Coalition forces
|
|
|
|
ctld.enabledFOBBuilding = true -- if true, you can load a crate INTO a C-130 than when unpacked creates a Forward Operating Base (FOB) which is a new place to spawn (crates) and carry crates from
|
|
-- In future i'd like it to be a FARP but so far that seems impossible...
|
|
-- You can also enable troop Pickup at FOBS
|
|
|
|
ctld.cratesRequiredForFOB = 3 -- The amount of crates required to build a FOB. Once built, helis can spawn crates at this outpost to be carried and deployed in another area.
|
|
-- The crates can only be loaded and dropped by large aircraft, like the C-130 and listed in ctld.vehicleTransportEnabled
|
|
|
|
ctld.troopPickupAtFOB = true -- if true, troops can also be picked up at a created FOB
|
|
|
|
ctld.buildTimeFOB = 120 --time in seconds for the FOB to be built
|
|
|
|
|
|
-- ***************** JTAC CONFIGURATION *****************
|
|
|
|
ctld.JTAC_LIMIT_RED = 5 -- max number of JTAC Crates for the RED Side
|
|
ctld.JTAC_LIMIT_BLUE = 5 -- max number of JTAC Crates for the BLUE Side
|
|
|
|
ctld.JTAC_dropEnabled = true -- allow JTAC Crate spawn from F10 menu
|
|
|
|
ctld.JTAC_maxDistance = 5000 -- How far a JTAC can "see" in meters (with Line of Sight)
|
|
|
|
ctld.JTAC_smokeOn_RED = true -- enables marking of target with smoke for RED forces
|
|
ctld.JTAC_smokeOn_BLUE = true -- enables marking of target with smoke for BLUE forces
|
|
|
|
ctld.JTAC_smokeColour_RED = 4 -- RED side smoke colour -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4
|
|
ctld.JTAC_smokeColour_BLUE = 1 -- BLUE side smoke colour -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4
|
|
|
|
ctld.JTAC_jtacStatusF10 = true -- enables F10 JTAC Status menu
|
|
|
|
ctld.JTAC_location = true -- shows location of target in JTAC message
|
|
|
|
ctld.JTAC_lock = "all" -- "vehicle" OR "troop" OR "all" forces JTAC to only lock vehicles or troops or all ground units
|
|
|
|
-- ***************** 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
|
|
-- When a logistic unit is destroyed, you will no longer be able to spawn crates
|
|
|
|
ctld.logisticUnits = {
|
|
"logistic1",
|
|
"logistic2",
|
|
"logistic3",
|
|
"logistic4",
|
|
"logistic5",
|
|
"logistic6",
|
|
"logistic7",
|
|
"logistic8",
|
|
"logistic9",
|
|
"logistic10",
|
|
}
|
|
|
|
-- ************** UNITS ABLE TO TRANSPORT VEHICLES ******************
|
|
-- Add the model name of the unit that you want to be able to transport and deploy vehicles
|
|
-- units db has all the names or you can extract a mission.miz file by making it a zip and looking
|
|
-- in the contained mission file
|
|
ctld.vehicleTransportEnabled = {
|
|
"76MD", -- the il-76 mod doesnt use a normal - sign so il-76md wont match... !!!! GRR
|
|
"C-130",
|
|
}
|
|
|
|
|
|
-- ************** SPAWNABLE CRATES ******************
|
|
-- Weights must be unique as we use the weight to change the cargo to the correct unit
|
|
-- when we unpack
|
|
--
|
|
ctld.spawnableCrates = {
|
|
|
|
-- name of the sub menu on F10 for spawning crates
|
|
["Ground Forces"] = {
|
|
|
|
--crates you can spawn
|
|
-- weight in KG
|
|
-- Desc is the description on the F10 MENU
|
|
-- unit is the model name of the unit to spawn
|
|
-- cratesRequired - if set requires that many crates of the same type within 100m of each other in order build the unit
|
|
-- side is optional but 2 is BLUE and 1 is RED
|
|
-- dont use that option with the HAWK Crates
|
|
{ weight = 1400, desc = "HMMWV - TOW", unit = "M1045 HMMWV TOW" , side = 2 },
|
|
{ weight = 1200, desc = "HMMWV - MG", unit = "M1043 HMMWV Armament", side = 2 },
|
|
|
|
{ weight = 1600, desc = "BTR-D", unit = "BTR_D", side = 1 },
|
|
{ weight = 1800, desc = "BRDM-2", unit = "BRDM-2", side = 1 },
|
|
|
|
{ weight = 1100, desc = "HMMWV - JTAC", unit = "Hummer", side = 2, }, -- used as jtac and unarmed, not on the crate list if JTAC is disabled
|
|
{ weight = 1500, desc = "SKP-11 - JTAC", unit = "SKP-11", side = 1, }, -- used as jtac and unarmed, not on the crate list if JTAC is disabled
|
|
|
|
{ weight = 200, desc = "2B11 Mortar", unit = "2B11 mortar" },
|
|
-- { weight = 500, desc = "M-109", unit = "M-109", cratesRequired = 3 },
|
|
},
|
|
|
|
["AA Crates"] = {
|
|
|
|
{ weight = 210, desc = "MANPAD", unit = "Stinger manpad" },
|
|
{ weight = 1000, desc = "HAWK Launcher", unit = "Hawk ln" },
|
|
{ weight = 1010, desc = "HAWK Search Radar", unit = "Hawk sr" },
|
|
{ weight = 1020, desc = "HAWK Track Radar", unit = "Hawk tr" },
|
|
-- { weight = 505, desc = "M6 Linebacker", unit = "M6 Linebacker", cratesRequired = 3 },
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
-- if the unit is on this list, it will be made into a JTAC
|
|
ctld.jtacUnitTypes = {
|
|
"SKP","Hummer"
|
|
}
|
|
|
|
-- ***************************************************************
|
|
-- **************** Mission Editor Functions *********************
|
|
-- ***************************************************************
|
|
|
|
|
|
-----------------------------------------------------------------
|
|
-- Spawn group at a trigger and set 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
|
|
|
|
local _country
|
|
if _groupSide == "red" then
|
|
_groupSide = 1
|
|
_country = 0
|
|
else
|
|
_groupSide = 2
|
|
_country = 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 _groupDetails = ctld.generateTroopTypes(_groupSide,_number,_country)
|
|
|
|
local _droppedTroops = ctld.spawnDroppedGroup(_pos3, _groupDetails, 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
|
|
|
|
|
|
|
|
-- ***************************************************************
|
|
-- **************** BE CAREFUL BELOW HERE ************************
|
|
-- ***************************************************************
|
|
|
|
---------------- INTERNAL FUNCTIONS ----------------
|
|
|
|
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(_country,_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 = coalition.addStaticObject(_country, _crate)
|
|
|
|
return _spawnedCrate
|
|
end
|
|
|
|
function ctld.spawnFOBCrateStatic(_country,_unitId,_point,_name)
|
|
|
|
local _crate = {
|
|
["category"] = "Fortifications",
|
|
["shape_name"] = "konteiner_red1",
|
|
["type"] = "Container red 1",
|
|
["unitId"] = _unitId,
|
|
["y"] = _point.z ,
|
|
["x"] = _point.x ,
|
|
["name"] = _name,
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
local _spawnedCrate = coalition.addStaticObject(_country, _crate)
|
|
|
|
return _spawnedCrate
|
|
end
|
|
|
|
|
|
function ctld.spawnFOB(_country,_unitId,_point,_name)
|
|
|
|
local _crate = {
|
|
["category"] = "Fortifications",
|
|
["type"] = "outpost",
|
|
["unitId"] = _unitId,
|
|
["y"] = _point.z ,
|
|
["x"] = _point.x ,
|
|
["name"] = _name,
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
local _spawnedCrate = coalition.addStaticObject(_country, _crate)
|
|
|
|
local _id = mist.getNextUnitId()
|
|
local _tower = {
|
|
["type"] = "house2arm",
|
|
["unitId"] = _id,
|
|
["rate"] = 100,
|
|
["y"] = _point.z + -36.57142857,
|
|
["x"] = _point.x + 14.85714286,
|
|
["name"] = "FOB Watchtower #".._id,
|
|
["category"] = "Fortifications",
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
coalition.addStaticObject(_country, _tower)
|
|
|
|
return _spawnedCrate
|
|
end
|
|
|
|
--function ctld.spawnFARP(_country,_point)
|
|
--
|
|
-- local _crate = {
|
|
-- ["type"] = "FARP",
|
|
-- ["unitId"] = _unitId,
|
|
-- ["heliport_modulation"] = 0,
|
|
-- ["y"] = _point.z+1,
|
|
-- ["x"] = _point.x+1,
|
|
-- ["name"] = _name,
|
|
-- ["category"] = "Heliports",
|
|
-- ["canCargo"] = false,
|
|
-- ["heliport_frequency"] = 127.5,
|
|
-- ["heliport_callsign_id"] = 1,
|
|
-- ["heading"] = 3.1415926535898,
|
|
--
|
|
-- }
|
|
--
|
|
--
|
|
-- local _farpPiece = {
|
|
-- ["shape_name"] = "PalatkaB",
|
|
-- ["type"] = "FARP Tent",
|
|
--
|
|
-- ["y"] = _point.z+1.5,
|
|
-- ["x"] = _point.x+1.5,
|
|
-- ["name"] = "Unit #"..mist.getNextUnitId(),
|
|
-- ["unitId"] = mist.getNextUnitId(),
|
|
-- ["category"] = "Fortifications",
|
|
-- ["heading"] = 3.1415926535898,
|
|
-- }
|
|
--
|
|
-- coalition.addStaticObject(_country, _farpPiece)
|
|
-- local _farpPiece = {
|
|
-- ["shape_name"] = "SetkaKP",
|
|
-- ["type"] = "FARP Ammo Dump Coating",
|
|
--
|
|
-- ["y"] = _point.z+2,
|
|
-- ["x"] = _point.x+2,
|
|
-- ["name"] = "Unit #"..mist.getNextUnitId(),
|
|
-- ["unitId"] = mist.getNextUnitId(),
|
|
-- ["category"] = "Fortifications",
|
|
-- ["heading"] = 3.1415926535898,
|
|
-- }
|
|
-- coalition.addStaticObject(_country, _farpPiece)
|
|
-- local _farpPiece = {
|
|
-- ["shape_name"] = "GSM Rus",
|
|
-- ["type"] = "FARP Fuel Depot",
|
|
--
|
|
-- ["y"] = _point.z+2.5,
|
|
-- ["x"] = _point.x+2.5,
|
|
-- ["name"] = "Unit #"..mist.getNextUnitId(),
|
|
-- ["unitId"] = mist.getNextUnitId(),
|
|
-- ["category"] = "Fortifications",
|
|
-- ["heading"] = 3.1415926535898,
|
|
-- }
|
|
-- coalition.addStaticObject(_country, _farpPiece)
|
|
--
|
|
--
|
|
--
|
|
-- local _farpUnits = {
|
|
-- {
|
|
--
|
|
-- ["type"] = "M978 HEMTT Tanker",
|
|
-- ["name"] = "Unit #"..mist.getNextUnitId(),
|
|
-- ["unitId"] = mist.getNextUnitId(),
|
|
-- ["heading"] = 4.7822021504645,
|
|
-- ["playerCanDrive"] = true,
|
|
-- ["skill"] = "Average",
|
|
-- ["x"] = _point.x,
|
|
-- ["y"] = _point.z,
|
|
-- },
|
|
-- {
|
|
--
|
|
-- ["type"] = "M 818",
|
|
-- ["name"] = "Unit #"..mist.getNextUnitId(),
|
|
-- ["unitId"] = mist.getNextUnitId(),
|
|
-- ["heading"] = 4.7822021504645,
|
|
-- ["playerCanDrive"] = true,
|
|
-- ["skill"] = "Average",
|
|
-- ["x"] = _point.x,
|
|
-- ["y"] = _point.z,
|
|
--
|
|
-- },
|
|
-- {
|
|
--
|
|
-- ["type"] = "M-113",
|
|
-- ["name"] = "Unit #"..mist.getNextUnitId(),
|
|
-- ["unitId"] = mist.getNextUnitId(),
|
|
-- ["heading"] = 4.7822021504645,
|
|
-- ["playerCanDrive"] = true,
|
|
-- ["skill"] = "Average",
|
|
-- ["x"] = _point.x,
|
|
-- ["y"] = _point.z,
|
|
--
|
|
-- },
|
|
-- }
|
|
--
|
|
-- mist.dynAdd({units = _farpUnits,country=_country,category=Group.Category.GROUND})
|
|
--
|
|
--end
|
|
|
|
function ctld.spawnCrate(_args)
|
|
|
|
-- use the cargo weight to guess the type of unit as no way to add description :(
|
|
|
|
local _crateType = ctld.crateLookupTable[tostring(_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
|
|
|
|
if ctld.isJTACUnitType(_crateType.unit) then
|
|
|
|
local _limitHit = false
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
|
|
if ctld.JTAC_LIMIT_RED == 0 then
|
|
_limitHit = true
|
|
else
|
|
ctld.JTAC_LIMIT_RED = ctld.JTAC_LIMIT_RED - 1
|
|
end
|
|
else
|
|
if ctld.JTAC_LIMIT_BLUE == 0 then
|
|
_limitHit = true
|
|
else
|
|
ctld.JTAC_LIMIT_BLUE = ctld.JTAC_LIMIT_BLUE - 1
|
|
end
|
|
end
|
|
|
|
if _limitHit then
|
|
ctld.displayMessageToGroup(_heli, "No more JTAC Crates Left!",10)
|
|
return
|
|
end
|
|
|
|
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(_heli:getCountry(),_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.units ~= nil and #_onboard.troops.units > 0 then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
else
|
|
|
|
if _onboard.vehicles ~= nil and _onboard.vehicles.units ~= nil and #_onboard.vehicles.units > 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.units > 0 then
|
|
|
|
local _droppedTroops = ctld.spawnDroppedGroup(_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 = nil
|
|
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.units > 0 then
|
|
|
|
local _droppedVehicles = ctld.spawnDroppedGroup(_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 = nil
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(),ctld.getPlayerNameOrType(_heli) .. " dropped vehicles from " .. _heli:getTypeName() .. " into combat", 10)
|
|
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function ctld.generateTroopTypes(_side,_count, _country)
|
|
|
|
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
|
|
|
|
local _unitId = mist.getNextUnitId()
|
|
|
|
_troops[_i] = {type = _unitType, unitId = _unitId, name = string.format("Dropped %s #%i",_unitType,_unitId)}
|
|
|
|
end
|
|
|
|
local _groupId = mist.getNextGroupId()
|
|
local _details = {units = _troops, groupId = _groupId, groupName = string.format("Dropped Group %i",_groupId), side=_side, country=_country}
|
|
|
|
return _details
|
|
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
|
|
|
|
local _list
|
|
if _heli:getCoalition() == 1 then
|
|
_list = ctld.vehiclesForTransportRED
|
|
else
|
|
_list = ctld.vehiclesForTransportBLUE
|
|
end
|
|
|
|
if _troops then
|
|
|
|
_onboard.troops = ctld.generateTroopTypes(_heli:getCoalition(),_number,_heli:getCountry())
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " loaded ".._number.." troops into " .. _heli:getTypeName(), 10)
|
|
|
|
else
|
|
|
|
_onboard.vehicles = ctld.generateVehiclesForTransport(_heli:getCoalition(), _heli:getCountry())
|
|
|
|
local _count = #_list
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " loaded ".._count.." vehicles into " .. _heli:getTypeName(), 10)
|
|
|
|
end
|
|
|
|
ctld.inTransitTroops[_heli:getName()] = _onboard
|
|
|
|
end
|
|
|
|
function ctld.generateVehiclesForTransport(_side,_country)
|
|
|
|
local _vehicles = {}
|
|
local _list
|
|
if _side == 1 then
|
|
_list = ctld.vehiclesForTransportRED
|
|
else
|
|
_list = ctld.vehiclesForTransportBLUE
|
|
end
|
|
|
|
|
|
for _i, _type in ipairs(_list) do
|
|
|
|
local _unitId= mist.getNextUnitId()
|
|
|
|
_vehicles[_i] = {type = _type, unitId = _unitId, name = string.format("Dropped %s #%i",_type,_unitId)}
|
|
|
|
end
|
|
|
|
|
|
local _groupId = mist.getNextGroupId()
|
|
local _details = {units = _vehicles, groupId = _groupId, groupName = string.format("Dropped Group %i",_groupId), side=_side, country=_country }
|
|
|
|
return _details
|
|
|
|
end
|
|
|
|
function ctld.loadUnloadFOBCrate(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
local _troops = _args[2]
|
|
|
|
if _heli == nil then
|
|
return
|
|
end
|
|
|
|
if _heli:inAir() == true then
|
|
return
|
|
end
|
|
|
|
|
|
local _side = _heli:getCoalition()
|
|
|
|
local _inZone = ctld.inLogisticsZone(_heli)
|
|
local _crateOnboard = ctld.inTransitFOBCrates[_heli:getName()] ~= nil
|
|
|
|
if _inZone == false and _crateOnboard == true then
|
|
|
|
ctld.inTransitFOBCrates[_heli:getName()] = nil
|
|
|
|
local _position = _heli:getPosition()
|
|
|
|
--try to spawn at 6 oclock to us
|
|
local _angle = math.atan2(_position.x.z, _position.x.x)
|
|
local _xOffset = math.cos(_angle) * -60
|
|
local _yOffset = math.sin(_angle) * -60
|
|
|
|
local _point = _heli:getPoint()
|
|
|
|
local _side = _heli:getCoalition()
|
|
|
|
local _unitId = mist.getNextUnitId()
|
|
|
|
local _name = string.format("FOB Crate #%i", _unitId)
|
|
|
|
local _spawnedCrate = ctld.spawnFOBCrateStatic(_heli:getCountry(),mist.getNextUnitId(),{x=_point.x+_xOffset,z=_point.z + _yOffset},_name)
|
|
|
|
if _side == 1 then
|
|
ctld.droppedFOBCratesRED[_name] = _name
|
|
else
|
|
ctld.droppedFOBCratesBLUE[_name] = _name
|
|
end
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " delivered a FOB Crate", 10)
|
|
|
|
ctld.displayMessageToGroup(_heli, "Delivered FOB Crate 60m at 6'oclock to you", 10)
|
|
|
|
elseif _inZone == true and _crateOnboard == true then
|
|
|
|
ctld.displayMessageToGroup(_heli, "FOB Crate dropped back to base", 10)
|
|
|
|
ctld.inTransitFOBCrates[_heli:getName()] = nil
|
|
|
|
elseif _inZone == true and _crateOnboard == false then
|
|
ctld.displayMessageToGroup(_heli, "FOB Crate Loaded", 10)
|
|
|
|
ctld.inTransitFOBCrates[_heli:getName()] = true
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " loaded a FOB Crate ready for delivery!", 10)
|
|
|
|
else
|
|
|
|
-- nearest Crate
|
|
local _crates = ctld.getFOBCratesAndDistance(_heli)
|
|
local _nearestCrate = ctld.getClosestCrate(_heli, _crates)
|
|
|
|
if _nearestCrate ~= nil and _nearestCrate.dist < 100 then
|
|
|
|
ctld.displayMessageToGroup(_heli, "FOB Crate Loaded", 10)
|
|
ctld.inTransitFOBCrates[_heli:getName()] = true
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " loaded a FOB Crate ready for delivery!", 10)
|
|
|
|
if _side == 1 then
|
|
ctld.droppedFOBCratesRED[_nearestCrate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.droppedFOBCratesBLUE[_nearestCrate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
--remove
|
|
_nearestCrate.crateUnit:destroy()
|
|
|
|
else
|
|
ctld.displayMessageToGroup(_heli, "There are no friendly logistic units nearby to load a FOB crate from!", 10)
|
|
end
|
|
|
|
end
|
|
|
|
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)
|
|
|
|
-- first check for extractable troops regardless of if we're in a zone or not
|
|
|
|
-- if not ctld.troopsOnboard(_heli,_troops) then
|
|
--
|
|
-- local _extract
|
|
--
|
|
-- if _troops then
|
|
-- if _heli:getCoalition() == 1 then
|
|
--
|
|
-- _extract = ctld.findNearestGroup(_heli, ctld.droppedTroopsRED)
|
|
-- else
|
|
--
|
|
-- _extract = ctld.findNearestGroup(_heli, ctld.droppedTroopsBLUE)
|
|
-- end
|
|
-- else
|
|
--
|
|
-- if _heli:getCoalition() == 1 then
|
|
--
|
|
-- _extract = ctld.findNearestGroup(_heli, ctld.droppedVehiclesRED)
|
|
-- else
|
|
--
|
|
-- _extract = ctld.findNearestGroup(_heli, ctld.droppedVehiclesBLUE)
|
|
-- end
|
|
--
|
|
-- end
|
|
--
|
|
-- if _extract ~= nil then
|
|
-- -- search for nearest troops to pickup
|
|
-- ctld.extractTroops(_heli,_troops)
|
|
--
|
|
-- return -- stop
|
|
-- end
|
|
-- end
|
|
|
|
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 = nil
|
|
|
|
else
|
|
ctld.displayMessageToGroup(_heli, "Dropped vehicles back to base", 20)
|
|
ctld.inTransitTroops[_heli:getName()].vehicles = nil
|
|
|
|
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 = nil, vehicles = nil }
|
|
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.details
|
|
|
|
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 = nil
|
|
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.details
|
|
|
|
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 = nil
|
|
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
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
if _heli == nil then
|
|
return
|
|
end
|
|
|
|
local _onboard = ctld.inTransitTroops[_heli:getName()]
|
|
|
|
if _onboard == nil then
|
|
|
|
if ctld.inTransitFOBCrates[_heli:getName()] == true then
|
|
ctld.displayMessageToGroup(_heli, "1 FOB Crate Onboard", 10)
|
|
else
|
|
ctld.displayMessageToGroup(_heli, "No troops onboard", 10)
|
|
end
|
|
|
|
|
|
else
|
|
local _troops = _onboard.troops
|
|
local _vehicles = _onboard.vehicles
|
|
|
|
local _txt = ""
|
|
|
|
if _troops ~= nil and _troops.units ~= nil and #_troops.units > 0 then
|
|
_txt = _txt .. " " .. #_troops.units .. " troops onboard\n"
|
|
end
|
|
|
|
if _vehicles ~= nil and _vehicles.units ~= nil and #_vehicles.units > 0 then
|
|
_txt = _txt .. " " .. #_vehicles.units .. " vehicles onboard\n"
|
|
end
|
|
|
|
if ctld.inTransitFOBCrates[_heli:getName()] == true then
|
|
_txt = _txt .. " 1 FOB Crate oboard\n"
|
|
end
|
|
|
|
if _txt ~= "" then
|
|
ctld.displayMessageToGroup(_heli, _txt, 10)
|
|
else
|
|
if ctld.inTransitFOBCrates[_heli:getName()] == true then
|
|
ctld.displayMessageToGroup(_heli, "1 FOB Crate Onboard", 10)
|
|
else
|
|
ctld.displayMessageToGroup(_heli, "No troops onboard", 10)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Removes troops from transport when it dies
|
|
function ctld.checkTransportStatus()
|
|
|
|
timer.scheduleFunction(ctld.checkTransportStatus,nil,timer.getTime()+3)
|
|
|
|
for _,_name in ipairs(ctld.transportPilotNames) do
|
|
|
|
local _transUnit = ctld.getTransportUnit(_name)
|
|
|
|
if _transUnit == nil then
|
|
--env.info("CTLD Transport Unit Dead event")
|
|
ctld.inTransitTroops[_name] = nil
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
function ctld.listNearbyCrates(_args)
|
|
|
|
local _message = ""
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
if _heli == nil then
|
|
|
|
return -- no heli!
|
|
end
|
|
|
|
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
|
|
|
|
local _fobCrates = ctld.getFOBCratesAndDistance(_heli)
|
|
|
|
local _fobMsg = ""
|
|
for _, _fobCrate in pairs(_fobCrates) do
|
|
|
|
if _fobCrate.dist < 1000 then
|
|
_fobMsg = _fobMsg..string.format("FOB Crate - %d m\n", _fobCrate.dist)
|
|
end
|
|
end
|
|
|
|
if _message ~= "" or _fobMsg ~= "" then
|
|
|
|
local _txt= ""
|
|
|
|
if _message ~= "" then
|
|
_txt = "Nearby Crates:\n" .. _message
|
|
end
|
|
|
|
if _fobMsg ~= "" then
|
|
|
|
if _message ~= "" then
|
|
_txt = _txt.."\n\n"
|
|
end
|
|
|
|
_txt = _txt.."Nearby FOB Crates (Not Slingloadable):\n" .. _fobMsg
|
|
|
|
end
|
|
|
|
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.getFOBCratesAndDistance(_heli)
|
|
|
|
local _crates = {}
|
|
|
|
local _allCrates
|
|
if _heli:getCoalition() == 1 then
|
|
|
|
_allCrates = ctld.droppedFOBCratesRED
|
|
else
|
|
|
|
_allCrates = ctld.droppedFOBCratesBLUE
|
|
end
|
|
|
|
for _crateName, _details in pairs(_allCrates) do
|
|
|
|
--get crate
|
|
local _crate = StaticObject.getByName(_crateName)
|
|
|
|
if _crate ~= nil and _crate:getLife() > 0 then
|
|
|
|
local _dist = ctld.getDistance(_crate:getPoint(), _heli:getPoint())
|
|
|
|
local _crateDetails = { crateUnit = _crate, dist = _dist, 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 ctld.isMultiCrate(_crate.details) then
|
|
-- multicrate
|
|
|
|
ctld.unpackMultiCrate(_heli,_crate,_crates)
|
|
|
|
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()
|
|
|
|
local _spawnedGroups = 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)
|
|
|
|
if ctld.isJTACUnitType(_crate.details.unit) and ctld.JTAC_dropEnabled then
|
|
|
|
local _code = table.remove(ctld.jtacGeneratedLaserCodes,1)
|
|
--put to the end
|
|
table.insert(ctld.jtacGeneratedLaserCodes,_code)
|
|
|
|
ctld.JTACAutoLase(_spawnedGroups:getName(),_code) --(_jtacGroupName, _laserCode, _smoke, _lock, _colour)
|
|
end
|
|
end
|
|
|
|
else
|
|
|
|
ctld.displayMessageToGroup(_heli, "No friendly crates close enough to unpack", 20)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- builds a fob!
|
|
function ctld.unpackFOBCrates(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
if _heli ~= nil and _heli:inAir() == false then
|
|
|
|
local _crates = ctld.getFOBCratesAndDistance(_heli)
|
|
local _nearestCrate = ctld.getClosestCrate(_heli, _crates)
|
|
|
|
if _nearestCrate ~= nil and _nearestCrate.dist < 750 then
|
|
|
|
if ctld.inLogisticsZone(_heli) == true then
|
|
|
|
ctld.displayMessageToGroup(_heli, "You can't unpack the FOB here! Take it to where it's needed!", 20)
|
|
|
|
return
|
|
end
|
|
|
|
-- unpack multi crate
|
|
local _nearbyMultiCrates = {}
|
|
|
|
for _, _nearbyCrate in pairs(_crates) do
|
|
|
|
if _nearbyCrate.dist < 750 then
|
|
|
|
table.insert(_nearbyMultiCrates, _nearbyCrate)
|
|
|
|
if #_nearbyMultiCrates == ctld.cratesRequiredForFOB then
|
|
break
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
--- check crate count
|
|
if #_nearbyMultiCrates == ctld.cratesRequiredForFOB then
|
|
|
|
-- destroy crates
|
|
|
|
local _points = {}
|
|
|
|
for _, _crate in pairs(_nearbyMultiCrates) do
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.droppedFOBCratesRED[_crate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.droppedFOBCratesRED[_crate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
table.insert(_points, _crate.crateUnit:getPoint())
|
|
|
|
--destroy
|
|
_crate.crateUnit:destroy()
|
|
end
|
|
|
|
local _centroid = ctld.getCentroid(_points)
|
|
|
|
timer.scheduleFunction(
|
|
function(_args)
|
|
|
|
local _unitId = mist.getNextUnitId()
|
|
local _name = "Deployed FOB #".._unitId
|
|
|
|
local _fob = ctld.spawnFOB(_args[2],_unitId,_args[1],_name)
|
|
|
|
--make it able to deploy crates
|
|
table.insert(ctld.logisticUnits, _fob:getName())
|
|
|
|
if ctld.troopPickupAtFOB == true then
|
|
|
|
table.insert(ctld.builtFOBS, _fob:getName())
|
|
|
|
trigger.action.outTextForCoalition(_args[3],"Finished building FOB! Crates and Troops can now be picked up.", 10)
|
|
else
|
|
trigger.action.outTextForCoalition(_args[3],"Finished building FOB! Crates can now be picked up.", 10)
|
|
end
|
|
|
|
-- spawn smoke
|
|
trigger.action.smoke(_args[1],trigger.smokeColor.Green)
|
|
|
|
end, {_centroid, _heli:getCountry(),_heli:getCoalition()}, timer.getTime() + ctld.buildTimeFOB)
|
|
|
|
|
|
local _txt = string.format("%s started building FOB using %d FOB crates, it will be finished in %d seconds",ctld.getPlayerNameOrType(_heli),#_nearbyMultiCrates,ctld.buildTimeFOB)
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), _txt, 10)
|
|
|
|
else
|
|
|
|
local _txt = string.format("Cannot build FOB!\n\nIt requires %d FOB crates and there are %d \n\nOr the crates are not within 750m of each other",ctld.cratesRequiredForFOB,#_nearbyMultiCrates)
|
|
|
|
ctld.displayMessageToGroup(_heli, _txt, 20)
|
|
end
|
|
|
|
|
|
else
|
|
|
|
ctld.displayMessageToGroup(_heli, "No friendly FOB crates close enough to unpack", 20)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- gets the center of a bunch of points!
|
|
-- return proper DCS point with height
|
|
function ctld.getCentroid(_points)
|
|
local _tx, _ty = 0, 0
|
|
for _index, _point in ipairs(_points) do
|
|
_tx = _tx + _point.x
|
|
_ty = _ty + _point.z
|
|
end
|
|
|
|
local _npoints = #_points
|
|
|
|
local _point= {x = _tx / _npoints, z = _ty / _npoints }
|
|
|
|
_point.y = land.getHeight({_point.x, _point.z})
|
|
|
|
return _point
|
|
end
|
|
|
|
|
|
function ctld.isMultiCrate(_crateDetails)
|
|
|
|
if string.match(_crateDetails.desc, "HAWK")
|
|
or (_crateDetails.cratesRequired ~= nil and _crateDetails.cratesRequired > 1) then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
|
|
end
|
|
|
|
function ctld.rearmHawk(_heli,_nearestCrate,_nearbyCrates)
|
|
|
|
-- are we adding to existing hawk system?
|
|
if _nearestCrate.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[_nearestCrate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_nearestCrate.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 true -- all done so quit
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function ctld.unpackHawk(_heli,_nearestCrate,_nearbyCrates)
|
|
|
|
if ctld.rearmHawk(_heli,_nearestCrate,_nearbyCrates) then
|
|
-- rearmed hawk
|
|
return
|
|
end
|
|
|
|
|
|
-- are there all the pieces close enough together
|
|
local _hawkParts = { ["Hawk ln"] = false, ["Hawk tr"] = false, ["Hawk sr"] = false }
|
|
|
|
for _, _nearbyCrate in pairs(_nearbyCrates) 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
|
|
end
|
|
|
|
function ctld.unpackMultiCrate(_heli,_nearestCrate,_nearbyCrates)
|
|
|
|
if string.match(_nearestCrate.details.desc, "HAWK") then
|
|
ctld.unpackHawk(_heli,_nearestCrate,_nearbyCrates)
|
|
|
|
return -- stop processing
|
|
end
|
|
|
|
-- unpack multi crate
|
|
local _nearbyMultiCrates = {}
|
|
|
|
for _, _nearbyCrate in pairs(_nearbyCrates) do
|
|
|
|
if _nearbyCrate.dist < 300 then
|
|
|
|
if _nearbyCrate.details.unit == _nearestCrate.details.unit then
|
|
|
|
table.insert(_nearbyMultiCrates, _nearbyCrate)
|
|
|
|
if #_nearbyMultiCrates == _nearestCrate.details.cratesRequired then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- check crate count
|
|
if #_nearbyMultiCrates == _nearestCrate.details.cratesRequired then
|
|
|
|
local _point = _nearestCrate.crateUnit:getPoint()
|
|
|
|
-- destroy crates
|
|
for _, _crate in pairs(_nearbyMultiCrates) do
|
|
|
|
if _point == nil then
|
|
_point = _crate.crateUnit:getPoint()
|
|
end
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.spawnedCratesRED[_crate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_crate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
--destroy
|
|
_crate.crateUnit:destroy()
|
|
end
|
|
|
|
|
|
local _spawnedGroup = ctld.spawnCrateGroup(_heli,{_point} ,{_nearestCrate.details.unit})
|
|
|
|
local _txt = string.format("%s successfully deployed %s to the field using %d crates",ctld.getPlayerNameOrType(_heli),_nearestCrate.details.desc,#_nearbyMultiCrates)
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), _txt, 10)
|
|
|
|
else
|
|
|
|
local _txt = string.format("Cannot build %s!\n\nIt requires %d crates and there are %d \n\nOr the crates are not within 300m of each other",_nearestCrate.details.desc,_nearestCrate.details.cratesRequired,#_nearbyMultiCrates)
|
|
|
|
ctld.displayMessageToGroup(_heli, _txt, 20)
|
|
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
|
|
|
|
local _unitId = mist.getNextUnitId()
|
|
local _details = {type= _types[1], unitId = _unitId, name = string.format("Unpacked %s #%i",_types[1],_unitId)}
|
|
|
|
_group.units[1] = ctld.createUnit(_positions[1].x + 5, _positions[1].z + 5, 120, _details)
|
|
|
|
else
|
|
|
|
for _i, _pos in ipairs(_positions) do
|
|
|
|
local _unitId = mist.getNextUnitId()
|
|
local _details = {type= _types[_i], unitId = _unitId, name = string.format("Unpacked %s #%i",_types[_i],_unitId)}
|
|
|
|
_group.units[_i] = ctld.createUnit(_pos.x + 5, _pos.z + 5, 120, _details)
|
|
end
|
|
end
|
|
|
|
local _spawnedGroup = coalition.addGroup(_heli:getCountry(), 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(_point, _details, _spawnBehind,_maxSearch)
|
|
|
|
local _groupName = _details.groupName
|
|
|
|
local _group = {
|
|
["visible"] = false,
|
|
["groupId"] = _details.groupId,
|
|
["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, _detail in ipairs(_details.units) do
|
|
|
|
local _angle = math.pi * 2 * (_i - 1) / #_details.units
|
|
local _xOffset = math.cos(_angle) * 30
|
|
local _yOffset = math.sin(_angle) * 30
|
|
|
|
_group.units[_i] = ctld.createUnit(_pos.x + _xOffset, _pos.z + _yOffset, _angle, _detail)
|
|
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, _detail in ipairs(_details.units) do
|
|
_group.units[_i] = ctld.createUnit(_pos.x + (_xOffset + 10 * _i), _pos.z + (_yOffset + 10 * _i), _angle,_detail)
|
|
end
|
|
end
|
|
|
|
local _spawnedGroup = coalition.addGroup(_details.country, Group.Category.GROUND, _group)
|
|
|
|
|
|
-- find nearest enemy and head there
|
|
if _maxSearch == nil then
|
|
_maxSearch = ctld.maximumSearchDistance
|
|
end
|
|
|
|
local _enemyPos = ctld.findNearestEnemy(_details.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 _closestGroupDetails = {}
|
|
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 _groupDetails = { groupId = _group:getID(), groupName = _group:getName(), side = _group:getCoalition(), units = {} }
|
|
|
|
-- find alive leader
|
|
for x = 1, #_units do
|
|
if _units[x]:getLife() > 0 then
|
|
|
|
if _leader == nil then
|
|
_leader = _units[x]
|
|
-- set country based on leader
|
|
_groupDetails.country = _leader:getCountry()
|
|
end
|
|
|
|
local _unitDetails = {type = _units[x]:getTypeName(), unitId = _units[x]:getID(), name = _units[x]:getName() }
|
|
|
|
table.insert(_groupDetails.units, _unitDetails)
|
|
end
|
|
end
|
|
|
|
if _leader ~= nil then
|
|
local _leaderPos = _leader:getPoint()
|
|
local _dist = ctld.getDistance(_heliPoint, _leaderPos)
|
|
if _dist < _closestGroupDist then
|
|
_closestGroupDist = _dist
|
|
_closestGroupDetails = _groupDetails
|
|
_closestGroup = _group
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
if _closestGroup ~= nil then
|
|
|
|
return { group = _closestGroup, details = _closestGroupDetails }
|
|
else
|
|
|
|
return nil
|
|
end
|
|
end
|
|
|
|
|
|
function ctld.createUnit(_x, _y, _angle, _details)
|
|
|
|
local _newUnit = {
|
|
["y"] = _y,
|
|
["type"] = _details.type,
|
|
["name"] = _details.name,
|
|
["unitId"] = _details.unitId,
|
|
["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
|
|
|
|
-- now check spawned fobs
|
|
for _, _fobName in ipairs(ctld.builtFOBS) do
|
|
|
|
local _fob = StaticObject.getByName(_fobName)
|
|
|
|
if _fob ~= nil and _fob:isExist() and _fob:getCoalition() == _heli:getCoalition() and _fob:getLife() > 0 then
|
|
|
|
--get distance to center
|
|
|
|
local _dist = ctld.getDistance(_heliPoint,_fob:getPoint())
|
|
|
|
if _dist <= 150 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 = string.lower(_unit:getTypeName())
|
|
|
|
local _found = false
|
|
for _,_name in ipairs(ctld.vehicleTransportEnabled) do
|
|
|
|
-- env.info("TYPE: ".._type)
|
|
|
|
local _nameLower = string.lower(_name)
|
|
-- env.info("NAME: ".._nameLower)
|
|
|
|
if string.match(_type, _nameLower) ~= nil then
|
|
-- env.info("MATCH")
|
|
_found = true
|
|
break
|
|
end
|
|
|
|
end
|
|
|
|
return _found
|
|
|
|
end
|
|
|
|
function ctld.isJTACUnitType(_type)
|
|
|
|
_type = string.lower(_type)
|
|
|
|
local _found = false
|
|
for _,_name in ipairs(ctld.jtacUnitTypes) do
|
|
local _nameLower = string.lower(_name)
|
|
if string.match(_type, _nameLower) ~= nil then
|
|
_found = true
|
|
break
|
|
end
|
|
|
|
end
|
|
|
|
return _found
|
|
|
|
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 ctld.addF10MenuOptions()
|
|
-- Loop through all Heli units
|
|
|
|
timer.scheduleFunction(ctld.addF10MenuOptions, 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[tostring(_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 })
|
|
|
|
if ctld.enabledFOBBuilding then
|
|
|
|
missionCommands.addCommandForGroup(_groupId, "Load / Unload FOB Crate", { "Troop Transport" }, ctld.loadUnloadFOBCrate, { _unitName,false })
|
|
end
|
|
|
|
end
|
|
|
|
missionCommands.addCommandForGroup(_groupId, "Check Status", { "Troop Transport" }, ctld.checkTroopStatus, { _unitName })
|
|
|
|
if ctld.enableCrates then
|
|
|
|
if ctld.unitCanCarryVehicles(_unit) == false then
|
|
|
|
-- add menu for spawning crates
|
|
for _subMenuName, _crates in pairs(ctld.spawnableCrates) do
|
|
|
|
missionCommands.addSubMenuForGroup(_groupId, _subMenuName)
|
|
for _, _crate in pairs(_crates) do
|
|
|
|
if ctld.isJTACUnitType(_crate.unit) == false or ( ctld.isJTACUnitType(_crate.unit) == true and ctld.JTAC_dropEnabled ) then
|
|
if _crate.side == nil or (_crate.side == _unit:getCoalition()) then
|
|
missionCommands.addCommandForGroup(_groupId, _crate.desc, {_subMenuName }, ctld.spawnCrate, { _unitName,_crate.weight })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
missionCommands.addSubMenuForGroup(_groupId, "CTLD Commands")
|
|
missionCommands.addCommandForGroup(_groupId, "List Nearby Crates", { "CTLD Commands" }, ctld.listNearbyCrates, { _unitName })
|
|
missionCommands.addCommandForGroup(_groupId, "Unpack Sling Crates", { "CTLD Commands" }, ctld.unpackCrates, { _unitName })
|
|
|
|
if ctld.enabledFOBBuilding then
|
|
missionCommands.addCommandForGroup(_groupId, "Unpack FOB Crates", { "CTLD Commands" }, ctld.unpackFOBCrates, { _unitName })
|
|
end
|
|
|
|
if ctld.enableSmokeDrop then
|
|
missionCommands.addCommandForGroup(_groupId, "Drop Red Smoke", { "CTLD Commands" }, ctld.dropSmoke, { _unitName, trigger.smokeColor.Red })
|
|
missionCommands.addCommandForGroup(_groupId, "Drop Blue Smoke", { "CTLD 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", { "CTLD 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[tostring(_groupId)] = true
|
|
end
|
|
else
|
|
-- env.info(string.format("unit nil %s",_unitName))
|
|
end
|
|
end
|
|
|
|
if ctld.JTAC_jtacStatusF10 then
|
|
-- get all BLUE players
|
|
ctld.addJTACRadioCommand(coalition.side.BLUE)
|
|
|
|
-- get all RED players
|
|
ctld.addJTACRadioCommand(coalition.side.RED)
|
|
end
|
|
|
|
|
|
end
|
|
|
|
function ctld.addJTACRadioCommand(_side)
|
|
|
|
local _players = coalition.getPlayers(_side)
|
|
|
|
if _players ~= nil then
|
|
|
|
for _,_playerUnit in pairs(_players) do
|
|
|
|
local _groupId = _playerUnit:getGroup():getID()
|
|
|
|
-- env.info("adding command for "..index)
|
|
if ctld.jtacRadioAdded[tostring(_groupId)] == nil then
|
|
-- env.info("about command for "..index)
|
|
missionCommands.addCommandForGroup(_groupId, "JTAC Status", nil, ctld.getJTACStatus, _playerUnit:getCoalition())
|
|
ctld.jtacRadioAdded[tostring(_groupId)] = true
|
|
-- env.info("Added command for " .. index)
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
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
|
|
|
|
|
|
------------ JTAC -----------
|
|
|
|
|
|
ctld.jtacLaserPoints = {}
|
|
ctld.jtacIRPoints = {}
|
|
ctld.jtacSmokeMarks = {}
|
|
ctld.jtacUnits = {} -- list of JTAC units for f10 command
|
|
ctld.jtacCurrentTargets = {}
|
|
ctld.jtacRadioAdded = {} --keeps track of who's had the radio command added
|
|
ctld.jtacGeneratedLaserCodes = {} -- keeps track of generated codes, cycles when they run out
|
|
ctld.jtacLaserPointCodes = {}
|
|
|
|
|
|
function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour)
|
|
|
|
|
|
if _lock == nil then
|
|
|
|
_lock = ctld.JTAC_lock
|
|
end
|
|
|
|
|
|
ctld.jtacLaserPointCodes[_jtacGroupName] = _laserCode
|
|
|
|
local _jtacGroup = ctld.getGroup(_jtacGroupName)
|
|
local _jtacUnit
|
|
|
|
if _jtacGroup == nil or #_jtacGroup == 0 then
|
|
|
|
if ctld.jtacUnits[_jtacGroupName] ~= nil then
|
|
ctld.notifyCoalition("JTAC Group " .. _jtacGroupName .. " KIA!", 10,ctld.jtacUnits[_jtacGroupName].side)
|
|
end
|
|
|
|
--remove from list
|
|
ctld.jtacUnits[_jtacGroupName] = nil
|
|
|
|
ctld.cleanupJTAC(_jtacGroupName)
|
|
|
|
return
|
|
else
|
|
|
|
_jtacUnit = _jtacGroup[1]
|
|
--add to list
|
|
ctld.jtacUnits[_jtacGroupName] = {name = _jtacUnit:getName(), side = _jtacUnit:getCoalition()}
|
|
|
|
-- work out smoke colour
|
|
if _colour == nil then
|
|
|
|
if _jtacUnit:getCoalition() == 1 then
|
|
_colour = ctld.JTAC_smokeColour_RED
|
|
else
|
|
_colour = ctld.JTAC_smokeColour_BLUE
|
|
end
|
|
end
|
|
|
|
|
|
if _smoke == nil then
|
|
|
|
if _jtacUnit:getCoalition() == 1 then
|
|
_smoke = ctld.JTAC_smokeOn_RED
|
|
else
|
|
_smoke = ctld.JTAC_smokeOn_BLUE
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
-- search for current unit
|
|
|
|
if _jtacUnit:isActive() == false then
|
|
|
|
ctld.cleanupJTAC(_jtacGroupName)
|
|
|
|
env.info(_jtacGroupName .. ' Not Active - Waiting 30 seconds')
|
|
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour }, timer.getTime() + 30)
|
|
|
|
return
|
|
end
|
|
|
|
local _enemyUnit = ctld.getCurrentUnit(_jtacUnit, _jtacGroupName)
|
|
|
|
if _enemyUnit == nil and ctld.jtacCurrentTargets[_jtacGroupName] ~= nil then
|
|
|
|
local _tempUnitInfo = ctld.jtacCurrentTargets[_jtacGroupName]
|
|
|
|
-- env.info("TEMP UNIT INFO: " .. tempUnitInfo.name .. " " .. tempUnitInfo.unitType)
|
|
|
|
local _tempUnit = Unit.getByName(_tempUnitInfo.name)
|
|
|
|
if _tempUnit ~= nil and _tempUnit:getLife() > 0 and _tempUnit:isActive() == true then
|
|
ctld.notifyCoalition(_jtacGroupName .. " target " .. _tempUnitInfo.unitType .. " lost. Scanning for Targets. ", 10,_jtacUnit:getCoalition())
|
|
else
|
|
ctld.notifyCoalition(_jtacGroupName .. " target " .. _tempUnitInfo.unitType .. " KIA. Good Job! Scanning for Targets. ", 10,_jtacUnit:getCoalition())
|
|
end
|
|
|
|
--remove from smoke list
|
|
ctld.jtacSmokeMarks[_tempUnitInfo.name] = nil
|
|
|
|
-- remove from target list
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = nil
|
|
|
|
--stop lasing
|
|
ctld.cancelLase(_jtacGroupName)
|
|
|
|
end
|
|
|
|
|
|
if _enemyUnit == nil then
|
|
_enemyUnit = ctld.findNearestVisibleEnemy(_jtacUnit, _lock)
|
|
|
|
if _enemyUnit ~= nil then
|
|
|
|
-- store current target for easy lookup
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = { name = _enemyUnit:getName(), unitType = _enemyUnit:getTypeName(), unitId = _enemyUnit:getID() }
|
|
|
|
ctld.notifyCoalition(_jtacGroupName .. " lasing new target " .. _enemyUnit:getTypeName() .. '. CODE: ' .. _laserCode ..ctld.getPositionString(_enemyUnit) , 10,_jtacUnit:getCoalition())
|
|
|
|
-- create smoke
|
|
if _smoke == true then
|
|
|
|
--create first smoke
|
|
ctld.createSmokeMarker(_enemyUnit, _colour)
|
|
end
|
|
end
|
|
end
|
|
|
|
if _enemyUnit ~= nil then
|
|
|
|
ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
|
|
|
|
-- env.info('Timer timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName())
|
|
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour }, timer.getTime() + 1)
|
|
|
|
|
|
if _smoke == true then
|
|
local _nextSmokeTime = ctld.jtacSmokeMarks[_enemyUnit:getName()]
|
|
|
|
--recreate smoke marker after 5 mins
|
|
if _nextSmokeTime ~= nil and _nextSmokeTime < timer.getTime() then
|
|
|
|
ctld.createSmokeMarker(_enemyUnit, _colour)
|
|
end
|
|
end
|
|
|
|
else
|
|
-- env.info('LASE: No Enemies Nearby')
|
|
|
|
-- stop lazing the old spot
|
|
ctld.cancelLase(_jtacGroupName)
|
|
-- env.info('Timer Slow timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName())
|
|
|
|
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour }, timer.getTime() + 5)
|
|
end
|
|
end
|
|
|
|
|
|
-- used by the timer function
|
|
function ctld.timerJTACAutoLase(_args)
|
|
|
|
ctld.JTACAutoLase(_args[1], _args[2], _args[3], _args[4], _args[5])
|
|
end
|
|
|
|
function ctld.cleanupJTAC(_jtacGroupName)
|
|
-- clear laser - just in case
|
|
ctld.cancelLase(_jtacGroupName)
|
|
|
|
-- Cleanup
|
|
ctld.jtacUnits[_jtacGroupName] = nil
|
|
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = nil
|
|
end
|
|
|
|
|
|
function ctld.notifyCoalition(_message, _displayFor, _side)
|
|
|
|
|
|
trigger.action.outTextForCoalition(_side, _message, _displayFor)
|
|
trigger.action.outSoundForCoalition(_side, "radiobeep.ogg")
|
|
end
|
|
|
|
function ctld.createSmokeMarker(_enemyUnit, _colour)
|
|
|
|
--recreate in 5 mins
|
|
ctld.jtacSmokeMarks[_enemyUnit:getName()] = timer.getTime() + 300.0
|
|
|
|
-- move smoke 2 meters above target for ease
|
|
local _enemyPoint = _enemyUnit:getPoint()
|
|
trigger.action.smoke({ x = _enemyPoint.x, y = _enemyPoint.y + 2.0, z = _enemyPoint.z }, _colour)
|
|
end
|
|
|
|
function ctld.cancelLase(_jtacGroupName)
|
|
|
|
--local index = "JTAC_"..jtacUnit:getID()
|
|
|
|
local _tempLase = ctld.jtacLaserPoints[_jtacGroupName]
|
|
|
|
if _tempLase ~= nil then
|
|
Spot.destroy(_tempLase)
|
|
ctld.jtacLaserPoints[_jtacGroupName] = nil
|
|
|
|
-- env.info('Destroy laze '..index)
|
|
|
|
_tempLase = nil
|
|
end
|
|
|
|
local _tempIR = ctld.jtacIRPoints[_jtacGroupName]
|
|
|
|
if _tempIR ~= nil then
|
|
Spot.destroy(_tempIR)
|
|
ctld.jtacIRPoints[_jtacGroupName] = nil
|
|
|
|
-- env.info('Destroy laze '..index)
|
|
|
|
_tempIR = nil
|
|
end
|
|
end
|
|
|
|
function ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
|
|
|
|
--cancelLase(jtacGroupName)
|
|
|
|
local _spots = {}
|
|
|
|
local _enemyVector = _enemyUnit:getPoint()
|
|
local _enemyVectorUpdated = { x = _enemyVector.x, y = _enemyVector.y + 2.0, z = _enemyVector.z }
|
|
|
|
local _oldLase = ctld.jtacLaserPoints[_jtacGroupName]
|
|
local _oldIR = ctld.jtacIRPoints[_jtacGroupName]
|
|
|
|
if _oldLase == nil or _oldIR == nil then
|
|
|
|
-- create lase
|
|
|
|
local _status, _result = pcall(function()
|
|
_spots['irPoint'] = Spot.createInfraRed(_jtacUnit, { x = 0, y = 2.0, z = 0 }, _enemyVectorUpdated)
|
|
_spots['laserPoint'] = Spot.createLaser(_jtacUnit, { x = 0, y = 2.0, z = 0 }, _enemyVectorUpdated, _laserCode)
|
|
return _spots
|
|
end)
|
|
|
|
if not _status then
|
|
env.error('ERROR: ' .. _result, false)
|
|
else
|
|
if _result.irPoint then
|
|
|
|
-- env.info(jtacUnit:getName() .. ' placed IR Pointer on '..enemyUnit:getName())
|
|
|
|
ctld.jtacIRPoints[_jtacGroupName] = _result.irPoint --store so we can remove after
|
|
|
|
end
|
|
if _result.laserPoint then
|
|
|
|
-- env.info(jtacUnit:getName() .. ' is Lasing '..enemyUnit:getName()..'. CODE:'..laserCode)
|
|
|
|
ctld.jtacLaserPoints[_jtacGroupName] = _result.laserPoint
|
|
end
|
|
end
|
|
|
|
else
|
|
|
|
-- update lase
|
|
|
|
if _oldLase ~=nil then
|
|
_oldLase:setPoint(_enemyVectorUpdated)
|
|
end
|
|
|
|
if _oldIR ~= nil then
|
|
_oldIR:setPoint(_enemyVectorUpdated)
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- get currently selected unit and check they're still in range
|
|
function ctld.getCurrentUnit(_jtacUnit, _jtacGroupName)
|
|
|
|
|
|
local _unit = nil
|
|
|
|
if ctld.jtacCurrentTargets[_jtacGroupName] ~= nil then
|
|
_unit = Unit.getByName(ctld.jtacCurrentTargets[_jtacGroupName].name)
|
|
end
|
|
|
|
local _tempPoint = nil
|
|
local _tempDist = nil
|
|
local _tempPosition = nil
|
|
|
|
local _jtacPosition = _jtacUnit:getPosition()
|
|
local _jtacPoint = _jtacUnit:getPoint()
|
|
|
|
if _unit ~= nil and _unit:getLife() > 0 and _unit:isActive() == true then
|
|
|
|
-- calc distance
|
|
_tempPoint = _unit:getPoint()
|
|
-- tempPosition = unit:getPosition()
|
|
|
|
_tempDist = ctld.getDistance(_unit:getPoint(), _jtacUnit:getPoint() )
|
|
if _tempDist < ctld.JTAC_maxDistance then
|
|
-- calc visible
|
|
|
|
-- check slightly above the target as rounding errors can cause issues, plus the unit has some height anyways
|
|
local _offsetEnemyPos = { x = _tempPoint.x, y = _tempPoint.y + 2.0, z = _tempPoint.z }
|
|
local _offsetJTACPos = { x = _jtacPoint.x, y = _jtacPoint.y + 2.0, z = _jtacPoint.z }
|
|
|
|
if land.isVisible(_offsetEnemyPos, _offsetJTACPos) then
|
|
return _unit
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
|
|
-- Find nearest enemy to JTAC that isn't blocked by terrain
|
|
function ctld.findNearestVisibleEnemy(_jtacUnit, _targetType)
|
|
|
|
local _x = 1
|
|
local _i = 1
|
|
|
|
local _units = nil
|
|
local _groupName = nil
|
|
|
|
local _nearestUnit = nil
|
|
local _nearestDistance = ctld.JTAC_maxDistance
|
|
|
|
local _enemyGroups
|
|
|
|
if _jtacUnit:getCoalition() == 1 then
|
|
_enemyGroups = coalition.getGroups(2, Group.Category.GROUND)
|
|
else
|
|
_enemyGroups = coalition.getGroups(1, Group.Category.GROUND)
|
|
end
|
|
|
|
local _jtacPoint = _jtacUnit:getPoint()
|
|
local _jtacPosition = _jtacUnit:getPosition()
|
|
|
|
local _tempPoint = nil
|
|
local _tempPosition = nil
|
|
|
|
local _tempDist = nil
|
|
|
|
-- finish this function
|
|
for _i = 1, #_enemyGroups do
|
|
if _enemyGroups[_i] ~= nil then
|
|
_groupName = _enemyGroups[_i]:getName()
|
|
_units = ctld.getGroup(_groupName)
|
|
if #_units > 0 then
|
|
|
|
for _x = 1, #_units do
|
|
|
|
--check to see if a JTAC has already targeted this unit
|
|
local _targeted = ctld.alreadyTarget(_jtacUnit, _units[_x])
|
|
local _allowedTarget = true
|
|
|
|
if _targetType == "vehicle" then
|
|
|
|
_allowedTarget = ctld.isVehicle(_units[_x])
|
|
|
|
elseif _targetType == "troop" then
|
|
|
|
_allowedTarget = ctld.isInfantry(_units[_x])
|
|
|
|
end
|
|
|
|
if _units[_x]:isActive() == true and _targeted == false and _allowedTarget == true then
|
|
|
|
-- calc distance
|
|
_tempPoint = _units[_x]:getPoint()
|
|
_tempDist = ctld.getDistance(_tempPoint, _jtacPoint)
|
|
|
|
if _tempDist < ctld.JTAC_maxDistance and _tempDist < _nearestDistance then
|
|
|
|
local _offsetEnemyPos = { x = _tempPoint.x, y = _tempPoint.y + 2.0, z = _tempPoint.z }
|
|
local _offsetJTACPos = { x = _jtacPoint.x, y = _jtacPoint.y + 2.0, z = _jtacPoint.z }
|
|
|
|
|
|
-- calc visible
|
|
if land.isVisible(_offsetEnemyPos, _offsetJTACPos) then
|
|
|
|
_nearestDistance = _tempDist
|
|
_nearestUnit = _units[_x]
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
if _nearestUnit == nil then
|
|
return nil
|
|
end
|
|
|
|
|
|
return _nearestUnit
|
|
end
|
|
-- tests whether the unit is targeted by another JTAC
|
|
function ctld.alreadyTarget(_jtacUnit, _enemyUnit)
|
|
|
|
for _ , _jtacTarget in pairs(ctld.jtacCurrentTargets) do
|
|
|
|
if _jtacTarget.unitId == _enemyUnit:getID() then
|
|
-- env.info("ALREADY TARGET")
|
|
return true
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
-- Returns only alive units from group but the group / unit may not be active
|
|
|
|
function ctld.getGroup(groupName)
|
|
|
|
local _groupUnits = Group.getByName(groupName)
|
|
|
|
local _filteredUnits = {} --contains alive units
|
|
local _x = 1
|
|
|
|
if _groupUnits ~= nil then
|
|
|
|
_groupUnits = _groupUnits:getUnits()
|
|
|
|
if _groupUnits ~= nil and #_groupUnits > 0 then
|
|
for _x = 1, #_groupUnits do
|
|
if _groupUnits[_x]:getLife() > 0 and _groupUnits[_x]:isExist() then
|
|
table.insert(_filteredUnits, _groupUnits[_x])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return _filteredUnits
|
|
end
|
|
|
|
|
|
-- gets the JTAC status and displays to coalition units
|
|
function ctld.getJTACStatus(_side)
|
|
|
|
--returns the status of all JTAC units
|
|
|
|
local _jtacGroupName = nil
|
|
local _jtacUnit = nil
|
|
|
|
local _message = "JTAC STATUS: \n\n"
|
|
|
|
for _jtacGroupName, _jtacDetails in pairs(ctld.jtacUnits) do
|
|
|
|
--look up units
|
|
_jtacUnit = Unit.getByName(_jtacDetails.name)
|
|
|
|
if _jtacUnit ~= nil and _jtacUnit:getLife() > 0 and _jtacUnit:isActive() == true and _jtacUnit:getCoalition() == _side then
|
|
|
|
local _enemyUnit = ctld.getCurrentUnit(_jtacUnit, _jtacGroupName)
|
|
|
|
local _laserCode = ctld.jtacLaserPointCodes[_jtacGroupName]
|
|
|
|
if _laserCode == nil then
|
|
_laserCode = "UNKNOWN"
|
|
end
|
|
|
|
if _enemyUnit ~= nil and _enemyUnit:getLife() > 0 and _enemyUnit:isActive() == true then
|
|
_message = _message .. "" .. _jtacGroupName .. " targeting " .. _enemyUnit:getTypeName().. " CODE: ".. _laserCode .. ctld.getPositionString(_enemyUnit) .. "\n"
|
|
else
|
|
_message = _message .. "" .. _jtacGroupName .. " searching for targets" .. ctld.getPositionString(_jtacUnit) .."\n"
|
|
end
|
|
end
|
|
end
|
|
|
|
if _message == "JTAC STATUS: \n\n" then
|
|
_message = "No Active JTACs"
|
|
end
|
|
|
|
|
|
ctld.notifyCoalition(_message, 10,_side)
|
|
end
|
|
|
|
|
|
|
|
function ctld.isInfantry(_unit)
|
|
|
|
local _typeName = _unit:getTypeName()
|
|
|
|
--type coerce tostring
|
|
_typeName = string.lower(_typeName .."")
|
|
|
|
local _soldierType = { "infantry","paratrooper","stinger","manpad","mortar"}
|
|
|
|
for _key, _value in pairs(_soldierType) do
|
|
if string.match(_typeName, _value) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
-- assume anything that isnt soldier is vehicle
|
|
function ctld.isVehicle(_unit)
|
|
|
|
if ctld.isInfantry(_unit) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
-- The entered value can range from 1111 - 1788,
|
|
-- -- but the first digit of the series must be a 1 or 2
|
|
-- -- and the last three digits must be between 1 and 8.
|
|
-- The range used to be bugged so its not 1 - 8 but 0 - 7.
|
|
-- function below will use the range 1-7 just incase
|
|
function ctld.generateLaserCode()
|
|
|
|
ctld.jtacGeneratedLaserCodes = {}
|
|
|
|
-- generate list of laser codes
|
|
local _code = 1111
|
|
|
|
local _count = 1
|
|
|
|
while _code < 1777 and _count < 30 do
|
|
|
|
while true do
|
|
|
|
_code = _code+1
|
|
|
|
if not ctld.containsDigit(_code,8)
|
|
and not ctld.containsDigit(_code,9)
|
|
and not ctld.containsDigit(_code,0) then
|
|
|
|
table.insert(ctld.jtacGeneratedLaserCodes,_code)
|
|
|
|
--env.info(_code.." Code")
|
|
break
|
|
end
|
|
end
|
|
_count = _count + 1
|
|
end
|
|
|
|
end
|
|
|
|
function ctld.containsDigit(_number,_numberToFind)
|
|
|
|
local _thisNumber = _number
|
|
local _thisDigit = 0
|
|
|
|
while _thisNumber ~= 0 do
|
|
|
|
_thisDigit = _thisNumber % 10
|
|
_thisNumber = math.floor(_thisNumber / 10)
|
|
|
|
if _thisDigit == _numberToFind then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
function ctld.getPositionString(_unit)
|
|
|
|
if ctld.JTAC_location == false then
|
|
return ""
|
|
end
|
|
|
|
local _lat, _lon = coord.LOtoLL(_unit:getPosition().p)
|
|
|
|
local _latLngStr = mist.tostringLL(_lat, _lon,3,false)
|
|
|
|
local _mgrsString = mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(_unit:getPosition().p)),5)
|
|
|
|
return " @ " .. _latLngStr .. " - MGRS ".. _mgrsString
|
|
|
|
end
|
|
|
|
|
|
-- ***************** SETUP SCRIPT ****************
|
|
|
|
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.inTransitFOBCrates = {}
|
|
|
|
ctld.droppedFOBCratesRED = {}
|
|
ctld.droppedFOBCratesBLUE = {}
|
|
|
|
ctld.builtFOBS = {} -- stores fully built fobs
|
|
|
|
ctld.completeHawkSystems = {} -- stores complete spawned groups from multiple crates
|
|
|
|
|
|
--used to lookup what the crate will contain
|
|
ctld.crateLookupTable = {}
|
|
|
|
-- 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
|
|
|
|
-- create crate lookup table
|
|
for _subMenuName, _crates in pairs(ctld.spawnableCrates) do
|
|
|
|
for _, _crate in pairs(_crates) 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)] = _crate
|
|
end
|
|
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
|
|
|
|
|
|
-- Scheduled functions (run cyclically)
|
|
|
|
timer.scheduleFunction(ctld.refreshSmoke, nil, timer.getTime() + 5)
|
|
timer.scheduleFunction(ctld.addF10MenuOptions, nil, timer.getTime() + 5)
|
|
timer.scheduleFunction(ctld.checkAIStatus,nil,timer.getTime() + 5)
|
|
timer.scheduleFunction(ctld.checkTransportStatus,nil,timer.getTime()+5)
|
|
|
|
|
|
--event handler for deaths
|
|
--world.addEventHandler(ctld.eventHandler)
|
|
|
|
--env.info("CTLD event handler added")
|
|
|
|
env.info("Generating Laser Codes")
|
|
ctld.generateLaserCode()
|
|
env.info("Generated Laser Codes")
|
|
|
|
env.info("CTLD READY")
|
|
--DEBUG FUNCTION
|
|
-- for key, value in pairs(getmetatable(_spawnedCrate)) do
|
|
-- env.info(tostring(key))
|
|
-- env.info(tostring(value))
|
|
-- end |