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 15:55:06 +01:00
parent 2fba4dba03
commit ea762d4701
4 changed files with 383 additions and 198 deletions
+279 -134
View File
@@ -26,7 +26,7 @@ ctld = {} -- DONT REMOVE!
ctld.Id = "CTLD - "
--- 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`
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_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 *****************
@@ -1034,7 +1037,6 @@ end
-- EG: ctld.activatePickupZone("pickzone3")
-- This is enable pickzone3 to be used as a pickup zone for the team set
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
@@ -1582,7 +1584,6 @@ function ctld.spawnCrateStatic(_country, _unitId, _point, _name, _weight,_side)
_crate["heading"] = 0
_crate["country"] = _country
ctld.logTrace(string.format("_crate=%s", ctld.p(_crate)))
mist.dynAddStatic(_crate)
_spawnedCrate = StaticObject.getByName(_crate["name"])
@@ -1863,13 +1864,10 @@ function ctld.deployTroops(_heli, _troops)
if _extractZone == false then
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
local _code = table.remove(ctld.jtacGeneratedLaserCodes, 1)
ctld.logTrace(string.format("_code=%s", ctld.p(_code)))
table.insert(ctld.jtacGeneratedLaserCodes, _code)
ctld.logTrace(string.format("_droppedTroops:getName()=%s", ctld.p(_droppedTroops:getName())))
ctld.JTACAutoLase(_droppedTroops:getName(), _code)
ctld.JTACStart(_droppedTroops:getName(), _code)
end
if _heli:getCoalition() == 1 then
@@ -1960,7 +1958,6 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
local _weight = 0
for i = 1, count do
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
end
return _weight
@@ -1969,54 +1966,43 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
if type(_countOrTemplate) == "table" then
if _countOrTemplate.aa then
ctld.logTrace(string.format("_countOrTemplate.aa=%s", ctld.p(_countOrTemplate.aa)))
if _side == 2 then
_troops = ctld.insertIntoTroopsArray("Soldier stinger",_countOrTemplate.aa,_troops)
else
_troops = ctld.insertIntoTroopsArray("SA-18 Igla manpad",_countOrTemplate.aa,_troops)
end
_weight = _weight + getSoldiersWeight(_countOrTemplate.aa, ctld.MANPAD_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end
if _countOrTemplate.inf then
ctld.logTrace(string.format("_countOrTemplate.inf=%s", ctld.p(_countOrTemplate.inf)))
if _side == 2 then
_troops = ctld.insertIntoTroopsArray("Soldier M4",_countOrTemplate.inf,_troops)
else
_troops = ctld.insertIntoTroopsArray("Soldier AK",_countOrTemplate.inf,_troops)
end
_weight = _weight + getSoldiersWeight(_countOrTemplate.inf, ctld.RIFLE_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end
if _countOrTemplate.mg then
ctld.logTrace(string.format("_countOrTemplate.mg=%s", ctld.p(_countOrTemplate.mg)))
if _side == 2 then
_troops = ctld.insertIntoTroopsArray("Soldier M249",_countOrTemplate.mg,_troops)
else
_troops = ctld.insertIntoTroopsArray("Paratrooper AKS-74",_countOrTemplate.mg,_troops)
end
_weight = _weight + getSoldiersWeight(_countOrTemplate.mg, ctld.MG_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end
if _countOrTemplate.at then
ctld.logTrace(string.format("_countOrTemplate.at=%s", ctld.p(_countOrTemplate.at)))
_troops = ctld.insertIntoTroopsArray("Paratrooper RPG-16",_countOrTemplate.at,_troops)
_weight = _weight + getSoldiersWeight(_countOrTemplate.at, ctld.RPG_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end
if _countOrTemplate.mortar then
ctld.logTrace(string.format("_countOrTemplate.mortar=%s", ctld.p(_countOrTemplate.mortar)))
_troops = ctld.insertIntoTroopsArray("2B11 mortar",_countOrTemplate.mortar,_troops)
_weight = _weight + getSoldiersWeight(_countOrTemplate.mortar, ctld.MORTAR_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end
if _countOrTemplate.jtac then
ctld.logTrace(string.format("_countOrTemplate.jtac=%s", ctld.p(_countOrTemplate.jtac)))
if _side == 2 then
_troops = ctld.insertIntoTroopsArray("Soldier M4",_countOrTemplate.jtac,_troops, "JTAC")
else
@@ -2024,7 +2010,6 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
end
_hasJTAC = true
_weight = _weight + getSoldiersWeight(_countOrTemplate.jtac, ctld.JTAC_WEIGHT + ctld.RIFLE_WEIGHT)
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
end
else
@@ -2036,37 +2021,29 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
if _i <=2 then
_unitType = "Soldier M249"
_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
_unitType = "Paratrooper RPG-16"
_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
_unitType = "Soldier stinger"
_weight = _weight + getSoldiersWeight(1, ctld.MANPAD_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
else
_unitType = "Soldier M4"
_weight = _weight + getSoldiersWeight(1, ctld.RIFLE_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
end
else
if _i <=2 then
_unitType = "Paratrooper AKS-74"
_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
_unitType = "Paratrooper RPG-16"
_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
_unitType = "SA-18 Igla manpad"
_weight = _weight + getSoldiersWeight(1, ctld.MANPAD_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
else
_unitType = "Infantry AK"
_weight = _weight + getSoldiersWeight(1, ctld.RIFLE_WEIGHT)
ctld.logTrace(string.format("_unitType=%s, _weight=%s", ctld.p(_unitType), ctld.p(_weight)))
end
end
@@ -2082,7 +2059,6 @@ function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
_groupName = "Dropped JTAC Group"
end
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
end
@@ -2141,10 +2117,8 @@ function ctld.loadTroops(_heli, _troops, _numberOrTemplate)
_list = ctld.vehiclesForTransportBLUE
end
ctld.logTrace(string.format("_troops=%s", ctld.p(_troops)))
if _troops then
_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)
ctld.processCallback({unit = _heli, onboard = _onboard.troops, action = "load_troops"})
@@ -2160,7 +2134,6 @@ function ctld.loadTroops(_heli, _troops, _numberOrTemplate)
end
ctld.inTransitTroops[_heli:getName()] = _onboard
ctld.logTrace(string.format("ctld.inTransitTroops=%s", ctld.p(ctld.inTransitTroops[_heli:getName()])))
ctld.adaptWeightToCargo(_heli:getName())
end
@@ -2540,7 +2513,6 @@ function ctld.checkTroopStatus(_args)
end
local _, _message = ctld.getWeightOfCargo(_unitName)
ctld.logTrace(string.format("_message=%s", ctld.p(_message)))
if _message and _message ~= "" then
ctld.displayMessageToGroup(_heli, _message, 10)
end
@@ -2570,7 +2542,6 @@ function ctld.adaptWeightToCargo(unitName)
end
function ctld.getWeightOfCargo(unitName)
ctld.logDebug(string.format("ctld.getWeightOfCargo(%s)", ctld.p(unitName)))
local FOB_CRATE_WEIGHT = 800
local _weight = 0
@@ -2578,13 +2549,10 @@ function ctld.getWeightOfCargo(unitName)
-- add troops weight
if ctld.inTransitTroops[unitName] then
ctld.logTrace("ctld.inTransitTroops = true")
local _inTransit = ctld.inTransitTroops[unitName]
if _inTransit then
ctld.logTrace(string.format("_inTransit=%s", ctld.p(_inTransit)))
local _troops = _inTransit.troops
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)
_weight = _weight + _troops.weight
end
@@ -2593,44 +2561,35 @@ function ctld.getWeightOfCargo(unitName)
for _, _unit in pairs(_vehicles.units) do
_weight = _weight + _unit.weight
end
ctld.logTrace(string.format("_weight=%s", ctld.p(_weight)))
_description = _description .. string.format("%s vehicles onboard (%s kg)\n", #_vehicles.units, _weight)
end
end
end
ctld.logTrace(string.format("with troops and vehicles : weight = %s", tostring(_weight)))
-- add FOB crates weight
if ctld.inTransitFOBCrates[unitName] then
ctld.logTrace("ctld.inTransitFOBCrates = true")
_weight = _weight + FOB_CRATE_WEIGHT
_description = _description .. string.format("1 FOB Crate oboard (%s kg)\n", FOB_CRATE_WEIGHT)
end
ctld.logTrace(string.format("with FOB crates : weight = %s", tostring(_weight)))
-- add simulated slingload crates weight
local _crate = ctld.inTransitSlingLoadCrates[unitName]
if _crate then
ctld.logTrace(string.format("_crate=%s", ctld.p(_crate)))
if _crate.simulatedSlingload then
ctld.logTrace(string.format("_crate.weight=%s", ctld.p(_crate.weight)))
_weight = _weight + _crate.weight
_description = _description .. string.format("1 %s crate onboard (%s kg)\n", _crate.desc, _crate.weight)
end
end
ctld.logTrace(string.format("with simulated slingload crates : weight = %s", tostring(_weight)))
if _description ~= "" then
_description = _description .. string.format("Total weight of cargo : %s kg\n", _weight)
else
_description = "No cargo."
end
ctld.logTrace(string.format("_description = %s", tostring(_description)))
return _weight, _description
end
function ctld.checkHoverStatus()
--ctld.logDebug(string.format("ctld.checkHoverStatus()"))
timer.scheduleFunction(ctld.checkHoverStatus, nil, timer.getTime() + 1.0)
local _status, _result = pcall(function()
@@ -2643,13 +2602,10 @@ function ctld.checkHoverStatus()
--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
--ctld.logTrace(string.format("%s - capable of slingloading", ctld.p(_name)))
local _crates = ctld.getCratesAndDistance(_transUnit)
--ctld.logTrace(string.format("_crates = %s", ctld.p(_crates)))
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
--check height!
@@ -2657,12 +2613,10 @@ function ctld.checkHoverStatus()
--env.info("HEIGHT " .. _name .. " " .. _height .. " " .. _transUnit:getPoint().y .. " " .. _crate.crateUnit:getPoint().y)
-- ctld.heightDiff(_transUnit)
--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
local _time = ctld.hoverStatus[_transUnit:getName()]
--ctld.logTrace(string.format("_time = %s", ctld.p(_time)))
if _time == nil then
ctld.hoverStatus[_transUnit:getName()] = ctld.hoverTime
@@ -2691,7 +2645,6 @@ function ctld.checkHoverStatus()
local _copiedCrate = mist.utils.deepCopy(_crate.details)
_copiedCrate.simulatedSlingload = true
--ctld.logTrace(string.format("_copiedCrate = %s", ctld.p(_copiedCrate)))
ctld.inTransitSlingLoadCrates[_name] = _copiedCrate
ctld.adaptWeightToCargo(_name)
end
@@ -2771,7 +2724,6 @@ end
--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.
function ctld.refreshRadioBeacons()
ctld.logDebug("ctld.refreshRadioBeacons()")
timer.scheduleFunction(ctld.refreshRadioBeacons, nil, timer.getTime() + 60)
@@ -3214,7 +3166,7 @@ function ctld.unpackCrates(_arguments)
--put to the end
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
@@ -3396,7 +3348,6 @@ end
-- one for VHF and one for UHF
-- The units are set to to NOT engage
function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTime, _isFOB)
ctld.logDebug(string.format("ctld.createRadioBeacon(_name=%s)", ctld.p(_name)))
local _freq = ctld.generateADFFrequencies()
@@ -3424,9 +3375,6 @@ function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTim
_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)
@@ -3446,7 +3394,6 @@ function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTim
coalition = _coalition,
}
ctld.logDebug(string.format("calling ctld.updateRadioBeacon for beacon %s", ctld.p(_name)))
ctld.updateRadioBeacon(_beaconDetails)
table.insert(ctld.deployedRadioBeacons, _beaconDetails)
@@ -3489,7 +3436,6 @@ end
function ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
ctld.logDebug(string.format("ctld.spawnRadioBeaconUnit(_name=%s)", ctld.p(_name)))
local _groupId = ctld.getNextGroupId()
@@ -3525,8 +3471,6 @@ function ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
end
function ctld.updateRadioBeacon(_beaconDetails)
ctld.logDebug("ctld.updateRadioBeacon()")
ctld.logTrace(string.format("_beaconDetails=%s", ctld.p(_beaconDetails)))
local _vhfGroup = Group.getByName(_beaconDetails.vhfGroup)
@@ -3537,17 +3481,14 @@ function ctld.updateRadioBeacon(_beaconDetails)
local _radioLoop = {}
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 })
end
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 })
end
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 })
end
@@ -3555,19 +3496,15 @@ function ctld.updateRadioBeacon(_beaconDetails)
if (_batLife <= 0 and _beaconDetails.battery ~= -1) or #_radioLoop ~= 3 then
-- ran out of batteries
ctld.logDebug("ran out of batteries")
if _vhfGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_vhfGroup:getName())))
trigger.action.stopRadioTransmission(_vhfGroup:getName())
_vhfGroup:destroy()
end
if _uhfGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_uhfGroup:getName())))
trigger.action.stopRadioTransmission(_uhfGroup:getName())
_uhfGroup:destroy()
end
if _fmGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_fmGroup:getName())))
trigger.action.stopRadioTransmission(_fmGroup:getName())
_fmGroup:destroy()
end
@@ -3593,7 +3530,6 @@ function ctld.updateRadioBeacon(_beaconDetails)
_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)
trigger.action.stopRadioTransmission(_radio.group:getName())
@@ -3692,17 +3628,14 @@ function ctld.removeRadioBeacon(_args)
local _fmGroup = Group.getByName(_closetBeacon.fmGroup)
if _vhfGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_vhfGroup:getName())))
trigger.action.stopRadioTransmission(_vhfGroup:getName())
_vhfGroup:destroy()
end
if _uhfGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_uhfGroup:getName())))
trigger.action.stopRadioTransmission(_uhfGroup:getName())
_uhfGroup:destroy()
end
if _fmGroup ~= nil then
ctld.logTrace(string.format("stopping transmission of %s", ctld.p(_fmGroup:getName())))
trigger.action.stopRadioTransmission(_fmGroup:getName())
_fmGroup:destroy()
end
@@ -4471,7 +4404,6 @@ end
-- are we in pickup zone
function ctld.inPickupZone(_heli)
ctld.logDebug(string.format("ctld.inPickupZone(_heli=%s)", ctld.p(_heli)))
if ctld.inAir(_heli) then
return { inZone = false, limit = -1, index = -1 }
@@ -4480,7 +4412,6 @@ function ctld.inPickupZone(_heli)
local _heliPoint = _heli:getPoint()
for _i, _zoneDetails in pairs(ctld.pickupZones) do
ctld.logTrace(string.format("_zoneDetails=%s", ctld.p(_zoneDetails)))
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
@@ -4501,7 +4432,6 @@ function ctld.inPickupZone(_heli)
--get distance to center
local _dist = ctld.getDistance(_heliPoint, _triggerZone.point)
ctld.logTrace(string.format("_dist=%s", ctld.p(_dist)))
if _dist <= _triggerZone.radius then
local _heliCoalition = _heli:getCoalition()
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 _unitActions = ctld.getUnitActions(_unit:getTypeName())
ctld.logTrace(string.format("_unitActions=%s", ctld.p(_unitActions)))
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 _transportLimit = ctld.getTransportLimit(_unit:getTypeName())
ctld.logTrace(string.format("_transportLimit=%s", ctld.p(_transportLimit)))
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
-- check size & unit
@@ -5108,21 +5035,18 @@ function ctld.addJTACRadioCommand(_side)
if _groupId then
local newGroup = false
-- env.info("adding command for "..index)
if ctld.jtacRadioAdded[tostring(_groupId)] == nil then
-- env.info("about command for "..index)
newGroup = true
local JTACpath = missionCommands.addSubMenuForGroup(_groupId, ctld.jtacMenuName)
missionCommands.addCommandForGroup(_groupId, "JTAC Status", JTACpath, ctld.getJTACStatus, { _playerUnit:getName() })
ctld.jtacRadioAdded[tostring(_groupId)] = true
-- env.info("Added command for " .. index)
end
--fetch the time to check for a regular refresh
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.
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
@@ -5138,39 +5062,87 @@ function ctld.addJTACRadioCommand(_side)
local jtacCounter = 0
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
local jtacCoalition = ctld.jtacUnits[_jtacGroupName].side
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
if ctld.jtacGroupSubMenuPath[_jtacGroupName] and #ctld.jtacGroupSubMenuPath[_jtacGroupName]==2 then
missionCommands.removeItemForGroup(_groupId, ctld.jtacGroupSubMenuPath[_jtacGroupName])
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
--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
--recover the path to the current page with space available for JTAC group submenus
jtacCurrentPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacCurrentPagePath)
end
--add the JTAC group submenu to the current page
ctld.jtacGroupSubMenuPath[_jtacGroupName] = missionCommands.addSubMenuForGroup(_groupId, jtacGroupSubMenuName, jtacCurrentPagePath)
ctld.logTrace(string.format("jtacGroupSubMenuPath for %s is : %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacGroupSubMenuPath[_jtacGroupName])))
ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacGroupSubMenuPath = %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
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
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
local itemCounter = 0
itemCounter = itemCounter + 1 --one item is added to the first JTAC target page
--indicator table to know which unitType was already added to the radio submenu
local typeNameList = {}
@@ -5197,9 +5169,10 @@ function ctld.addJTACRadioCommand(_side)
local targetName = info.targetName
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
if itemCounter%9 == 0 then
--F1 through F10 makes 10 entries possible per page, with one being the NextMenu submenu.
if itemCounter%10 == 0 then
jtacTargetPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacTargetPagePath)
itemCounter = itemCounter + 1 --added the next page item
end
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
if ctld.newJtac[_side] then
ctld.newJtac[_side] = false
end
if ctld.refreshJTACmenu[_side] then
ctld.refreshJTACmenu[_side] = false
end
end
end
@@ -5254,15 +5227,55 @@ ctld.jtacStop = {} -- jtacs to tell to stop lasing
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.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.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.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.jtacLaserPointCodes = {}
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)
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
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)
return
end
@@ -5324,7 +5337,7 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
--jtac vehicle being transported by heli
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)
return
end
@@ -5347,10 +5360,18 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
--add to list
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
--Target list
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
if not ctld.jtacSelectedTarget[_jtacGroupName] then
@@ -5385,7 +5406,7 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
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)
return
@@ -5456,7 +5477,14 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
-- store current target for easy lookup
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
action = ", temporarily " .. action
@@ -5495,30 +5523,30 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
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 targetSpeedVec = _enemyUnit:getVelocity()
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
ctld.logDebug(string.format("targetSpeed=%s", ctld.p(targetSpeed)))
ctld.logTrace(string.format("targetSpeed=%s", ctld.p(targetSpeed)))
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 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
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
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)
i = i + 1
end
ctld.logTrace(string.format("JTAC - LASE - [%s] - scheduled %s moving target lasing steps", ctld.p(_jtacGroupName), ctld.p(i)))
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)
if _smoke == true then
@@ -5532,12 +5560,13 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
end
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
ctld.logDebug(string.format("JTAC - LASE - [%s] - canceling lasing of the old spot", ctld.p(_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)
end
@@ -5574,6 +5603,12 @@ function ctld.cleanupJTAC(_jtacGroupName)
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
--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
--- if _radio is set, the message will be read out loud via SRS
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
if _shortMessage == nil then
@@ -5619,15 +5652,6 @@ function ctld.notifyCoalition(_message, _displayFor, _side, _radio, _shortMessag
local _culture = _radio.culture or "en-US"
local _voice = _radio.voice
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)
end
@@ -5689,7 +5713,7 @@ function ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
local _enemyVector = _enemyUnit:getPoint()
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()
ctld.logTrace(string.format("_enemySpeedVector=%s", ctld.p(_enemySpeedVector)))
@@ -5811,7 +5835,7 @@ function ctld.findNearestVisibleEnemy(_jtacUnit, _targetType,_distance)
local _nearestDistance = _maxDistance
local _jtacGroupName = _jtacUnit:getName()
local _jtacGroupName = _jtacUnit:getGroup():getName()
local _jtacPoint = _jtacUnit:getPoint()
local _coa = _jtacUnit:getCoalition()
@@ -5993,19 +6017,25 @@ end
function ctld.getGroup(groupName)
local _groupUnits = Group.getByName(groupName)
local _group = Group.getByName(groupName)
local _filteredUnits = {} --contains alive units
local _x = 1
if _groupUnits ~= nil and _groupUnits:isExist() then
_groupUnits = _groupUnits:getUnits()
if _group ~= nil then
ctld.logTrace(string.format("ctld.getGroup - %s - group ~= nil", ctld.p(groupName)))
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
ctld.logTrace(string.format("ctld.getGroup - %s - group has %s units", ctld.p(groupName), ctld.p(#_groupUnits)))
for _x = 1, #_groupUnits do
if _groupUnits[_x]:getLife() > 0 then -- removed and _groupUnits[_x]:isExist() as isExist doesnt work on single units!
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
@@ -6028,15 +6058,22 @@ end
-- gets the JTAC status and displays to coalition units
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 _singleJtacGroupName = _args[2]
if _playerUnit == nil then
if _playerUnit == nil and _singleJtacGroupName == nil then
return
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 _jtacUnit = nil
@@ -6046,6 +6083,7 @@ function ctld.getJTACStatus(_args)
for _jtacGroupName, _jtacDetails in pairs(ctld.jtacUnits) do
--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)
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
if ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
action = action .. "(Laser OFF) "
end
_message = _message .. "" .. _start .. action .. _enemyUnit:getTypeName() .. " CODE: " .. _laserCode .. ctld.getPositionString(_enemyUnit) .. "\n"
local _list = ctld.listNearbyEnemies(_jtacUnit)
@@ -6083,7 +6125,7 @@ function ctld.getJTACStatus(_args)
_message = _message.."Visual On: "
for _,_type in pairs(_list) do
_message = _message.._type.." "
_message = _message.._type..", "
end
_message = _message.."\n"
end
@@ -6093,6 +6135,7 @@ function ctld.getJTACStatus(_args)
end
end
end
end
if _message == "JTAC STATUS: \n\n" then
_message = "No Active JTACs"
@@ -6134,9 +6177,110 @@ function ctld.setJTACTarget(_args)
local message = _jtacGroupName .. ", target selection reset."
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
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
--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)
@@ -6391,6 +6535,7 @@ function ctld.getPositionString(_unit)
return " @ " .. _latLngStr .. " - MGRS " .. _mgrsString
end
-- ***************** SETUP SCRIPT ****************
function ctld.initialize(force)
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
### 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
**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_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_BLUE = true -- enables marking of target with smoke for BLUE forces
ctld.JTAC_smokeOn_RED = true -- enables automatic marking of target with smoke for RED 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_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_y = 2.0 -- distance in the Y direction from target to spawn smoke marker (default 2 meters)
ctld.JTAC_smokeOffset_z = 0.0 -- distance in the z 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 smoke (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_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.
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.
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! ***
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
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.**
@@ -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 spawned by crate", action = "unpack"}```
* ```{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