Release 2024-01

Many changes:
- Added framework/menu for JTAC special options (toggle on/off or requests)
- Added 9 Line, Smoke on TGT. Toggle lasing, Toggle speed/wind corrections special options
- Reworked the JTAC pagination system to fully utilize F1-F9 entries
- Reworked refresh for JTAC radio menus, any action triggers a refresh for a JTAC and otherwise it stays for 2min (Mitigation of the radio menu overlap when refreshing while the player is already in the radio menu)
- Bug fixes here and there regarding the criterias to display or not the target list of a JTAC and it's special options
- Updated documentation for the JTAC automatic lasing feature
- Added the ability to load dynamically the ctld.lua file in the test mission
- Bug fixes to the ctld.getGroup function
- Added a one-second delay when a JTAC is spawned, before starting auto-lase
This commit is contained in:
David Pierron
2024-01-04 16:47:27 +01:00
committed by GitHub
4 changed files with 383 additions and 198 deletions
+280 -135
View File
@@ -26,7 +26,7 @@ ctld = {} -- DONT REMOVE!
ctld.Id = "CTLD - " ctld.Id = "CTLD - "
--- Version. --- Version.
ctld.Version = "202310.01" ctld.Version = "202401.01"
-- To add debugging messages to dcs.log, change the following log levels to `true`; `Debug` is less detailed than `Trace` -- To add debugging messages to dcs.log, change the following log levels to `true`; `Debug` is less detailed than `Trace`
ctld.Debug = false ctld.Debug = false
@@ -153,7 +153,10 @@ ctld.location_DMS = false -- shows coordinates as Degrees Minutes Seconds instea
ctld.JTAC_lock = "all" -- "vehicle" OR "troop" OR "all" forces JTAC to only lock vehicles or troops or all ground units ctld.JTAC_lock = "all" -- "vehicle" OR "troop" OR "all" forces JTAC to only lock vehicles or troops or all ground units
ctld.JTAC_laseSpotCorrections = false -- if true, the JTAC will attempt to lead the target, taking into account current wind conditions and the speed of the target (particularily useful against moving heavy armor) ctld.JTAC_allowStandbyMode = true -- if true, allow players to toggle lasing on/off
ctld.JTAC_laseSpotCorrections = true -- if true, each JTAC will have a special option (toggle on/off) available in it's menu to attempt to lead the target, taking into account current wind conditions and the speed of the target (particularily useful against moving heavy armor)
ctld.JTAC_allowSmokeRequest = true -- if true, allow players to request a smoke on target (temporary)
ctld.JTAC_allow9Line = true -- if true, allow players to ask for a 9Line (individual) for a specific JTAC's target
-- ***************** Pickup, dropoff and waypoint zones ***************** -- ***************** Pickup, dropoff and waypoint zones *****************
@@ -1034,7 +1037,6 @@ end
-- EG: ctld.activatePickupZone("pickzone3") -- EG: ctld.activatePickupZone("pickzone3")
-- This is enable pickzone3 to be used as a pickup zone for the team set -- This is enable pickzone3 to be used as a pickup zone for the team set
function ctld.activatePickupZone(_zoneName) function ctld.activatePickupZone(_zoneName)
ctld.logDebug(string.format("ctld.activatePickupZone(_zoneName=%s)", ctld.p(_zoneName)))
local _triggerZone = trigger.misc.getZone(_zoneName) -- trigger to use as reference position local _triggerZone = trigger.misc.getZone(_zoneName) -- trigger to use as reference position
@@ -1582,7 +1584,6 @@ function ctld.spawnCrateStatic(_country, _unitId, _point, _name, _weight,_side)
_crate["heading"] = 0 _crate["heading"] = 0
_crate["country"] = _country _crate["country"] = _country
ctld.logTrace(string.format("_crate=%s", ctld.p(_crate)))
mist.dynAddStatic(_crate) mist.dynAddStatic(_crate)
_spawnedCrate = StaticObject.getByName(_crate["name"]) _spawnedCrate = StaticObject.getByName(_crate["name"])
@@ -1863,13 +1864,10 @@ function ctld.deployTroops(_heli, _troops)
if _extractZone == false then if _extractZone == false then
local _droppedTroops = ctld.spawnDroppedGroup(_heli:getPoint(), _onboard.troops, false) local _droppedTroops = ctld.spawnDroppedGroup(_heli:getPoint(), _onboard.troops, false)
ctld.logTrace(string.format("_onboard.troops=%s", ctld.p(_onboard.troops)))
if _onboard.troops.jtac or _droppedTroops:getName():lower():find("jtac") then if _onboard.troops.jtac or _droppedTroops:getName():lower():find("jtac") then
local _code = table.remove(ctld.jtacGeneratedLaserCodes, 1) local _code = table.remove(ctld.jtacGeneratedLaserCodes, 1)
ctld.logTrace(string.format("_code=%s", ctld.p(_code)))
table.insert(ctld.jtacGeneratedLaserCodes, _code) table.insert(ctld.jtacGeneratedLaserCodes, _code)
ctld.logTrace(string.format("_droppedTroops:getName()=%s", ctld.p(_droppedTroops:getName()))) ctld.JTACStart(_droppedTroops:getName(), _code)
ctld.JTACAutoLase(_droppedTroops:getName(), _code)
end end
if _heli:getCoalition() == 1 then if _heli:getCoalition() == 1 then
@@ -1960,7 +1958,6 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
local _weight = 0 local _weight = 0
for i = 1, count do for i = 1, count do
local _soldierWeight = math.random(90, 120) * ctld.SOLDIER_WEIGHT / 100 local _soldierWeight = math.random(90, 120) * ctld.SOLDIER_WEIGHT / 100
ctld.logTrace(string.format("_soldierWeight=%s", ctld.p(_soldierWeight)))
_weight = _weight + _soldierWeight + ctld.KIT_WEIGHT + additionalWeight _weight = _weight + _soldierWeight + ctld.KIT_WEIGHT + additionalWeight
end end
return _weight return _weight
@@ -1969,54 +1966,43 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
if type(_countOrTemplate) == "table" then if type(_countOrTemplate) == "table" then
if _countOrTemplate.aa then if _countOrTemplate.aa then
ctld.logTrace(string.format("_countOrTemplate.aa=%s", ctld.p(_countOrTemplate.aa)))
if _side == 2 then if _side == 2 then
_troops = ctld.insertIntoTroopsArray("Soldier stinger",_countOrTemplate.aa,_troops) _troops = ctld.insertIntoTroopsArray("Soldier stinger",_countOrTemplate.aa,_troops)
else else
_troops = ctld.insertIntoTroopsArray("SA-18 Igla manpad",_countOrTemplate.aa,_troops) _troops = ctld.insertIntoTroopsArray("SA-18 Igla manpad",_countOrTemplate.aa,_troops)
end end
_weight = _weight + getSoldiersWeight(_countOrTemplate.aa, ctld.MANPAD_WEIGHT) _weight = _weight + getSoldiersWeight(_countOrTemplate.aa, ctld.MANPAD_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end end
if _countOrTemplate.inf then if _countOrTemplate.inf then
ctld.logTrace(string.format("_countOrTemplate.inf=%s", ctld.p(_countOrTemplate.inf)))
if _side == 2 then if _side == 2 then
_troops = ctld.insertIntoTroopsArray("Soldier M4",_countOrTemplate.inf,_troops) _troops = ctld.insertIntoTroopsArray("Soldier M4",_countOrTemplate.inf,_troops)
else else
_troops = ctld.insertIntoTroopsArray("Soldier AK",_countOrTemplate.inf,_troops) _troops = ctld.insertIntoTroopsArray("Soldier AK",_countOrTemplate.inf,_troops)
end end
_weight = _weight + getSoldiersWeight(_countOrTemplate.inf, ctld.RIFLE_WEIGHT) _weight = _weight + getSoldiersWeight(_countOrTemplate.inf, ctld.RIFLE_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end end
if _countOrTemplate.mg then if _countOrTemplate.mg then
ctld.logTrace(string.format("_countOrTemplate.mg=%s", ctld.p(_countOrTemplate.mg)))
if _side == 2 then if _side == 2 then
_troops = ctld.insertIntoTroopsArray("Soldier M249",_countOrTemplate.mg,_troops) _troops = ctld.insertIntoTroopsArray("Soldier M249",_countOrTemplate.mg,_troops)
else else
_troops = ctld.insertIntoTroopsArray("Paratrooper AKS-74",_countOrTemplate.mg,_troops) _troops = ctld.insertIntoTroopsArray("Paratrooper AKS-74",_countOrTemplate.mg,_troops)
end end
_weight = _weight + getSoldiersWeight(_countOrTemplate.mg, ctld.MG_WEIGHT) _weight = _weight + getSoldiersWeight(_countOrTemplate.mg, ctld.MG_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end end
if _countOrTemplate.at then if _countOrTemplate.at then
ctld.logTrace(string.format("_countOrTemplate.at=%s", ctld.p(_countOrTemplate.at)))
_troops = ctld.insertIntoTroopsArray("Paratrooper RPG-16",_countOrTemplate.at,_troops) _troops = ctld.insertIntoTroopsArray("Paratrooper RPG-16",_countOrTemplate.at,_troops)
_weight = _weight + getSoldiersWeight(_countOrTemplate.at, ctld.RPG_WEIGHT) _weight = _weight + getSoldiersWeight(_countOrTemplate.at, ctld.RPG_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end end
if _countOrTemplate.mortar then if _countOrTemplate.mortar then
ctld.logTrace(string.format("_countOrTemplate.mortar=%s", ctld.p(_countOrTemplate.mortar)))
_troops = ctld.insertIntoTroopsArray("2B11 mortar",_countOrTemplate.mortar,_troops) _troops = ctld.insertIntoTroopsArray("2B11 mortar",_countOrTemplate.mortar,_troops)
_weight = _weight + getSoldiersWeight(_countOrTemplate.mortar, ctld.MORTAR_WEIGHT) _weight = _weight + getSoldiersWeight(_countOrTemplate.mortar, ctld.MORTAR_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end end
if _countOrTemplate.jtac then if _countOrTemplate.jtac then
ctld.logTrace(string.format("_countOrTemplate.jtac=%s", ctld.p(_countOrTemplate.jtac)))
if _side == 2 then if _side == 2 then
_troops = ctld.insertIntoTroopsArray("Soldier M4",_countOrTemplate.jtac,_troops, "JTAC") _troops = ctld.insertIntoTroopsArray("Soldier M4",_countOrTemplate.jtac,_troops, "JTAC")
else else
@@ -2024,7 +2010,6 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
end end
_hasJTAC = true _hasJTAC = true
_weight = _weight + getSoldiersWeight(_countOrTemplate.jtac, ctld.JTAC_WEIGHT + ctld.RIFLE_WEIGHT) _weight = _weight + getSoldiersWeight(_countOrTemplate.jtac, ctld.JTAC_WEIGHT + ctld.RIFLE_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end end
else else
@@ -2036,37 +2021,29 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
if _i <=2 then if _i <=2 then
_unitType = "Soldier M249" _unitType = "Soldier M249"
_weight = _weight + getSoldiersWeight(1, ctld.MG_WEIGHT) _weight = _weight + getSoldiersWeight(1, ctld.MG_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
elseif ctld.spawnRPGWithCoalition and _i > 2 and _i <= 4 then elseif ctld.spawnRPGWithCoalition and _i > 2 and _i <= 4 then
_unitType = "Paratrooper RPG-16" _unitType = "Paratrooper RPG-16"
_weight = _weight + getSoldiersWeight(1, ctld.RPG_WEIGHT) _weight = _weight + getSoldiersWeight(1, ctld.RPG_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
elseif ctld.spawnStinger and _i > 4 and _i <= 5 then elseif ctld.spawnStinger and _i > 4 and _i <= 5 then
_unitType = "Soldier stinger" _unitType = "Soldier stinger"
_weight = _weight + getSoldiersWeight(1, ctld.MANPAD_WEIGHT) _weight = _weight + getSoldiersWeight(1, ctld.MANPAD_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
else else
_unitType = "Soldier M4" _unitType = "Soldier M4"
_weight = _weight + getSoldiersWeight(1, ctld.RIFLE_WEIGHT) _weight = _weight + getSoldiersWeight(1, ctld.RIFLE_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
end end
else else
if _i <=2 then if _i <=2 then
_unitType = "Paratrooper AKS-74" _unitType = "Paratrooper AKS-74"
_weight = _weight + getSoldiersWeight(1, ctld.MG_WEIGHT) _weight = _weight + getSoldiersWeight(1, ctld.MG_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
elseif ctld.spawnRPGWithCoalition and _i > 2 and _i <= 4 then elseif ctld.spawnRPGWithCoalition and _i > 2 and _i <= 4 then
_unitType = "Paratrooper RPG-16" _unitType = "Paratrooper RPG-16"
_weight = _weight + getSoldiersWeight(1, ctld.RPG_WEIGHT) _weight = _weight + getSoldiersWeight(1, ctld.RPG_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
elseif ctld.spawnStinger and _i > 4 and _i <= 5 then elseif ctld.spawnStinger and _i > 4 and _i <= 5 then
_unitType = "SA-18 Igla manpad" _unitType = "SA-18 Igla manpad"
_weight = _weight + getSoldiersWeight(1, ctld.MANPAD_WEIGHT) _weight = _weight + getSoldiersWeight(1, ctld.MANPAD_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
else else
_unitType = "Infantry AK" _unitType = "Infantry AK"
_weight = _weight + getSoldiersWeight(1, ctld.RIFLE_WEIGHT) _weight = _weight + getSoldiersWeight(1, ctld.RIFLE_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
end end
end end
@@ -2082,7 +2059,6 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
_groupName = "Dropped JTAC Group" _groupName = "Dropped JTAC Group"
end end
local _details = { units = _troops, groupId = _groupId, groupName = string.format("%s %i", _groupName, _groupId), side = _side, country = _country, weight = _weight, jtac = _hasJTAC } local _details = { units = _troops, groupId = _groupId, groupName = string.format("%s %i", _groupName, _groupId), side = _side, country = _country, weight = _weight, jtac = _hasJTAC }
ctld.logTrace(string.format("total weight=%s", ctld.p(_weight)))
return _details return _details
end end
@@ -2141,10 +2117,8 @@ function ctld.loadTroops(_heli, _troops, _numberOrTemplate)
_list = ctld.vehiclesForTransportBLUE _list = ctld.vehiclesForTransportBLUE
end end
ctld.logTrace(string.format("_troops=%s", ctld.p(_troops)))
if _troops then if _troops then
_onboard.troops = ctld.generateTroopTypes(_heli:getCoalition(), _numberOrTemplate, _heli:getCountry()) _onboard.troops = ctld.generateTroopTypes(_heli:getCoalition(), _numberOrTemplate, _heli:getCountry())
ctld.logTrace(string.format("_onboard.troops=%s", ctld.p(_onboard.troops)))
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " loaded troops into " .. _heli:getTypeName(), 10) trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.getPlayerNameOrType(_heli) .. " loaded troops into " .. _heli:getTypeName(), 10)
ctld.processCallback({unit = _heli, onboard = _onboard.troops, action = "load_troops"}) ctld.processCallback({unit = _heli, onboard = _onboard.troops, action = "load_troops"})
@@ -2160,7 +2134,6 @@ function ctld.loadTroops(_heli, _troops, _numberOrTemplate)
end end
ctld.inTransitTroops[_heli:getName()] = _onboard ctld.inTransitTroops[_heli:getName()] = _onboard
ctld.logTrace(string.format("ctld.inTransitTroops=%s", ctld.p(ctld.inTransitTroops[_heli:getName()])))
ctld.adaptWeightToCargo(_heli:getName()) ctld.adaptWeightToCargo(_heli:getName())
end end
@@ -2540,7 +2513,6 @@ function ctld.checkTroopStatus(_args)
end end
local _, _message = ctld.getWeightOfCargo(_unitName) local _, _message = ctld.getWeightOfCargo(_unitName)
ctld.logTrace(string.format("_message=%s", ctld.p(_message)))
if _message and _message ~= "" then if _message and _message ~= "" then
ctld.displayMessageToGroup(_heli, _message, 10) ctld.displayMessageToGroup(_heli, _message, 10)
end end
@@ -2570,7 +2542,6 @@ function ctld.adaptWeightToCargo(unitName)
end end
function ctld.getWeightOfCargo(unitName) function ctld.getWeightOfCargo(unitName)
ctld.logDebug(string.format("ctld.getWeightOfCargo(%s)", ctld.p(unitName)))
local FOB_CRATE_WEIGHT = 800 local FOB_CRATE_WEIGHT = 800
local _weight = 0 local _weight = 0
@@ -2578,13 +2549,10 @@ function ctld.getWeightOfCargo(unitName)
-- add troops weight -- add troops weight
if ctld.inTransitTroops[unitName] then if ctld.inTransitTroops[unitName] then
ctld.logTrace("ctld.inTransitTroops = true")
local _inTransit = ctld.inTransitTroops[unitName] local _inTransit = ctld.inTransitTroops[unitName]
if _inTransit then if _inTransit then
ctld.logTrace(string.format("_inTransit=%s", ctld.p(_inTransit)))
local _troops = _inTransit.troops local _troops = _inTransit.troops
if _troops and _troops.units then if _troops and _troops.units then
ctld.logTrace(string.format("_troops.weight=%s", ctld.p(_troops.weight)))
_description = _description .. string.format("%s troops onboard (%s kg)\n", #_troops.units, _troops.weight) _description = _description .. string.format("%s troops onboard (%s kg)\n", #_troops.units, _troops.weight)
_weight = _weight + _troops.weight _weight = _weight + _troops.weight
end end
@@ -2593,44 +2561,35 @@ function ctld.getWeightOfCargo(unitName)
for _, _unit in pairs(_vehicles.units) do for _, _unit in pairs(_vehicles.units) do
_weight = _weight + _unit.weight _weight = _weight + _unit.weight
end end
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
_description = _description .. string.format("%s vehicles onboard (%s kg)\n", #_vehicles.units, _weight) _description = _description .. string.format("%s vehicles onboard (%s kg)\n", #_vehicles.units, _weight)
end end
end end
end end
ctld.logTrace(string.format("with troops and vehicles : weight = %s", tostring(_weight)))
-- add FOB crates weight -- add FOB crates weight
if ctld.inTransitFOBCrates[unitName] then if ctld.inTransitFOBCrates[unitName] then
ctld.logTrace("ctld.inTransitFOBCrates = true")
_weight = _weight + FOB_CRATE_WEIGHT _weight = _weight + FOB_CRATE_WEIGHT
_description = _description .. string.format("1 FOB Crate oboard (%s kg)\n", FOB_CRATE_WEIGHT) _description = _description .. string.format("1 FOB Crate oboard (%s kg)\n", FOB_CRATE_WEIGHT)
end end
ctld.logTrace(string.format("with FOB crates : weight = %s", tostring(_weight)))
-- add simulated slingload crates weight -- add simulated slingload crates weight
local _crate = ctld.inTransitSlingLoadCrates[unitName] local _crate = ctld.inTransitSlingLoadCrates[unitName]
if _crate then if _crate then
ctld.logTrace(string.format("_crate=%s", ctld.p(_crate)))
if _crate.simulatedSlingload then if _crate.simulatedSlingload then
ctld.logTrace(string.format("_crate.weight=%s", ctld.p(_crate.weight)))
_weight = _weight + _crate.weight _weight = _weight + _crate.weight
_description = _description .. string.format("1 %s crate onboard (%s kg)\n", _crate.desc, _crate.weight) _description = _description .. string.format("1 %s crate onboard (%s kg)\n", _crate.desc, _crate.weight)
end end
end end
ctld.logTrace(string.format("with simulated slingload crates : weight = %s", tostring(_weight)))
if _description ~= "" then if _description ~= "" then
_description = _description .. string.format("Total weight of cargo : %s kg\n", _weight) _description = _description .. string.format("Total weight of cargo : %s kg\n", _weight)
else else
_description = "No cargo." _description = "No cargo."
end end
ctld.logTrace(string.format("_description = %s", tostring(_description)))
return _weight, _description return _weight, _description
end end
function ctld.checkHoverStatus() function ctld.checkHoverStatus()
--ctld.logDebug(string.format("ctld.checkHoverStatus()"))
timer.scheduleFunction(ctld.checkHoverStatus, nil, timer.getTime() + 1.0) timer.scheduleFunction(ctld.checkHoverStatus, nil, timer.getTime() + 1.0)
local _status, _result = pcall(function() local _status, _result = pcall(function()
@@ -2643,13 +2602,10 @@ function ctld.checkHoverStatus()
--only check transports that are hovering and not planes --only check transports that are hovering and not planes
if _transUnit ~= nil and ctld.inTransitSlingLoadCrates[_name] == nil and ctld.inAir(_transUnit) and ctld.unitCanCarryVehicles(_transUnit) == false then if _transUnit ~= nil and ctld.inTransitSlingLoadCrates[_name] == nil and ctld.inAir(_transUnit) and ctld.unitCanCarryVehicles(_transUnit) == false then
--ctld.logTrace(string.format("%s - capable of slingloading", ctld.p(_name)))
local _crates = ctld.getCratesAndDistance(_transUnit) local _crates = ctld.getCratesAndDistance(_transUnit)
--ctld.logTrace(string.format("_crates = %s", ctld.p(_crates)))
for _, _crate in pairs(_crates) do for _, _crate in pairs(_crates) do
--ctld.logTrace(string.format("_crate = %s", ctld.p(_crate)))
if _crate.dist < ctld.maxDistanceFromCrate and _crate.details.unit ~= "FOB" then if _crate.dist < ctld.maxDistanceFromCrate and _crate.details.unit ~= "FOB" then
--check height! --check height!
@@ -2657,12 +2613,10 @@ function ctld.checkHoverStatus()
--env.info("HEIGHT " .. _name .. " " .. _height .. " " .. _transUnit:getPoint().y .. " " .. _crate.crateUnit:getPoint().y) --env.info("HEIGHT " .. _name .. " " .. _height .. " " .. _transUnit:getPoint().y .. " " .. _crate.crateUnit:getPoint().y)
-- ctld.heightDiff(_transUnit) -- ctld.heightDiff(_transUnit)
--env.info("HEIGHT ABOVE GROUD ".._name.." ".._height.." ".._transUnit:getPoint().y.." ".._crate.crateUnit:getPoint().y) --env.info("HEIGHT ABOVE GROUD ".._name.." ".._height.." ".._transUnit:getPoint().y.." ".._crate.crateUnit:getPoint().y)
--ctld.logTrace(string.format("_height = %s", ctld.p(_height)))
if _height > ctld.minimumHoverHeight and _height <= ctld.maximumHoverHeight then if _height > ctld.minimumHoverHeight and _height <= ctld.maximumHoverHeight then
local _time = ctld.hoverStatus[_transUnit:getName()] local _time = ctld.hoverStatus[_transUnit:getName()]
--ctld.logTrace(string.format("_time = %s", ctld.p(_time)))
if _time == nil then if _time == nil then
ctld.hoverStatus[_transUnit:getName()] = ctld.hoverTime ctld.hoverStatus[_transUnit:getName()] = ctld.hoverTime
@@ -2691,7 +2645,6 @@ function ctld.checkHoverStatus()
local _copiedCrate = mist.utils.deepCopy(_crate.details) local _copiedCrate = mist.utils.deepCopy(_crate.details)
_copiedCrate.simulatedSlingload = true _copiedCrate.simulatedSlingload = true
--ctld.logTrace(string.format("_copiedCrate = %s", ctld.p(_copiedCrate)))
ctld.inTransitSlingLoadCrates[_name] = _copiedCrate ctld.inTransitSlingLoadCrates[_name] = _copiedCrate
ctld.adaptWeightToCargo(_name) ctld.adaptWeightToCargo(_name)
end end
@@ -2771,7 +2724,6 @@ end
--check each minute if the beacons' batteries have failed, and stop them accordingly --check each minute if the beacons' batteries have failed, and stop them accordingly
--there's no more need to actually refresh the beacons, since we set "loop" to true. --there's no more need to actually refresh the beacons, since we set "loop" to true.
function ctld.refreshRadioBeacons() function ctld.refreshRadioBeacons()
ctld.logDebug("ctld.refreshRadioBeacons()")
timer.scheduleFunction(ctld.refreshRadioBeacons, nil, timer.getTime() + 60) timer.scheduleFunction(ctld.refreshRadioBeacons, nil, timer.getTime() + 60)
@@ -3214,7 +3166,7 @@ function ctld.unpackCrates(_arguments)
--put to the end --put to the end
table.insert(ctld.jtacGeneratedLaserCodes, _code) table.insert(ctld.jtacGeneratedLaserCodes, _code)
ctld.JTACAutoLase(_spawnedGroups:getName(), _code) --(_jtacGroupName, _laserCode, _smoke, _lock, _colour) ctld.JTACStart(_spawnedGroups:getName(), _code) --(_jtacGroupName, _laserCode, _smoke, _lock, _colour)
end end
end end
@@ -3396,7 +3348,6 @@ end
-- one for VHF and one for UHF -- one for VHF and one for UHF
-- The units are set to to NOT engage -- The units are set to to NOT engage
function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTime, _isFOB) function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTime, _isFOB)
ctld.logDebug(string.format("ctld.createRadioBeacon(_name=%s)", ctld.p(_name)))
local _freq = ctld.generateADFFrequencies() local _freq = ctld.generateADFFrequencies()
@@ -3424,9 +3375,6 @@ function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTim
_freqsText = _freqsText .. " - " .. _latLngStr _freqsText = _freqsText .. " - " .. _latLngStr
ctld.logTrace(string.format("GEN UHF: %s", ctld.p(_freq.uhf)))
ctld.logTrace(string.format("GEN HF: %s", ctld.p(_freq.vhf)))
ctld.logTrace(string.format("GEN FM: %s", ctld.p(_freq.fm)))
_freqsText = string.format("%.2f kHz - %.2f / %.2f MHz", _freq.vhf / 1000, _freq.uhf / 1000000, _freq.fm / 1000000) _freqsText = string.format("%.2f kHz - %.2f / %.2f MHz", _freq.vhf / 1000, _freq.uhf / 1000000, _freq.fm / 1000000)
@@ -3446,7 +3394,6 @@ function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTim
coalition = _coalition, coalition = _coalition,
} }
ctld.logDebug(string.format("calling ctld.updateRadioBeacon for beacon %s", ctld.p(_name)))
ctld.updateRadioBeacon(_beaconDetails) ctld.updateRadioBeacon(_beaconDetails)
table.insert(ctld.deployedRadioBeacons, _beaconDetails) table.insert(ctld.deployedRadioBeacons, _beaconDetails)
@@ -3489,7 +3436,6 @@ end
function ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText) function ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
ctld.logDebug(string.format("ctld.spawnRadioBeaconUnit(_name=%s)", ctld.p(_name)))
local _groupId = ctld.getNextGroupId() local _groupId = ctld.getNextGroupId()
@@ -3525,8 +3471,6 @@ function ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
end end
function ctld.updateRadioBeacon(_beaconDetails) function ctld.updateRadioBeacon(_beaconDetails)
ctld.logDebug("ctld.updateRadioBeacon()")
ctld.logTrace(string.format("_beaconDetails=%s", ctld.p(_beaconDetails)))
local _vhfGroup = Group.getByName(_beaconDetails.vhfGroup) local _vhfGroup = Group.getByName(_beaconDetails.vhfGroup)
@@ -3537,17 +3481,14 @@ function ctld.updateRadioBeacon(_beaconDetails)
local _radioLoop = {} local _radioLoop = {}
if _vhfGroup ~= nil and _vhfGroup:getUnits() ~= nil and #_vhfGroup:getUnits() == 1 then if _vhfGroup ~= nil and _vhfGroup:getUnits() ~= nil and #_vhfGroup:getUnits() == 1 then
ctld.logTrace(string.format("_vhfGroup=%s", ctld.p(_vhfGroup)))
table.insert(_radioLoop, { group = _vhfGroup, freq = _beaconDetails.vhf, silent = false, mode = 0 }) table.insert(_radioLoop, { group = _vhfGroup, freq = _beaconDetails.vhf, silent = false, mode = 0 })
end end
if _uhfGroup ~= nil and _uhfGroup:getUnits() ~= nil and #_uhfGroup:getUnits() == 1 then if _uhfGroup ~= nil and _uhfGroup:getUnits() ~= nil and #_uhfGroup:getUnits() == 1 then
ctld.logTrace(string.format("_uhfGroup=%s", ctld.p(_uhfGroup)))
table.insert(_radioLoop, { group = _uhfGroup, freq = _beaconDetails.uhf, silent = true, mode = 0 }) table.insert(_radioLoop, { group = _uhfGroup, freq = _beaconDetails.uhf, silent = true, mode = 0 })
end end
if _fmGroup ~= nil and _fmGroup:getUnits() ~= nil and #_fmGroup:getUnits() == 1 then if _fmGroup ~= nil and _fmGroup:getUnits() ~= nil and #_fmGroup:getUnits() == 1 then
ctld.logTrace(string.format("_fmGroup=%s", ctld.p(_fmGroup)))
table.insert(_radioLoop, { group = _fmGroup, freq = _beaconDetails.fm, silent = false, mode = 1 }) table.insert(_radioLoop, { group = _fmGroup, freq = _beaconDetails.fm, silent = false, mode = 1 })
end end
@@ -3555,19 +3496,15 @@ function ctld.updateRadioBeacon(_beaconDetails)
if (_batLife <= 0 and _beaconDetails.battery ~= -1) or #_radioLoop ~= 3 then if (_batLife <= 0 and _beaconDetails.battery ~= -1) or #_radioLoop ~= 3 then
-- ran out of batteries -- ran out of batteries
ctld.logDebug("ran out of batteries")
if _vhfGroup ~= nil then if _vhfGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_vhfGroup:getName())))
trigger.action.stopRadioTransmission(_vhfGroup:getName()) trigger.action.stopRadioTransmission(_vhfGroup:getName())
_vhfGroup:destroy() _vhfGroup:destroy()
end end
if _uhfGroup ~= nil then if _uhfGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_uhfGroup:getName())))
trigger.action.stopRadioTransmission(_uhfGroup:getName()) trigger.action.stopRadioTransmission(_uhfGroup:getName())
_uhfGroup:destroy() _uhfGroup:destroy()
end end
if _fmGroup ~= nil then if _fmGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_fmGroup:getName())))
trigger.action.stopRadioTransmission(_fmGroup:getName()) trigger.action.stopRadioTransmission(_fmGroup:getName())
_fmGroup:destroy() _fmGroup:destroy()
end end
@@ -3593,7 +3530,6 @@ function ctld.updateRadioBeacon(_beaconDetails)
_groupController:setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD) _groupController:setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
ctld.logTrace(string.format("stopping and restarting transmission of %s", ctld.p(_radio.group:getName())))
-- stop the transmission at each call to the ctld.updateRadioBeacon method (default each minute) -- stop the transmission at each call to the ctld.updateRadioBeacon method (default each minute)
trigger.action.stopRadioTransmission(_radio.group:getName()) trigger.action.stopRadioTransmission(_radio.group:getName())
@@ -3692,17 +3628,14 @@ function ctld.removeRadioBeacon(_args)
local _fmGroup = Group.getByName(_closetBeacon.fmGroup) local _fmGroup = Group.getByName(_closetBeacon.fmGroup)
if _vhfGroup ~= nil then if _vhfGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_vhfGroup:getName())))
trigger.action.stopRadioTransmission(_vhfGroup:getName()) trigger.action.stopRadioTransmission(_vhfGroup:getName())
_vhfGroup:destroy() _vhfGroup:destroy()
end end
if _uhfGroup ~= nil then if _uhfGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_uhfGroup:getName())))
trigger.action.stopRadioTransmission(_uhfGroup:getName()) trigger.action.stopRadioTransmission(_uhfGroup:getName())
_uhfGroup:destroy() _uhfGroup:destroy()
end end
if _fmGroup ~= nil then if _fmGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_fmGroup:getName())))
trigger.action.stopRadioTransmission(_fmGroup:getName()) trigger.action.stopRadioTransmission(_fmGroup:getName())
_fmGroup:destroy() _fmGroup:destroy()
end end
@@ -4471,7 +4404,6 @@ end
-- are we in pickup zone -- are we in pickup zone
function ctld.inPickupZone(_heli) function ctld.inPickupZone(_heli)
ctld.logDebug(string.format("ctld.inPickupZone(_heli=%s)", ctld.p(_heli)))
if ctld.inAir(_heli) then if ctld.inAir(_heli) then
return { inZone = false, limit = -1, index = -1 } return { inZone = false, limit = -1, index = -1 }
@@ -4480,7 +4412,6 @@ function ctld.inPickupZone(_heli)
local _heliPoint = _heli:getPoint() local _heliPoint = _heli:getPoint()
for _i, _zoneDetails in pairs(ctld.pickupZones) do for _i, _zoneDetails in pairs(ctld.pickupZones) do
ctld.logTrace(string.format("_zoneDetails=%s", ctld.p(_zoneDetails)))
local _triggerZone = trigger.misc.getZone(_zoneDetails[1]) local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
@@ -4501,7 +4432,6 @@ function ctld.inPickupZone(_heli)
--get distance to center --get distance to center
local _dist = ctld.getDistance(_heliPoint, _triggerZone.point) local _dist = ctld.getDistance(_heliPoint, _triggerZone.point)
ctld.logTrace(string.format("_dist=%s", ctld.p(_dist)))
if _dist <= _triggerZone.radius then if _dist <= _triggerZone.radius then
local _heliCoalition = _heli:getCoalition() local _heliCoalition = _heli:getCoalition()
if _zoneDetails[4] == 1 and (_zoneDetails[5] == _heliCoalition or _zoneDetails[5] == 0) then if _zoneDetails[4] == 1 and (_zoneDetails[5] == _heliCoalition or _zoneDetails[5] == 0) then
@@ -4917,7 +4847,6 @@ function ctld.addF10MenuOptions()
local _rootPath = missionCommands.addSubMenuForGroup(_groupId, "CTLD") local _rootPath = missionCommands.addSubMenuForGroup(_groupId, "CTLD")
local _unitActions = ctld.getUnitActions(_unit:getTypeName()) local _unitActions = ctld.getUnitActions(_unit:getTypeName())
ctld.logTrace(string.format("_unitActions=%s", ctld.p(_unitActions)))
missionCommands.addCommandForGroup(_groupId, "Check Cargo", _rootPath, ctld.checkTroopStatus, { _unitName }) missionCommands.addCommandForGroup(_groupId, "Check Cargo", _rootPath, ctld.checkTroopStatus, { _unitName })
@@ -4930,9 +4859,7 @@ function ctld.addF10MenuOptions()
-- local _loadPath = missionCommands.addSubMenuForGroup(_groupId, "Load From Zone", _troopCommandsPath) -- local _loadPath = missionCommands.addSubMenuForGroup(_groupId, "Load From Zone", _troopCommandsPath)
local _transportLimit = ctld.getTransportLimit(_unit:getTypeName()) local _transportLimit = ctld.getTransportLimit(_unit:getTypeName())
ctld.logTrace(string.format("_transportLimit=%s", ctld.p(_transportLimit)))
for _,_loadGroup in pairs(ctld.loadableGroups) do for _,_loadGroup in pairs(ctld.loadableGroups) do
ctld.logTrace(string.format("_loadGroup=%s", ctld.p(_loadGroup)))
if not _loadGroup.side or _loadGroup.side == _unit:getCoalition() then if not _loadGroup.side or _loadGroup.side == _unit:getCoalition() then
-- check size & unit -- check size & unit
@@ -5108,21 +5035,18 @@ function ctld.addJTACRadioCommand(_side)
if _groupId then if _groupId then
local newGroup = false local newGroup = false
-- env.info("adding command for "..index)
if ctld.jtacRadioAdded[tostring(_groupId)] == nil then if ctld.jtacRadioAdded[tostring(_groupId)] == nil then
-- env.info("about command for "..index)
newGroup = true newGroup = true
local JTACpath = missionCommands.addSubMenuForGroup(_groupId, ctld.jtacMenuName) local JTACpath = missionCommands.addSubMenuForGroup(_groupId, ctld.jtacMenuName)
missionCommands.addCommandForGroup(_groupId, "JTAC Status", JTACpath, ctld.getJTACStatus, { _playerUnit:getName() }) missionCommands.addCommandForGroup(_groupId, "JTAC Status", JTACpath, ctld.getJTACStatus, { _playerUnit:getName() })
ctld.jtacRadioAdded[tostring(_groupId)] = true ctld.jtacRadioAdded[tostring(_groupId)] = true
-- env.info("Added command for " .. index)
end end
--fetch the time to check for a regular refresh --fetch the time to check for a regular refresh
local time = timer.getTime() local time = timer.getTime()
--depending on the delay, this part of the radio menu will be refreshed less often or as often as the static JTAC status command, this is for better reliability for the user when navigating through the menus. New groups will get the lists regardless and if a new JTAC is added all lists will be refreshed regardless of the delay. --depending on the delay, this part of the radio menu will be refreshed less often or as often as the static JTAC status command, this is for better reliability for the user when navigating through the menus. New groups will get the lists regardless and if a new JTAC is added all lists will be refreshed regardless of the delay.
if ctld.jtacLastRadioRefresh + ctld.jtacRadioRefreshDelay <= time or ctld.newJtac[_side] or newGroup then if ctld.jtacLastRadioRefresh + ctld.jtacRadioRefreshDelay <= time or ctld.refreshJTACmenu[_side] or newGroup then
ctld.jtacLastRadioRefresh = time ctld.jtacLastRadioRefresh = time
@@ -5138,39 +5062,87 @@ function ctld.addJTACRadioCommand(_side)
local jtacCounter = 0 local jtacCounter = 0
for _jtacGroupName,jtacUnit in pairs(ctld.jtacUnits) do for _jtacGroupName,jtacUnit in pairs(ctld.jtacUnits) do
ctld.logTrace(string.format("JTAC - MENU - [%s] - processing menu", ctld.p(_jtacGroupName)))
local jtacCoalition = ctld.jtacUnits[_jtacGroupName].side
--if the JTAC is on the same team as the group being considered --if the JTAC is on the same team as the group being considered
local jtacCoalition = ctld.jtacUnits[_jtacGroupName].side
if jtacCoalition and jtacCoalition == _side then if jtacCoalition and jtacCoalition == _side then
--only bother removing the submenus on the first page of the CTLD JTAC menu as the other pages were deleted entirely above --only bother removing the submenus on the first page of the CTLD JTAC menu as the other pages were deleted entirely above
if ctld.jtacGroupSubMenuPath[_jtacGroupName] and #ctld.jtacGroupSubMenuPath[_jtacGroupName]==2 then if ctld.jtacGroupSubMenuPath[_jtacGroupName] and #ctld.jtacGroupSubMenuPath[_jtacGroupName]==2 then
missionCommands.removeItemForGroup(_groupId, ctld.jtacGroupSubMenuPath[_jtacGroupName]) missionCommands.removeItemForGroup(_groupId, ctld.jtacGroupSubMenuPath[_jtacGroupName])
end end
ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacTargetsList = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacTargetsList[_jtacGroupName])))
ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacCurrentTargets = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacCurrentTargets[_jtacGroupName])))
ctld.logTrace(string.format("jtacTargetsList for %s is : %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacTargetsList[_jtacGroupName]))) local jtacActionMenu = false
for _,_specialOptionTable in pairs(ctld.jtacSpecialOptions) do
if _specialOptionTable.globalToggle then
jtacActionMenu = true
break
end
end
if #ctld.jtacTargetsList[_jtacGroupName] > 1 then --if JTAC has at least one other target in sight or (if special options are available (NOTE : accessed through the JTAC's own menu also) and the JTAC has at least one target)
if (ctld.jtacTargetsList[_jtacGroupName] and #ctld.jtacTargetsList[_jtacGroupName] >= 1) or (ctld.jtacCurrentTargets[_jtacGroupName] and jtacActionMenu) then
local jtacGroupSubMenuName = string.format(_jtacGroupName .. " TGT Selection") local jtacGroupSubMenuName = string.format(_jtacGroupName .. " Selection")
jtacCounter = jtacCounter + 1 jtacCounter = jtacCounter + 1
--F2 through F10 makes 9 entries possible per page, with one being the NextMenu submenu --F2 through F10 makes 9 entries possible per page, with one being the NextMenu submenu. F1 is taken by JTAC status entry.
if jtacCounter%9 == 0 then if jtacCounter % 9 == 0 then
--recover the path to the current page with space available for JTAC group submenus --recover the path to the current page with space available for JTAC group submenus
jtacCurrentPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacCurrentPagePath) jtacCurrentPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacCurrentPagePath)
end end
--add the JTAC group submenu to the current page --add the JTAC group submenu to the current page
ctld.jtacGroupSubMenuPath[_jtacGroupName] = missionCommands.addSubMenuForGroup(_groupId, jtacGroupSubMenuName, jtacCurrentPagePath) ctld.jtacGroupSubMenuPath[_jtacGroupName] = missionCommands.addSubMenuForGroup(_groupId, jtacGroupSubMenuName, jtacCurrentPagePath)
ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacGroupSubMenuPath = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacGroupSubMenuPath[_jtacGroupName])))
ctld.logTrace(string.format("jtacGroupSubMenuPath for %s is : %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacGroupSubMenuPath[_jtacGroupName])))
--make a copy of the JTAC group submenu's path to insert the target's list on as many pages as required. The JTAC's group submenu path only leads to the first page --make a copy of the JTAC group submenu's path to insert the target's list on as many pages as required. The JTAC's group submenu path only leads to the first page
local jtacTargetPagePath = mist.utils.deepCopy(ctld.jtacGroupSubMenuPath[_jtacGroupName]) local jtacTargetPagePath = mist.utils.deepCopy(ctld.jtacGroupSubMenuPath[_jtacGroupName])
--counter to know when to add the next page submenu to fit all of the targets in the JTAC's group submenu. SMay not actually start at 0 due to static items being present on the first page
local itemCounter = 0
local jtacSpecialOptPagePath = nil
if jtacActionMenu then
--special options
local SpecialOptionsCounter = 0
for _,_specialOption in pairs(ctld.jtacSpecialOptions) do
if _specialOption.globalToggle then
if not jtacSpecialOptPagePath then
itemCounter = itemCounter + 1 --one item is added to the first JTAC target page
jtacSpecialOptPagePath = missionCommands.addSubMenuForGroup(_groupId, "Actions", jtacTargetPagePath)
end
SpecialOptionsCounter = SpecialOptionsCounter+1
if SpecialOptionsCounter%10 == 0 then
jtacSpecialOptPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacSpecialOptPagePath)
SpecialOptionsCounter = SpecialOptionsCounter+1 --Added Next Page item
end
if _specialOption.jtacs then
if _specialOption.jtacs[_jtacGroupName] then
missionCommands.addCommandForGroup(_groupId, "DISABLE " .. _specialOption.message, jtacSpecialOptPagePath, _specialOption.setter, {jtacGroupName = _jtacGroupName, value = false})
else
missionCommands.addCommandForGroup(_groupId, "ENABLE " .. _specialOption.message, jtacSpecialOptPagePath, _specialOption.setter, {jtacGroupName = _jtacGroupName, value = true})
end
else
missionCommands.addCommandForGroup(_groupId, "REQUEST " .. _specialOption.message, jtacSpecialOptPagePath, _specialOption.setter, {jtacGroupName = _jtacGroupName, value = false}) --value is not used here
end
end
end
end
if #ctld.jtacTargetsList[_jtacGroupName] >= 1 then
ctld.logTrace(string.format("JTAC - MENU - [%s] - adding targets menu", ctld.p(_jtacGroupName)))
--add a reset targeting option to revert to automatic JTAC unit targeting --add a reset targeting option to revert to automatic JTAC unit targeting
missionCommands.addCommandForGroup(_groupId, "Reset TGT Selection", jtacTargetPagePath, ctld.setJTACTarget, {jtacGroupName = _jtacGroupName, targetName = nil}) missionCommands.addCommandForGroup(_groupId, "Reset TGT Selection", jtacTargetPagePath, ctld.setJTACTarget, {jtacGroupName = _jtacGroupName, targetName = nil})
--counter to know when to add the next page submenu to fit all of the targets in the JTAC's group submenu itemCounter = itemCounter + 1 --one item is added to the first JTAC target page
local itemCounter = 0
--indicator table to know which unitType was already added to the radio submenu --indicator table to know which unitType was already added to the radio submenu
local typeNameList = {} local typeNameList = {}
@@ -5197,9 +5169,10 @@ function ctld.addJTACRadioCommand(_side)
local targetName = info.targetName local targetName = info.targetName
itemCounter = itemCounter + 1 itemCounter = itemCounter + 1
--F2 through F10 makes 9 entries possible per page, with one being the NextMenu submenu. Pages other than the first would have 10 entires but worse case scenario is considered --F1 through F10 makes 10 entries possible per page, with one being the NextMenu submenu.
if itemCounter%9 == 0 then if itemCounter%10 == 0 then
jtacTargetPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacTargetPagePath) jtacTargetPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacTargetPagePath)
itemCounter = itemCounter + 1 --added the next page item
end end
missionCommands.addCommandForGroup(_groupId, string.format(typeName .. "(" .. amount .. ")"), jtacTargetPagePath, ctld.setJTACTarget, {jtacGroupName = _jtacGroupName, targetName = targetName}) missionCommands.addCommandForGroup(_groupId, string.format(typeName .. "(" .. amount .. ")"), jtacTargetPagePath, ctld.setJTACTarget, {jtacGroupName = _jtacGroupName, targetName = targetName})
@@ -5210,11 +5183,11 @@ function ctld.addJTACRadioCommand(_side)
end end
end end
end end
if ctld.newJtac[_side] then
ctld.newJtac[_side] = false
end end
if ctld.refreshJTACmenu[_side] then
ctld.refreshJTACmenu[_side] = false
end
end end
end end
@@ -5254,15 +5227,55 @@ ctld.jtacStop = {} -- jtacs to tell to stop lasing
ctld.jtacCurrentTargets = {} ctld.jtacCurrentTargets = {}
ctld.jtacTargetsList = {} --current available targets to each JTAC for lasing (targets from other JTACs are filtered out). Contains DCS unit objects with their methods and the distance to the JTAC {unit, dist} ctld.jtacTargetsList = {} --current available targets to each JTAC for lasing (targets from other JTACs are filtered out). Contains DCS unit objects with their methods and the distance to the JTAC {unit, dist}
ctld.jtacSelectedTarget = {} --currently user selected target if it contains a unit's name, otherwise contains 1 or nil (if not initialized) ctld.jtacSelectedTarget = {} --currently user selected target if it contains a unit's name, otherwise contains 1 or nil (if not initialized)
ctld.jtacSpecialOptions = { --list which contains the status of special options for each jtac, ordered for them to show up in the correct order in the corresponding radio menu
standbyMode = { --#1
globalToggle = ctld.JTAC_allowStandbyMode;
message = "Standby Mode";
setter = nil; --ctld.setStdbMode, will be set after declaration of said function
jtacs = {
--enable flag for each JTAC
};
}; --disable designation by the JTAC
smokeMarker = { --#4
globalToggle = ctld.JTAC_allowSmokeRequest;
message = "Smoke on TGT";
setter = nil; --ctld.setSmokeOnTarget
}; --smoke marker on target
laseSpotCorrections = { --#2
globalToggle = ctld.JTAC_laseSpotCorrections;
message = "Speed Corrections";
setter = nil; --ctld.setLaseCompensation
jtacs = {
--enable flag for each JTAC
};
}; --target speed and wind compensation for laser spot
_9Line = { --#3
globalToggle = ctld.JTAC_allow9Line;
message = "9 Line";
setter = nil; --ctld.setJTAC9Line
}; --9Line message for JTAC
}
ctld.jtacRadioAdded = {} --keeps track of who's had the radio command added ctld.jtacRadioAdded = {} --keeps track of who's had the radio command added
ctld.jtacGroupSubMenuPath = {} --keeps track of which submenu contains each JTAC's target selection menu ctld.jtacGroupSubMenuPath = {} --keeps track of which submenu contains each JTAC's target selection menu
ctld.jtacRadioRefreshDelay = 60 --determines how often in seconds the dynamic parts of the jtac radio menu (target lists) will be refreshed ctld.jtacRadioRefreshDelay = 120 --determines how often in seconds the dynamic parts of the jtac radio menu (target lists) will be refreshed
ctld.jtacLastRadioRefresh = 0 -- time at which the target lists were refreshed for everyone at least ctld.jtacLastRadioRefresh = 0 -- time at which the target lists were refreshed for everyone at least
ctld.newJtac = {} --indicator to know when a new JTAC is added to a coalition in order to rebuild the corresponding target lists ctld.refreshJTACmenu = {} --indicator to know when a new JTAC is added to a coalition in order to rebuild the corresponding target lists
ctld.jtacGeneratedLaserCodes = {} -- keeps track of generated codes, cycles when they run out ctld.jtacGeneratedLaserCodes = {} -- keeps track of generated codes, cycles when they run out
ctld.jtacLaserPointCodes = {} ctld.jtacLaserPointCodes = {}
ctld.jtacRadioData = {} ctld.jtacRadioData = {}
--[[
Called when a new JTAC is spawned, it will wait one second for DCS to have time to fill the group with units, and then call ctld.JTACAutoLase.
The goal here is to correct a bug: when a group is respawned (i.e. when any group with the name of a previously existing group is spawned),
DCS spawns a group which exists (Group.getByName gets a valid table, and group:isExist returns true), but has no units (i.e. group:getUnits returns an empty table).
This causes JTACAutoLase to call cleanupJTAC because it does not find the JTAC unit, and the JTAC to be put out of the JTACAutoLase loop, and never processed again.
By waiting a bit, the group gets populated before JTACAutoLase is called, hence avoiding a trip to cleanupJTAC.
]]
function ctld.JTACStart(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio)
mist.scheduleFunction(ctld.JTACAutoLase, {_jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio}, timer.getTime()+1)
end
function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio) function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio)
ctld.logDebug(string.format("ctld.JTACAutoLase(_jtacGroupName=%s, _laserCode=%s", ctld.p(_jtacGroupName), ctld.p(_laserCode))) ctld.logDebug(string.format("ctld.JTACAutoLase(_jtacGroupName=%s, _laserCode=%s", ctld.p(_jtacGroupName), ctld.p(_laserCode)))
@@ -5315,7 +5328,7 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
--jtac soldier being transported by heli --jtac soldier being transported by heli
ctld.cleanupJTAC(_jtacGroupName) ctld.cleanupJTAC(_jtacGroupName)
env.info(_jtacGroupName .. ' in Transport - Waiting 10 seconds') ctld.logTrace(string.format("JTAC - LASE - [%s] - in transport, waiting - scheduling JTACAutoLase in %ss at %s", ctld.p(_jtacGroupName), ctld.p(10), ctld.p(timer.getTime() + 10)))
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 10) timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 10)
return return
end end
@@ -5324,7 +5337,7 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
--jtac vehicle being transported by heli --jtac vehicle being transported by heli
ctld.cleanupJTAC(_jtacGroupName) ctld.cleanupJTAC(_jtacGroupName)
env.info(_jtacGroupName .. ' in Transport - Waiting 10 seconds') ctld.logTrace(string.format("JTAC - LASE - [%s] - in transport, waiting - scheduling JTACAutoLase in %ss at %s", ctld.p(_jtacGroupName), ctld.p(10), ctld.p(timer.getTime() + 10)))
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 10) timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 10)
return return
end end
@@ -5347,10 +5360,18 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
--add to list --add to list
ctld.jtacUnits[_jtacGroupName] = { name = _jtacUnit:getName(), side = _jtacCoalition, radio = _radio } ctld.jtacUnits[_jtacGroupName] = { name = _jtacUnit:getName(), side = _jtacCoalition, radio = _radio }
--Targets list and Selected target initialization --Targets list, special options and Selected target initialization
if not ctld.jtacTargetsList[_jtacGroupName] then if not ctld.jtacTargetsList[_jtacGroupName] then
--Target list
ctld.jtacTargetsList[_jtacGroupName] = {} ctld.jtacTargetsList[_jtacGroupName] = {}
if _jtacCoalition then ctld.newJtac[_jtacCoalition] = true end if _jtacCoalition then ctld.refreshJTACmenu[_jtacCoalition] = true end
--Special Options
for _,_specialOption in pairs(ctld.jtacSpecialOptions) do
if _specialOption.jtacs then
_specialOption.jtacs[_jtacGroupName] = false
end
end
end end
if not ctld.jtacSelectedTarget[_jtacGroupName] then if not ctld.jtacSelectedTarget[_jtacGroupName] then
@@ -5385,7 +5406,7 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
ctld.cleanupJTAC(_jtacGroupName) ctld.cleanupJTAC(_jtacGroupName)
env.info(_jtacGroupName .. ' Not Active - Waiting 30 seconds') ctld.logTrace(string.format("JTAC - LASE - [%s] - not active, scheduling JTACAutoLase in 30s at %s", ctld.p(_jtacGroupName), ctld.p(timer.getTime() + 30)))
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 30) timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 30)
return return
@@ -5456,7 +5477,14 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
-- store current target for easy lookup -- store current target for easy lookup
ctld.jtacCurrentTargets[_jtacGroupName] = { name = _defaultEnemyUnit:getName(), unitType = _defaultEnemyUnit:getTypeName(), unitId = _defaultEnemyUnit:getID() } ctld.jtacCurrentTargets[_jtacGroupName] = { name = _defaultEnemyUnit:getName(), unitType = _defaultEnemyUnit:getTypeName(), unitId = _defaultEnemyUnit:getID() }
local action = "lasing new target, " --add check for lasing or not
local action = "new target, "
if ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
action = "standing by on " .. action
else
action = "lasing " .. action
end
if wasSelected and targetLost then if wasSelected and targetLost then
action = ", temporarily " .. action action = ", temporarily " .. action
@@ -5495,30 +5523,30 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
end end
end end
if _enemyUnit ~= nil then if _enemyUnit ~= nil and not ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
local refreshDelay = 15 --delay in between JTACAutoLase scheduled calls when a target is tracked local refreshDelay = 15 --delay in between JTACAutoLase scheduled calls when a target is tracked
local targetSpeedVec = _enemyUnit:getVelocity() local targetSpeedVec = _enemyUnit:getVelocity()
local targetSpeed = math.sqrt(targetSpeedVec.x^2+targetSpeedVec.y^2+targetSpeedVec.z^2) local targetSpeed = math.sqrt(targetSpeedVec.x^2+targetSpeedVec.y^2+targetSpeedVec.z^2)
local maxUpdateDist = 5 --maximum distance the unit will be allowed to travel before the lase spot is updated again local maxUpdateDist = 5 --maximum distance the unit will be allowed to travel before the lase spot is updated again
ctld.logDebug(string.format("targetSpeed=%s", ctld.p(targetSpeed))) ctld.logTrace(string.format("targetSpeed=%s", ctld.p(targetSpeed)))
ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode) ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
--if the target is going sufficiently fast for it to wander off futher than the maxUpdateDist, schedule laseUnit calls to update the lase spot only (we consider that the unit lives and drives on between JTACAutoLase calls) --if the target is going sufficiently fast for it to wander off futher than the maxUpdateDist, schedule laseUnit calls to update the lase spot only (we consider that the unit lives and drives on between JTACAutoLase calls)
if targetSpeed >= maxUpdateDist/refreshDelay then if targetSpeed >= maxUpdateDist/refreshDelay then
local updateTimeStep = maxUpdateDist/targetSpeed --calculate the time step so that the target is never more than maxUpdateDist from it's last lased position local updateTimeStep = maxUpdateDist/targetSpeed --calculate the time step so that the target is never more than maxUpdateDist from it's last lased position
ctld.logDebug(string.format("updateTimeStep=%s", ctld.p(updateTimeStep))) ctld.logTrace(string.format("JTAC - LASE - [%s] - target is moving at %s m/s, schedulting lasing steps every %ss", ctld.p(_jtacGroupName), ctld.p(targetSpeed), ctld.p(updateTimeStep)))
local i = 1 local i = 1
while i*updateTimeStep <= refreshDelay - updateTimeStep do --while the scheduled time for the laseUnit call isn't greater than the time between two JTACAutoLase() calls minus one time step (because at the next time step JTACAutoLase() should have been called and this in term also calls laseUnit()) while i*updateTimeStep <= refreshDelay - updateTimeStep do --while the scheduled time for the laseUnit call isn't greater than the time between two JTACAutoLase() calls minus one time step (because at the next time step JTACAutoLase() should have been called and this in term also calls laseUnit())
ctld.logTrace("ctld.laseUnit scheduled " .. i)
timer.scheduleFunction(ctld.timerLaseUnit,{_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode}, timer.getTime()+i*updateTimeStep) timer.scheduleFunction(ctld.timerLaseUnit,{_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode}, timer.getTime()+i*updateTimeStep)
i = i + 1 i = i + 1
end end
ctld.logTrace(string.format("JTAC - LASE - [%s] - scheduled %s moving target lasing steps", ctld.p(_jtacGroupName), ctld.p(i)))
end end
-- env.info('Timer timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName()) ctld.logTrace(string.format("JTAC - LASE - [%s] - scheduling JTACAutoLase in %ss at %s", ctld.p(_jtacGroupName), ctld.p(refreshDelay), ctld.p(timer.getTime() + refreshDelay)))
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + refreshDelay) timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + refreshDelay)
if _smoke == true then if _smoke == true then
@@ -5532,12 +5560,13 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
end end
else else
-- env.info('LASE: No Enemies Nearby') ctld.logDebug(string.format("JTAC - MODE - [%s] - No Enemies Nearby / Standby mode", ctld.p(_jtacGroupName)))
-- stop lazing the old spot -- stop lazing the old spot
ctld.logDebug(string.format("JTAC - LASE - [%s] - canceling lasing of the old spot", ctld.p(_jtacGroupName)))
ctld.cancelLase(_jtacGroupName) ctld.cancelLase(_jtacGroupName)
-- env.info('Timer Slow timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName())
ctld.logTrace(string.format("JTAC - LASE - [%s] - scheduling JTACAutoLase in %ss at %s", ctld.p(_jtacGroupName), ctld.p(5), ctld.p(timer.getTime() + 5)))
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 5) timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 5)
end end
@@ -5574,6 +5603,12 @@ function ctld.cleanupJTAC(_jtacGroupName)
ctld.jtacSelectedTarget[_jtacGroupName] = nil ctld.jtacSelectedTarget[_jtacGroupName] = nil
for _,_specialOption in pairs(ctld.jtacSpecialOptions) do --delete jtac specific settings for all special options
if _specialOption.jtacs then
_specialOption.jtacs[_jtacGroupName] = nil
end
end
ctld.jtacRadioData[_jtacGroupName] = nil ctld.jtacRadioData[_jtacGroupName] = nil
--remove the JTAC's group submenu and all of the target pages it potentially contained if the JTAC has or had a menu --remove the JTAC's group submenu and all of the target pages it potentially contained if the JTAC has or had a menu
@@ -5602,8 +5637,6 @@ end
--- send a message to the coalition --- send a message to the coalition
--- if _radio is set, the message will be read out loud via SRS --- if _radio is set, the message will be read out loud via SRS
function ctld.notifyCoalition(_message, _displayFor, _side, _radio, _shortMessage) function ctld.notifyCoalition(_message, _displayFor, _side, _radio, _shortMessage)
ctld.logDebug(string.format("ctld.notifyCoalition(_message=%s)", ctld.p(_message)))
ctld.logTrace(string.format("_radio=%s", ctld.p(_radio)))
local _shortMessage = _shortMessage local _shortMessage = _shortMessage
if _shortMessage == nil then if _shortMessage == nil then
@@ -5619,15 +5652,6 @@ function ctld.notifyCoalition(_message, _displayFor, _side, _radio, _shortMessag
local _culture = _radio.culture or "en-US" local _culture = _radio.culture or "en-US"
local _voice = _radio.voice local _voice = _radio.voice
local _googleTTS = _radio.googleTTS or false local _googleTTS = _radio.googleTTS or false
ctld.logTrace(string.format("calling STTS.TextToSpeech(%s)", ctld.p(_shortMessage)))
ctld.logTrace(string.format("_freq=%s", ctld.p(_freq)))
ctld.logTrace(string.format("_modulation=%s", ctld.p(_modulation)))
ctld.logTrace(string.format("_volume=%s", ctld.p(_volume)))
ctld.logTrace(string.format("_name=%s", ctld.p(_name)))
ctld.logTrace(string.format("_gender=%s", ctld.p(_gender)))
ctld.logTrace(string.format("_culture=%s", ctld.p(_culture)))
ctld.logTrace(string.format("_voice=%s", ctld.p(_voice)))
ctld.logTrace(string.format("_googleTTS=%s", ctld.p(_googleTTS)))
STTS.TextToSpeech(_shortMessage, _freq, _modulation, _volume, _name, _side, nil, 1, _gender, _culture, _voice, _googleTTS) STTS.TextToSpeech(_shortMessage, _freq, _modulation, _volume, _name, _side, nil, 1, _gender, _culture, _voice, _googleTTS)
end end
@@ -5689,7 +5713,7 @@ function ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
local _enemyVector = _enemyUnit:getPoint() local _enemyVector = _enemyUnit:getPoint()
local _enemyVectorUpdated = { x = _enemyVector.x, y = _enemyVector.y + 2.0, z = _enemyVector.z } local _enemyVectorUpdated = { x = _enemyVector.x, y = _enemyVector.y + 2.0, z = _enemyVector.z }
if ctld.JTAC_laseSpotCorrections then if ctld.jtacSpecialOptions.laseSpotCorrections.jtacs[_jtacGroupName] then
local _enemySpeedVector = _enemyUnit:getVelocity() local _enemySpeedVector = _enemyUnit:getVelocity()
ctld.logTrace(string.format("_enemySpeedVector=%s", ctld.p(_enemySpeedVector))) ctld.logTrace(string.format("_enemySpeedVector=%s", ctld.p(_enemySpeedVector)))
@@ -5811,7 +5835,7 @@ function ctld.findNearestVisibleEnemy(_jtacUnit, _targetType,_distance)
local _nearestDistance = _maxDistance local _nearestDistance = _maxDistance
local _jtacGroupName = _jtacUnit:getName() local _jtacGroupName = _jtacUnit:getGroup():getName()
local _jtacPoint = _jtacUnit:getPoint() local _jtacPoint = _jtacUnit:getPoint()
local _coa = _jtacUnit:getCoalition() local _coa = _jtacUnit:getCoalition()
@@ -5993,19 +6017,25 @@ end
function ctld.getGroup(groupName) function ctld.getGroup(groupName)
local _groupUnits = Group.getByName(groupName) local _group = Group.getByName(groupName)
local _filteredUnits = {} --contains alive units local _filteredUnits = {} --contains alive units
local _x = 1 local _x = 1
if _groupUnits ~= nil and _groupUnits:isExist() then if _group ~= nil then
ctld.logTrace(string.format("ctld.getGroup - %s - group ~= nil", ctld.p(groupName)))
_groupUnits = _groupUnits:getUnits() if _group:isExist() then
ctld.logTrace(string.format("ctld.getGroup - %s - group:isExist()", ctld.p(groupName)))
local _groupUnits = _group:getUnits()
if _groupUnits ~= nil and #_groupUnits > 0 then if _groupUnits ~= nil and #_groupUnits > 0 then
ctld.logTrace(string.format("ctld.getGroup - %s - group has %s units", ctld.p(groupName), ctld.p(#_groupUnits)))
for _x = 1, #_groupUnits do for _x = 1, #_groupUnits do
if _groupUnits[_x]:getLife() > 0 then -- removed and _groupUnits[_x]:isExist() as isExist doesnt work on single units! if _groupUnits[_x]:getLife() > 0 then -- removed and _groupUnits[_x]:isExist() as isExist doesnt work on single units!
table.insert(_filteredUnits, _groupUnits[_x]) table.insert(_filteredUnits, _groupUnits[_x])
else
ctld.logTrace(string.format("ctld.getGroup - %s - dead unit %s", ctld.p(groupName), ctld.p(_groupUnits[_x]:getName())))
end
end end
end end
end end
@@ -6028,15 +6058,22 @@ end
-- gets the JTAC status and displays to coalition units -- gets the JTAC status and displays to coalition units
function ctld.getJTACStatus(_args) function ctld.getJTACStatus(_args)
--returns the status of all JTAC units --returns the status of all JTAC units unless the status of a single JTAC is asked for (by inserting it's groupName in _args[2])
local _playerUnit = ctld.getTransportUnit(_args[1]) local _playerUnit = ctld.getTransportUnit(_args[1])
local _singleJtacGroupName = _args[2]
if _playerUnit == nil then if _playerUnit == nil and _singleJtacGroupName == nil then
return return
end end
local _side = _playerUnit:getCoalition() local _side = nil
if _playerUnit == nil then
_side = ctld.jtacUnits[_singleJtacGroupName].side
else
_side = _playerUnit:getCoalition()
end
local _jtacGroupName = nil local _jtacGroupName = nil
local _jtacUnit = nil local _jtacUnit = nil
@@ -6046,6 +6083,7 @@ function ctld.getJTACStatus(_args)
for _jtacGroupName, _jtacDetails in pairs(ctld.jtacUnits) do for _jtacGroupName, _jtacDetails in pairs(ctld.jtacUnits) do
--look up units --look up units
if _singleJtacGroupName == nil or (_singleJtacGroupName and _singleJtacGroupName == _jtacGroupName) then --if the status of a single JTAC or if the status of a single JTAC was asked and this is the correct JTAC we're going over in the loop
_jtacUnit = Unit.getByName(_jtacDetails.name) _jtacUnit = Unit.getByName(_jtacDetails.name)
if _jtacUnit ~= nil and _jtacUnit:getLife() > 0 and _jtacUnit:isActive() == true and _jtacUnit:getCoalition() == _side then if _jtacUnit ~= nil and _jtacUnit:getLife() > 0 and _jtacUnit:isActive() == true and _jtacUnit:getCoalition() == _side then
@@ -6075,6 +6113,10 @@ function ctld.getJTACStatus(_args)
end end
end end
if ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
action = action .. "(Laser OFF) "
end
_message = _message .. "" .. _start .. action .. _enemyUnit:getTypeName() .. " CODE: " .. _laserCode .. ctld.getPositionString(_enemyUnit) .. "\n" _message = _message .. "" .. _start .. action .. _enemyUnit:getTypeName() .. " CODE: " .. _laserCode .. ctld.getPositionString(_enemyUnit) .. "\n"
local _list = ctld.listNearbyEnemies(_jtacUnit) local _list = ctld.listNearbyEnemies(_jtacUnit)
@@ -6083,7 +6125,7 @@ function ctld.getJTACStatus(_args)
_message = _message.."Visual On: " _message = _message.."Visual On: "
for _,_type in pairs(_list) do for _,_type in pairs(_list) do
_message = _message.._type.." " _message = _message.._type..", "
end end
_message = _message.."\n" _message = _message.."\n"
end end
@@ -6093,6 +6135,7 @@ function ctld.getJTACStatus(_args)
end end
end end
end end
end
if _message == "JTAC STATUS: \n\n" then if _message == "JTAC STATUS: \n\n" then
_message = "No Active JTACs" _message = "No Active JTACs"
@@ -6134,10 +6177,111 @@ function ctld.setJTACTarget(_args)
local message = _jtacGroupName .. ", target selection reset." local message = _jtacGroupName .. ", target selection reset."
ctld.notifyCoalition(message, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName]) ctld.notifyCoalition(message, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
if ctld.jtacSpecialOptions.laseSpotCorrections.jtacs[_jtacGroupName] then
ctld.setLaseCompensation({jtacGroupName = _jtacGroupName, value = false}) --disable laser spot corrections
end end
if ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
ctld.setStdbMode({jtacGroupName = _jtacGroupName, value = false}) --make the JTAC exit standby mode after either target selection or targeting selection reset
end
end
ctld.refreshJTACmenu[ctld.jtacUnits[_jtacGroupName].side] = true
end end
end end
--special option setters (make sure to affect the function pointer to the corresponding .setter in the special options table after declaration of said function)
function ctld.setSpecialOptionArgsCheck(_args)
if _args then
local _jtacGroupName = _args.jtacGroupName
local _value = _args.value --expected boolean
local _notOutput = _args.noOutput --expected boolean
if _jtacGroupName then
return {jtacGroupName = _jtacGroupName, value = _value, noOutput = _notOutput}
end
end
return nil
end
function ctld.setStdbMode(_args)
local parsedArgs = ctld.setSpecialOptionArgsCheck(_args)
if parsedArgs then
local _jtacGroupName = parsedArgs.jtacGroupName
local _value = parsedArgs.value
local _noOutput = parsedArgs.noOutput
local message_end = " enabled"
if _value then
message_end = " disabled"
end
if not _noOutput then
ctld.notifyCoalition(_jtacGroupName .. ", Laser and Smokes" .. message_end, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
end
ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] = _value
ctld.refreshJTACmenu[ctld.jtacUnits[_jtacGroupName].side] = true
end
end
ctld.jtacSpecialOptions.standbyMode.setter = ctld.setStdbMode
function ctld.setLaseCompensation(_args)
local parsedArgs = ctld.setSpecialOptionArgsCheck(_args)
if parsedArgs then
local _jtacGroupName = parsedArgs.jtacGroupName
local _value = parsedArgs.value
local _noOutput = parsedArgs.noOutput
local message_end = " disabled."
if _value then
message_end = " enabled."
end
if not _noOutput then
ctld.notifyCoalition(_jtacGroupName .. ", Wind and Target speed Laser Spot compensations" .. message_end, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
end
ctld.jtacSpecialOptions.laseSpotCorrections.jtacs[_jtacGroupName] = _value
ctld.refreshJTACmenu[ctld.jtacUnits[_jtacGroupName].side] = true
end
end
ctld.jtacSpecialOptions.laseSpotCorrections.setter = ctld.setLaseCompensation
function ctld.setSmokeOnTarget(_args)
local parsedArgs = ctld.setSpecialOptionArgsCheck(_args)
if parsedArgs then
local _jtacGroupName = parsedArgs.jtacGroupName
local _noOutput = parsedArgs.noOutput
local _enemyUnit = Unit.getByName(ctld.jtacCurrentTargets[_jtacGroupName].name)
if _enemyUnit then
if not _noOutput then
ctld.notifyCoalition(_jtacGroupName .. ", WHITE Smoke deployed near TGT", 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
end
local _enemyPoint = _enemyUnit:getPoint()
local randomCircleDiam = 30;
trigger.action.smoke({ x = _enemyPoint.x + math.random(randomCircleDiam,-randomCircleDiam), y = _enemyPoint.y + 2.0, z = _enemyPoint.z + math.random(randomCircleDiam,-randomCircleDiam)}, 2)
end
end
end
ctld.jtacSpecialOptions.smokeMarker.setter = ctld.setSmokeOnTarget
function ctld.setJTAC9Line(_args)
local parsedArgs = ctld.setSpecialOptionArgsCheck(_args)
if parsedArgs then
local _jtacGroupName = parsedArgs.jtacGroupName
ctld.getJTACStatus({nil, _jtacGroupName})
end
end
ctld.jtacSpecialOptions._9Line.setter = ctld.setJTAC9Line
function ctld.isInfantry(_unit) function ctld.isInfantry(_unit)
local _typeName = _unit:getTypeName() local _typeName = _unit:getTypeName()
@@ -6391,6 +6535,7 @@ function ctld.getPositionString(_unit)
return " @ " .. _latLngStr .. " - MGRS " .. _mgrsString return " @ " .. _latLngStr .. " - MGRS " .. _mgrsString
end end
-- ***************** SETUP SCRIPT **************** -- ***************** SETUP SCRIPT ****************
function ctld.initialize(force) function ctld.initialize(force)
ctld.logInfo(string.format("Initializing version %s", ctld.Version)) ctld.logInfo(string.format("Initializing version %s", ctld.Version))
+53 -13
View File
@@ -101,6 +101,20 @@ You can also edit the CTLD.lua file to change some configuration options. Make s
## Setup in Mission Editor ## Setup in Mission Editor
### Test mission
You can use the `test-mission.miz` mission as a demonstration on how to use the CTLD script in a DCS mission.
This mission includes the CTLD script, a proper configuration, demonstration for some of the main features (including the "JTAC talk over the radio via SRS" functionality).
**Note to developers**: it's quite easy to set the loading of the CTLD script to dynamic, so you can make changes to the script, save it and simply reload the mission (left-shift + R) in the game to test the edits you made.
To do this, simply change the "Define loading mode" trigger (1) so that the condition reads "FLAG IS FALSE" (2), and edit the "DO SCRIPT" action (3) to replace the path with the path to the `CTLD.lua` file on your PC.
Optionaly, you can disable the STTS (text to speech over SRS) feature (4).
![dynamic_loading]
### Script Setup ### Script Setup
**This script requires MIST version 4.0.57 or above: https://github.com/mrSkortch/MissionScriptingTools** **This script requires MIST version 4.0.57 or above: https://github.com/mrSkortch/MissionScriptingTools**
@@ -661,45 +675,69 @@ ctld.JTAC_LIMIT_BLUE = 10 -- max number of JTAC Crates for the BLUE Side
ctld.JTAC_dropEnabled = true -- allow JTAC Crate spawn from F10 menu ctld.JTAC_dropEnabled = true -- allow JTAC Crate spawn from F10 menu
ctld.JTAC_maxDistance = 4000 -- How far a JTAC can "see" in meters (with Line of Sight) ctld.JTAC_maxDistance = 10000 -- 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_RED = true -- enables automatic marking of target with smoke for RED forces
ctld.JTAC_smokeOn_BLUE = true -- enables marking of target with smoke for BLUE forces ctld.JTAC_smokeOn_BLUE = true -- enables automatic 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_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_smokeColour_BLUE = 1 -- BLUE side smoke colour -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4
ctld.JTAC_smokeOffset_x = 0.0 -- distance in the X direction from target to spawn smoke marker (default 0 meters) ctld.JTAC_smokeOffset_x = 0.0 -- distance in the X direction from target to smoke (meters)
ctld.JTAC_smokeOffset_y = 2.0 -- distance in the Y direction from target to spawn smoke marker (default 2 meters) ctld.JTAC_smokeOffset_y = 2.0 -- distance in the Y direction from target to smoke (meters)
ctld.JTAC_smokeOffset_z = 0.0 -- distance in the z direction from target to spawn smoke marker (default 0 meters) ctld.JTAC_smokeOffset_z = 0.0 -- distance in the z direction from target to smoke (meters)
ctld.JTAC_jtacStatusF10 = false -- enables F10 JTAC Status menu ctld.JTAC_jtacStatusF10 = true -- enables F10 JTAC Status menu
ctld.JTAC_location = false -- shows location of target in JTAC message ctld.JTAC_location = true -- shows location of target in JTAC message
ctld.location_DMS = false -- shows coordinates as Degrees Minutes Seconds instead of Degrees Decimal minutes
ctld.JTAC_lock = "all" -- "vehicle" OR "troop" OR "all" forces JTAC to only lock vehicles or troops or all ground units ctld.JTAC_lock = "all" -- "vehicle" OR "troop" OR "all" forces JTAC to only lock vehicles or troops or all ground units
ctld.JTAC_allowStandbyMode = true -- Allow players to toggle lasing on/off
ctld.JTAC_laseSpotCorrections = true -- Allow players to toggle on/off the JTAC leading it's target, taking into account current wind conditions and the speed of the target (particularily useful against moving heavy armor)
ctld.JTAC_allowSmokeRequest = true -- Allow players to request a smoke on target (temporary)
ctld.JTAC_allow9Line = true -- Allow players to ask for a 9Line (individual) for a specific JTAC's target
``` ```
To make a unit deployed from a crate into a JTAC unit, add the type to the ```ctld.jtacUnitTypes``` list. To make a unit deployed from a crate into a JTAC unit, add the type to the ```ctld.jtacUnitTypes``` list.
The script allows a JTAC to mark and hold an IR and Laser point on a target allowing TGP's to lock onto the lase and ease of target location using NV Goggles. The script allows a JTAC to mark and hold an IR and Laser point on a target allowing TGP's to lock onto the lase and ease of target location using NV Goggles.
The JTAC will automatically switch targets when a target is destroyed or goes out of Line of Sight. The JTAC will automatically switch targets when a target is destroyed or goes out of Line of Sight. Alternatively, a target list is available to chose from for each JTAC.
The JTACs can be configured globally to target only vehicles or troops or all ground targets. The JTACs can be configured globally to target only vehicles or troops or all ground targets.
JTACs can also be asked to put smoke on target, give out 9-Lines, to toggle lasing on/off and compensate the laser spot position for target movement and local wind.
*** NOTE: LOS doesn't include buildings or tree's... Sorry! *** *** NOTE: LOS doesn't include buildings or tree's... Sorry! ***
The script can also be useful in daylight by enabling the JTAC to mark enemy positions with Smoke. The JTAC will only move the smoke to the target every 5 minutes (to stop a huge trail of smoke markers) unless the target is destroyed, in which case the new target will be marked straight away with smoke. There is also an F10 menu option for units allowing the JTAC(s) to report their current status but if a JTAC is down it won't report in. The script can also be useful in daylight by enabling the JTAC to automatically mark enemy positions with Smoke. The JTAC will only move the smoke to the target every 5 minutes (to stop a huge trail of smoke markers) unless the target is destroyed, in which case the new target will be marked straight away with smoke. There is also an F10 menu to get the status of all JTACs, access the target lists and options for each JTAC (such as toggling lasing on/off or requesting a smoke manually). Do note that if a JTAC is down it won't report in or have it's own menu for targets and options. JTACs also do not overlap each other so the target lists do not include already lased targets.
The smoke will be offset from the target by the distances declared in the `ctld.JTAC_smokeOffset_*` constants. The automatic smokes will be offset from the target by the distances declared in the `ctld.JTAC_smokeOffset_*` constants. Requested smokes will be put close but not on target.
To add JTACS to the mission using the editor place a JTAC unit on the map putting each JTAC in it's own group containing only itself and no In practice, this is what the F10 radio menu for JTACs looks like :
![alt text](https://imgur.com/pfVldQ1.png "JTAC F10 Radio Menu")
You can see the "JTAC Status" command and the Selection Lists for each JTAC. Those look like :
![alt text](https://imgur.com/oDtajwv.png "Selection List for a JTAC")
Each target type within LOS of the JTAC and not already being lased (by any JTAC) is listed. Quantity is indicated. There is also the Action menu which looks like :
![alt text](https://imgur.com/nYWODLj.png "Action List for a JTAC")
This will allow you to act on the behavior of the JTAC or make requests. These items get updated every minute or so to reflect current configuration, same for the target list.
*** NOTE: Please be patient with the JTAC menu, wait at least 10 seconds between commands. If a spurious command is triggered, wait the same 10 seconds and try again. Sorry for this inconvenience. ***
To add JTACs or AFACs to the mission using the editor place a JTAC/AFAC unit on the map putting each JTAC/AFAC in it's own group containing only itself and no
other units. Name the group something easy to remember e.g. JTAC1 and make sure the JTAC units have a unique name which must other units. Name the group something easy to remember e.g. JTAC1 and make sure the JTAC units have a unique name which must
not be the same as the group name. The editor should do this for you but be careful if you copy and paste. not be the same as the group name. The editor should do this for you but be careful if you copy and paste.
Run the code below as a DO SCRIPT at the start of the mission, or after a delay if you prefer to activate a mission JTAC. Run the code below as a DO SCRIPT at the start of the mission, or after a delay if you prefer to activate a mission JTAC or AFAC.
**JTAC units deployed by unpacking a crate will automatically activate and begin searching for targets immediately.** **JTAC units deployed by unpacking a crate will automatically activate and begin searching for targets immediately.**
@@ -1022,3 +1060,5 @@ Below is a complete list of all the "actions" plus the data that is sent through
* ```{unit = "Unit that did the action",crate = "Crate Details", spawnedGroup = "Group rearmed by crate", action = "rearm"}``` * ```{unit = "Unit that did the action",crate = "Crate Details", spawnedGroup = "Group rearmed by crate", action = "rearm"}```
* ```{unit = "Unit that did the action",crate = "Crate Details", spawnedGroup = "Group spawned by crate", action = "unpack"}``` * ```{unit = "Unit that did the action",crate = "Crate Details", spawnedGroup = "Group spawned by crate", action = "unpack"}```
* ```{unit = "Unit that did the action",crate = "Crate Details", spawnedGroup = "Group repaired by crate", action = "repair"}``` * ```{unit = "Unit that did the action",crate = "Crate Details", spawnedGroup = "Group repaired by crate", action = "repair"}```
[dynamic_loading]: trigger-dynamic-loading.png
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB