mirror of
https://github.com/ciribob/DCS-CTLD.git
synced 2025-08-15 06:17:22 +00:00
Compare commits
33 Commits
20210827.0
...
2023.10.01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fba4dba03 | ||
|
|
5831d1ab2d | ||
|
|
e33b78806b | ||
|
|
7f341df588 | ||
|
|
3b4da129fd | ||
|
|
09efd47acd | ||
|
|
8f9eba4801 | ||
|
|
6400becdfc | ||
|
|
c1891a1a52 | ||
|
|
c4c8e15a32 | ||
|
|
ea30456493 | ||
|
|
a3c9d369d5 | ||
|
|
9b1cf75420 | ||
|
|
9517dabb0d | ||
|
|
b264bc2df0 | ||
|
|
2632845fd3 | ||
|
|
74526597e0 | ||
|
|
a961b7aaea | ||
|
|
240d2b6cbb | ||
|
|
65ab11aae1 | ||
|
|
c0f9115f41 | ||
|
|
14066646e0 | ||
|
|
f71649f4f0 | ||
|
|
046d51d86b | ||
|
|
166efc6160 | ||
|
|
17426bf487 | ||
|
|
b801853ef2 | ||
|
|
4b39a322a0 | ||
|
|
77b5f6008e | ||
|
|
ae0d68f5ef | ||
|
|
f70c6a7131 | ||
|
|
3717f0f11b | ||
|
|
1911c608bb |
554
CTLD.lua
554
CTLD.lua
@@ -10,14 +10,14 @@
|
||||
|
||||
See https://github.com/ciribob/DCS-CTLD for a user manual and the latest version
|
||||
|
||||
Contributors:
|
||||
- Steggles - https://github.com/Bob7heBuilder
|
||||
- mvee - https://github.com/mvee
|
||||
- jmontleon - https://github.com/jmontleon
|
||||
- emilianomolina - https://github.com/emilianomolina
|
||||
- davidp57 - https://github.com/veaf
|
||||
|
||||
- Allow minimum distance from friendly logistics to be set
|
||||
Contributors:
|
||||
- Steggles - https://github.com/Bob7heBuilder
|
||||
- mvee - https://github.com/mvee
|
||||
- jmontleon - https://github.com/jmontleon
|
||||
- emilianomolina - https://github.com/emilianomolina
|
||||
- davidp57 - https://github.com/veaf
|
||||
- Queton1-1 - https://github.com/Queton1-1
|
||||
- Proxy404 - https://github.com/Proxy404
|
||||
]]
|
||||
|
||||
ctld = {} -- DONT REMOVE!
|
||||
@@ -26,12 +26,11 @@ ctld = {} -- DONT REMOVE!
|
||||
ctld.Id = "CTLD - "
|
||||
|
||||
--- Version.
|
||||
ctld.Version = "20210827.01"
|
||||
ctld.Version = "202310.01"
|
||||
|
||||
-- debug level, specific to this module
|
||||
ctld.Debug = true
|
||||
-- trace level, specific to this module
|
||||
ctld.Trace = true
|
||||
-- To add debugging messages to dcs.log, change the following log levels to `true`; `Debug` is less detailed than `Trace`
|
||||
ctld.Debug = false
|
||||
ctld.Trace = false
|
||||
|
||||
ctld.alreadyInitialized = false -- if true, ctld.initialize() will not run
|
||||
|
||||
@@ -43,6 +42,7 @@ ctld.staticBugWorkaround = false -- DCS had a bug where destroying statics woul
|
||||
ctld.disableAllSmoke = false -- if true, all smoke is diabled at pickup and drop off zones regardless of settings below. Leave false to respect settings below
|
||||
|
||||
ctld.hoverPickup = true -- if set to false you can load crates with the F10 menu instead of hovering... Only if not using real crates!
|
||||
ctld.loadCrateFromMenu = false -- if set to true, you can load crates with the F10 menu OR hovering, in case of using choppers and planes for example.
|
||||
|
||||
ctld.enableCrates = true -- if false, Helis will not be able to spawn or unpack crates so will be normal CTTS
|
||||
ctld.slingLoad = false -- if false, crates can be used WITHOUT slingloading, by hovering above the crate, simulating slingloading but not the weight...
|
||||
@@ -60,7 +60,7 @@ ctld.maximumMoveDistance = 2000 -- max distance for troops to move from drop poi
|
||||
ctld.minimumDeployDistance = 1000 -- minimum distance from a friendly pickup zone where you can deploy a crate
|
||||
|
||||
ctld.numberOfTroops = 10 -- default number of troops to load on a transport heli or C-130
|
||||
-- also works as maximum size of group that'll fit into a helicopter unless overridden
|
||||
-- also works as maximum size of group that'll fit into a helicopter unless overridden
|
||||
ctld.enableFastRopeInsertion = true -- allows you to drop troops by fast rope
|
||||
ctld.fastRopeMaximumHeight = 18.28 -- in meters which is 60 ft max fast rope (not rappell) safe height
|
||||
|
||||
@@ -74,7 +74,7 @@ ctld.vehiclesWeight = {
|
||||
}
|
||||
|
||||
ctld.aaLaunchers = 3 -- controls how many launchers to add to the kub/buk when its spawned.
|
||||
ctld.hawkLaunchers = 5 -- controls how many launchers to add to the hawk when its spawned.
|
||||
ctld.hawkLaunchers = 8 -- controls how many launchers to add to the hawk when its spawned.
|
||||
|
||||
ctld.spawnRPGWithCoalition = true --spawns a friendly RPG unit with Coalition forces
|
||||
ctld.spawnStinger = false -- spawns a stinger / igla soldier with a group of 6 or more soldiers!
|
||||
@@ -142,6 +142,10 @@ ctld.JTAC_smokeOn_BLUE = true -- enables marking of target with smoke for BLUE f
|
||||
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 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 = true -- enables F10 JTAC Status menu
|
||||
|
||||
ctld.JTAC_location = true -- shows location of target in JTAC message
|
||||
@@ -149,6 +153,8 @@ 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)
|
||||
|
||||
-- ***************** Pickup, dropoff and waypoint zones *****************
|
||||
|
||||
-- Available colors (anything else like "none" disables smoke): "green", "red", "white", "orange", "blue", "none",
|
||||
@@ -542,7 +548,7 @@ ctld.spawnableCrates = {
|
||||
{ weight = 545, desc = "HAWK Search Radar", unit = "Hawk sr", side = 2 },
|
||||
{ weight = 546, desc = "HAWK Track Radar", unit = "Hawk tr", side = 2 },
|
||||
{ weight = 547, desc = "HAWK PCP", unit = "Hawk pcp" , side = 2 }, -- Remove this if on 1.2
|
||||
{ weight = 548, desc = "HAWK CWAR", unit = "Hawk cwar" , side = 2 }, -- Remove this if on 2.5
|
||||
{ weight = 548, desc = "HAWK CWAR", unit = "Hawk cwar" , side = 2 }, -- Remove this if on 2.5
|
||||
{ weight = 549, desc = "HAWK Repair", unit = "HAWK Repair" , side = 2 },
|
||||
-- End of HAWK
|
||||
|
||||
@@ -1395,7 +1401,7 @@ ctld.AASystemTemplate = {
|
||||
{name = "Hawk tr", desc = "HAWK Track Radar"},
|
||||
{name = "Hawk sr", desc = "HAWK Search Radar"},
|
||||
{name = "Hawk pcp", desc = "HAWK PCP"},
|
||||
{name = "Hawk cwar", desc = "HAWK CWAR"},
|
||||
{name = "Hawk cwar", desc = "HAWK CWAR"},
|
||||
},
|
||||
repair = "HAWK Repair",
|
||||
},
|
||||
@@ -1409,7 +1415,7 @@ ctld.AASystemTemplate = {
|
||||
},
|
||||
repair = "Patriot Repair",
|
||||
},
|
||||
{
|
||||
{
|
||||
name = "BUK AA System",
|
||||
count = 3,
|
||||
parts = {
|
||||
@@ -1564,7 +1570,7 @@ function ctld.spawnCrateStatic(_country, _unitId, _point, _name, _weight,_side)
|
||||
if ctld.slingLoad then
|
||||
_crate = mist.utils.deepCopy(ctld.spawnableCratesModel_sling)
|
||||
_crate["canCargo"] = true
|
||||
else
|
||||
else
|
||||
_crate = mist.utils.deepCopy(ctld.spawnableCratesModel_load)
|
||||
_crate["canCargo"] = false
|
||||
end
|
||||
@@ -2121,7 +2127,7 @@ function ctld.loadTroops(_heli, _troops, _numberOrTemplate)
|
||||
|
||||
--number doesnt apply to vehicles
|
||||
if _numberOrTemplate == nil or (type(_numberOrTemplate) ~= "table" and type(_numberOrTemplate) ~= "number") then
|
||||
_numberOrTemplate = ctld.numberOfTroops
|
||||
_numberOrTemplate = ctld.getTransportLimit(_heli:getTypeName())
|
||||
end
|
||||
|
||||
if _onboard == nil then
|
||||
@@ -2762,10 +2768,12 @@ function ctld.loadNearbyCrate(_name)
|
||||
|
||||
end
|
||||
|
||||
--recreates beacons to make sure they work!
|
||||
--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() + 30)
|
||||
timer.scheduleFunction(ctld.refreshRadioBeacons, nil, timer.getTime() + 60)
|
||||
|
||||
|
||||
for _index, _beaconDetails in ipairs(ctld.deployedRadioBeacons) do
|
||||
@@ -3388,10 +3396,7 @@ 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)
|
||||
|
||||
local _uhfGroup = ctld.spawnRadioBeaconUnit(_point, _country, "UHF")
|
||||
local _vhfGroup = ctld.spawnRadioBeaconUnit(_point, _country, "VHF")
|
||||
local _fmGroup = ctld.spawnRadioBeaconUnit(_point, _country, "FM")
|
||||
ctld.logDebug(string.format("ctld.createRadioBeacon(_name=%s)", ctld.p(_name)))
|
||||
|
||||
local _freq = ctld.generateADFFrequencies()
|
||||
|
||||
@@ -3410,25 +3415,24 @@ function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTim
|
||||
|
||||
--local _mgrsString = mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(_point)), 5)
|
||||
|
||||
local _message = _name
|
||||
local _freqsText = _name
|
||||
|
||||
if _isFOB then
|
||||
-- _message = "FOB " .. _message
|
||||
_battery = -1 --never run out of power!
|
||||
end
|
||||
|
||||
_message = _message .. " - " .. _latLngStr
|
||||
_freqsText = _freqsText .. " - " .. _latLngStr
|
||||
|
||||
-- env.info("GEN UHF: ".. _freq.uhf)
|
||||
-- env.info("GEN VHF: ".. _freq.vhf)
|
||||
|
||||
_message = string.format("%s - %.2f KHz", _message, _freq.vhf / 1000)
|
||||
|
||||
_message = string.format("%s - %.2f MHz", _message, _freq.uhf / 1000000)
|
||||
|
||||
_message = string.format("%s - %.2f MHz ", _message, _freq.fm / 1000000)
|
||||
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)
|
||||
|
||||
local _uhfGroup = ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
|
||||
local _vhfGroup = ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
|
||||
local _fmGroup = ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
|
||||
|
||||
local _beaconDetails = {
|
||||
vhf = _freq.vhf,
|
||||
@@ -3437,10 +3441,12 @@ function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTim
|
||||
uhfGroup = _uhfGroup:getName(),
|
||||
fm = _freq.fm,
|
||||
fmGroup = _fmGroup:getName(),
|
||||
text = _message,
|
||||
text = _freqsText,
|
||||
battery = _battery,
|
||||
coalition = _coalition,
|
||||
}
|
||||
|
||||
ctld.logDebug(string.format("calling ctld.updateRadioBeacon for beacon %s", ctld.p(_name)))
|
||||
ctld.updateRadioBeacon(_beaconDetails)
|
||||
|
||||
table.insert(ctld.deployedRadioBeacons, _beaconDetails)
|
||||
@@ -3482,7 +3488,8 @@ end
|
||||
|
||||
|
||||
|
||||
function ctld.spawnRadioBeaconUnit(_point, _country, _type)
|
||||
function ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
|
||||
ctld.logDebug(string.format("ctld.spawnRadioBeaconUnit(_name=%s)", ctld.p(_name)))
|
||||
|
||||
local _groupId = ctld.getNextGroupId()
|
||||
|
||||
@@ -3496,7 +3503,7 @@ function ctld.spawnRadioBeaconUnit(_point, _country, _type)
|
||||
[1] = {
|
||||
["y"] = _point.z,
|
||||
["type"] = "TACAN_beacon",
|
||||
["name"] = _type .. " Radio Beacon Unit #" .. _unitId,
|
||||
["name"] = "Unit #" .. _unitId .. " - " .. _name .. " [" .. _freqsText .. "]",
|
||||
-- ["unitId"] = _unitId,
|
||||
["heading"] = 0,
|
||||
["playerCanDrive"] = true,
|
||||
@@ -3506,7 +3513,7 @@ function ctld.spawnRadioBeaconUnit(_point, _country, _type)
|
||||
},
|
||||
-- ["y"] = _positions[1].z,
|
||||
-- ["x"] = _positions[1].x,
|
||||
["name"] = _type .. " Radio Beacon Group #" .. _groupId,
|
||||
["name"] = "Group #" .. _groupId .. " - " .. _name,
|
||||
["task"] = {},
|
||||
--added two fields below for MIST
|
||||
["category"] = Group.Category.GROUND,
|
||||
@@ -3518,6 +3525,8 @@ function ctld.spawnRadioBeaconUnit(_point, _country, _type)
|
||||
end
|
||||
|
||||
function ctld.updateRadioBeacon(_beaconDetails)
|
||||
ctld.logDebug("ctld.updateRadioBeacon()")
|
||||
ctld.logTrace(string.format("_beaconDetails=%s", ctld.p(_beaconDetails)))
|
||||
|
||||
local _vhfGroup = Group.getByName(_beaconDetails.vhfGroup)
|
||||
|
||||
@@ -3528,14 +3537,17 @@ 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
|
||||
|
||||
@@ -3543,14 +3555,20 @@ 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
|
||||
|
||||
@@ -3575,17 +3593,17 @@ function ctld.updateRadioBeacon(_beaconDetails)
|
||||
|
||||
_groupController:setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
|
||||
|
||||
trigger.action.radioTransmission(_sound, _radio.group:getUnit(1):getPoint(), _radio.mode, false, _radio.freq, 1000)
|
||||
--This function doesnt actually stop transmitting when then sound is false. My hope is it will stop if a new beacon is created on the same
|
||||
-- frequency... OR they fix the bug where it wont stop.
|
||||
-- end
|
||||
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())
|
||||
|
||||
--
|
||||
-- restart it as the battery is still up
|
||||
-- the transmission is set to loop and has the name of the transmitting DCS group (that includes the type - i.e. FM, UHF, VHF)
|
||||
trigger.action.radioTransmission(_sound, _radio.group:getUnit(1):getPoint(), _radio.mode, true, _radio.freq, 1000, _radio.group:getName())
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
-- trigger.action.radioTransmission(ctld.radioSound, _point, 1, true, _frequency, 1000)
|
||||
end
|
||||
|
||||
function ctld.listRadioBeacons(_args)
|
||||
@@ -3674,12 +3692,18 @@ 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
|
||||
|
||||
@@ -4172,15 +4196,6 @@ function ctld.spawnCrateGroup(_heli, _positions, _types)
|
||||
|
||||
local _spawnedGroup = Group.getByName(mist.dynAdd(_group).name)
|
||||
|
||||
--local _spawnedGroup = coalition.addGroup(_heli:getCountry(), Group.Category.GROUND, _group)
|
||||
|
||||
--activate by moving and so we can set ROE and Alarm state
|
||||
|
||||
local _dest = _spawnedGroup:getUnit(1):getPoint()
|
||||
_dest = { x = _dest.x + 0.5, _y = _dest.y + 0.5, z = _dest.z + 0.5 }
|
||||
|
||||
ctld.orderGroupToMoveToPoint(_spawnedGroup:getUnit(1), _dest)
|
||||
|
||||
return _spawnedGroup
|
||||
end
|
||||
|
||||
@@ -4977,7 +4992,7 @@ function ctld.addF10MenuOptions()
|
||||
if (ctld.enabledFOBBuilding or ctld.enableCrates) and _unitActions.crates then
|
||||
|
||||
local _crateCommands = missionCommands.addSubMenuForGroup(_groupId, "CTLD Commands", _rootPath)
|
||||
if ctld.hoverPickup == false then
|
||||
if ctld.hoverPickup == false or ctld.loadCrateFromMenu == true then
|
||||
if ctld.slingLoad == false then
|
||||
missionCommands.addCommandForGroup(_groupId, "Load Nearby Crate", _crateCommands, ctld.loadNearbyCrate, _unitName )
|
||||
end
|
||||
@@ -5041,6 +5056,7 @@ function ctld.addF10MenuOptions()
|
||||
|
||||
|
||||
if ctld.JTAC_jtacStatusF10 then
|
||||
|
||||
-- get all BLUE players
|
||||
ctld.addJTACRadioCommand(2)
|
||||
|
||||
@@ -5090,17 +5106,115 @@ function ctld.addJTACRadioCommand(_side)
|
||||
local _groupId = ctld.getGroupId(_playerUnit)
|
||||
|
||||
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)
|
||||
missionCommands.addCommandForGroup(_groupId, "JTAC Status", nil, ctld.getJTACStatus, { _playerUnit:getName() })
|
||||
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
|
||||
|
||||
ctld.jtacLastRadioRefresh = time
|
||||
|
||||
--build the path to the CTLD JTAC menu
|
||||
local jtacCurrentPagePath = {[1]=ctld.jtacMenuName}
|
||||
--build the path for the NextPage submenu on the first page of the CTLD JTAC menu
|
||||
local NextPageText = "Next Page"
|
||||
local MainNextPagePath = {[1]=ctld.jtacMenuName, [2]=NextPageText}
|
||||
--remove it along with everything that's in it
|
||||
missionCommands.removeItemForGroup(_groupId, MainNextPagePath)
|
||||
|
||||
--counter to know when to add the next page submenu to fit all of the JTAC group submenus
|
||||
local jtacCounter = 0
|
||||
|
||||
for _jtacGroupName,jtacUnit in pairs(ctld.jtacUnits) do
|
||||
|
||||
local jtacCoalition = ctld.jtacUnits[_jtacGroupName].side
|
||||
--if the JTAC is on the same team as the group being considered
|
||||
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("jtacTargetsList for %s is : %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacTargetsList[_jtacGroupName])))
|
||||
|
||||
if #ctld.jtacTargetsList[_jtacGroupName] > 1 then
|
||||
|
||||
local jtacGroupSubMenuName = string.format(_jtacGroupName .. " TGT Selection")
|
||||
|
||||
jtacCounter = jtacCounter + 1
|
||||
--F2 through F10 makes 9 entries possible per page, with one being the NextMenu submenu
|
||||
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])))
|
||||
|
||||
--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])
|
||||
--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
|
||||
|
||||
--indicator table to know which unitType was already added to the radio submenu
|
||||
local typeNameList = {}
|
||||
for _,target in pairs(ctld.jtacTargetsList[_jtacGroupName]) do
|
||||
local targetName = target.unit:getName()
|
||||
--check if the jtac has a current target before filtering it out if possible
|
||||
if (ctld.jtacCurrentTargets[_jtacGroupName] and targetName ~= ctld.jtacCurrentTargets[_jtacGroupName].name) then
|
||||
local targetType_name = target.unit:getTypeName()
|
||||
|
||||
if targetType_name then
|
||||
if typeNameList[targetType_name] then
|
||||
typeNameList[targetType_name].amount = typeNameList[targetType_name].amount + 1
|
||||
else
|
||||
typeNameList[targetType_name] = {}
|
||||
typeNameList[targetType_name].targetName = targetName --store the first targetName
|
||||
typeNameList[targetType_name].amount = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for typeName,info in pairs(typeNameList) do
|
||||
local amount = info.amount
|
||||
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
|
||||
jtacTargetPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacTargetPagePath)
|
||||
end
|
||||
|
||||
missionCommands.addCommandForGroup(_groupId, string.format(typeName .. "(" .. amount .. ")"), jtacTargetPagePath, ctld.setJTACTarget, {jtacGroupName = _jtacGroupName, targetName = targetName})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
if ctld.newJtac[_side] then
|
||||
ctld.newJtac[_side] = false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5131,14 +5245,20 @@ end
|
||||
|
||||
------------ JTAC -----------
|
||||
|
||||
|
||||
ctld.jtacMenuName = "JTAC" --name of the CTLD JTAC radio menu
|
||||
ctld.jtacLaserPoints = {}
|
||||
ctld.jtacIRPoints = {}
|
||||
ctld.jtacSmokeMarks = {}
|
||||
ctld.jtacUnits = {} -- list of JTAC units for f10 command
|
||||
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.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.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.jtacGeneratedLaserCodes = {} -- keeps track of generated codes, cycles when they run out
|
||||
ctld.jtacLaserPointCodes = {}
|
||||
ctld.jtacRadioData = {}
|
||||
@@ -5175,11 +5295,9 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
|
||||
end
|
||||
|
||||
if _lock == nil then
|
||||
|
||||
_lock = ctld.JTAC_lock
|
||||
end
|
||||
|
||||
|
||||
ctld.jtacLaserPointCodes[_jtacGroupName] = _laserCode
|
||||
ctld.jtacRadioData[_jtacGroupName] = _radio
|
||||
|
||||
@@ -5214,22 +5332,30 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if ctld.jtacUnits[_jtacGroupName] ~= nil then
|
||||
ctld.notifyCoalition("JTAC Group " .. _jtacGroupName .. " KIA!", 10, ctld.jtacUnits[_jtacGroupName].side, _radio)
|
||||
end
|
||||
|
||||
--remove from list
|
||||
ctld.jtacUnits[_jtacGroupName] = nil
|
||||
|
||||
ctld.cleanupJTAC(_jtacGroupName)
|
||||
|
||||
return
|
||||
else
|
||||
|
||||
_jtacUnit = _jtacGroup[1]
|
||||
local _jtacCoalition = _jtacUnit:getCoalition()
|
||||
--add to list
|
||||
ctld.jtacUnits[_jtacGroupName] = { name = _jtacUnit:getName(), side = _jtacUnit:getCoalition(), radio = _radio }
|
||||
ctld.jtacUnits[_jtacGroupName] = { name = _jtacUnit:getName(), side = _jtacCoalition, radio = _radio }
|
||||
|
||||
--Targets list and Selected target initialization
|
||||
if not ctld.jtacTargetsList[_jtacGroupName] then
|
||||
ctld.jtacTargetsList[_jtacGroupName] = {}
|
||||
if _jtacCoalition then ctld.newJtac[_jtacCoalition] = true end
|
||||
end
|
||||
|
||||
if not ctld.jtacSelectedTarget[_jtacGroupName] then
|
||||
ctld.jtacSelectedTarget[_jtacGroupName] = 1
|
||||
end
|
||||
|
||||
-- work out smoke colour
|
||||
if _colour == nil then
|
||||
@@ -5266,8 +5392,32 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
|
||||
end
|
||||
|
||||
local _enemyUnit = ctld.getCurrentUnit(_jtacUnit, _jtacGroupName)
|
||||
--update targets list and store the next potential target if the selected one was lost
|
||||
local _defaultEnemyUnit = ctld.findNearestVisibleEnemy(_jtacUnit, _lock)
|
||||
|
||||
-- if the JTAC sees a unit and a target was selected by users but is not the current unit, check if the selected target is in the targets list, if it is, then it's been reacquired
|
||||
if _enemyUnit and ctld.jtacSelectedTarget[_jtacGroupName] ~= 1 and ctld.jtacSelectedTarget[_jtacGroupName] ~= _enemyUnit:getName() then
|
||||
for _,target in pairs(ctld.jtacTargetsList[_jtacGroupName]) do
|
||||
if target then
|
||||
local targetUnit = target.unit
|
||||
local targetName = targetUnit:getName()
|
||||
|
||||
if ctld.jtacSelectedTarget[_jtacGroupName] == targetName then
|
||||
|
||||
ctld.jtacCurrentTargets[_jtacGroupName] = { name = targetName, unitType = targetUnit:getTypeName(), unitId = targetUnit:getID() }
|
||||
_enemyUnit = targetUnit
|
||||
|
||||
local message = _jtacGroupName .. ", selected target reacquired, " .. _enemyUnit:getTypeName()
|
||||
local fullMessage = message .. '. CODE: ' .. _laserCode .. ". POSITION: " .. ctld.getPositionString(_enemyUnit)
|
||||
ctld.notifyCoalition(fullMessage, 10, _jtacUnit:getCoalition(), _radio, message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local targetDestroyed = false
|
||||
local targetLost = false
|
||||
local wasSelected = false
|
||||
|
||||
if _enemyUnit == nil and ctld.jtacCurrentTargets[_jtacGroupName] ~= nil then
|
||||
|
||||
@@ -5277,17 +5427,20 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
|
||||
|
||||
local _tempUnit = Unit.getByName(_tempUnitInfo.name)
|
||||
|
||||
wasSelected = (ctld.jtacCurrentTargets[_jtacGroupName].name == ctld.jtacSelectedTarget[_jtacGroupName])
|
||||
|
||||
if _tempUnit ~= nil and _tempUnit:getLife() > 0 and _tempUnit:isActive() == true then
|
||||
targetLost = true
|
||||
else
|
||||
targetDestroyed = true
|
||||
ctld.jtacSelectedTarget[_jtacGroupName] = 1
|
||||
end
|
||||
|
||||
--remove from smoke list
|
||||
ctld.jtacSmokeMarks[_tempUnitInfo.name] = nil
|
||||
|
||||
-- JTAC Unit: resume his route ------------
|
||||
trigger.action.groupContinueMoving(Group.getByName(_jtacGroupName))
|
||||
-- JTAC Unit: resume his route ------------
|
||||
trigger.action.groupContinueMoving(Group.getByName(_jtacGroupName))
|
||||
|
||||
-- remove from target list
|
||||
ctld.jtacCurrentTargets[_jtacGroupName] = nil
|
||||
@@ -5298,44 +5451,75 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
|
||||
|
||||
|
||||
if _enemyUnit == nil then
|
||||
_enemyUnit = ctld.findNearestVisibleEnemy(_jtacUnit, _lock)
|
||||
|
||||
if _enemyUnit ~= nil then
|
||||
if _defaultEnemyUnit ~= nil then
|
||||
|
||||
-- store current target for easy lookup
|
||||
ctld.jtacCurrentTargets[_jtacGroupName] = { name = _enemyUnit:getName(), unitType = _enemyUnit:getTypeName(), unitId = _enemyUnit:getID() }
|
||||
local action = ", lasing new target, "
|
||||
ctld.jtacCurrentTargets[_jtacGroupName] = { name = _defaultEnemyUnit:getName(), unitType = _defaultEnemyUnit:getTypeName(), unitId = _defaultEnemyUnit:getID() }
|
||||
|
||||
local action = "lasing new target, "
|
||||
|
||||
if wasSelected and targetLost then
|
||||
action = ", temporarily " .. action
|
||||
else
|
||||
action = ", " .. action
|
||||
end
|
||||
|
||||
if targetLost then
|
||||
action = ", target lost " .. action
|
||||
targetLost = false
|
||||
action = "target lost" .. action
|
||||
elseif targetDestroyed then
|
||||
action = ", target destroyed " .. action
|
||||
targetDestroyed = false
|
||||
action = "target destroyed" .. action
|
||||
end
|
||||
|
||||
local message = _jtacGroupName .. action .. _enemyUnit:getTypeName()
|
||||
local fullMessage = message .. '. CODE: ' .. _laserCode .. ". POSITION: " .. ctld.getPositionString(_enemyUnit)
|
||||
if wasSelected then
|
||||
action = ", selected " .. action
|
||||
elseif targetLost or targetDestroyed then
|
||||
action = ", " .. action
|
||||
end
|
||||
wasSelected = false
|
||||
targetDestroyed = false
|
||||
targetLost = false
|
||||
|
||||
local message = _jtacGroupName .. action .. _defaultEnemyUnit:getTypeName()
|
||||
local fullMessage = message .. '. CODE: ' .. _laserCode .. ". POSITION: " .. ctld.getPositionString(_defaultEnemyUnit)
|
||||
ctld.notifyCoalition(fullMessage, 10, _jtacUnit:getCoalition(), _radio, message)
|
||||
|
||||
-- JTAC Unit stop his route -----------------
|
||||
trigger.action.groupStopMoving(Group.getByName(_jtacGroupName)) -- stop JTAC
|
||||
-- JTAC Unit stop his route -----------------
|
||||
trigger.action.groupStopMoving(Group.getByName(_jtacGroupName)) -- stop JTAC
|
||||
|
||||
-- create smoke
|
||||
if _smoke == true then
|
||||
|
||||
--create first smoke
|
||||
ctld.createSmokeMarker(_enemyUnit, _colour)
|
||||
ctld.createSmokeMarker(_defaultEnemyUnit, _colour)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if _enemyUnit ~= nil 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.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
|
||||
|
||||
-- env.info('Timer timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName())
|
||||
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 15)
|
||||
--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)))
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
-- env.info('Timer timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName())
|
||||
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + refreshDelay)
|
||||
|
||||
if _smoke == true then
|
||||
local _nextSmokeTime = ctld.jtacSmokeMarks[_enemyUnit:getName()]
|
||||
@@ -5357,10 +5541,15 @@ function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _
|
||||
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 5)
|
||||
end
|
||||
|
||||
local action = ", "
|
||||
if wasSelected then
|
||||
action = action .. "selected "
|
||||
end
|
||||
|
||||
if targetLost then
|
||||
ctld.notifyCoalition(_jtacGroupName .. ", target lost.", 10, _jtacUnit:getCoalition(), _radio)
|
||||
ctld.notifyCoalition(_jtacGroupName .. action .. "target lost.", 10, _jtacUnit:getCoalition(), _radio)
|
||||
elseif targetDestroyed then
|
||||
ctld.notifyCoalition(_jtacGroupName .. ", target destroyed.", 10, _jtacUnit:getCoalition(), _radio)
|
||||
ctld.notifyCoalition(_jtacGroupName .. action .. "target destroyed.", 10, _jtacUnit:getCoalition(), _radio)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5379,11 +5568,34 @@ function ctld.cleanupJTAC(_jtacGroupName)
|
||||
ctld.cancelLase(_jtacGroupName)
|
||||
|
||||
-- Cleanup
|
||||
ctld.jtacUnits[_jtacGroupName] = nil
|
||||
|
||||
ctld.jtacCurrentTargets[_jtacGroupName] = nil
|
||||
|
||||
ctld.jtacTargetsList[_jtacGroupName] = nil
|
||||
|
||||
ctld.jtacSelectedTarget[_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
|
||||
if ctld.jtacUnits[_jtacGroupName] and ctld.jtacUnits[_jtacGroupName].side and ctld.jtacGroupSubMenuPath[_jtacGroupName] then
|
||||
local _players = coalition.getPlayers(ctld.jtacUnits[_jtacGroupName].side)
|
||||
|
||||
if _players ~= nil then
|
||||
|
||||
for _, _playerUnit in pairs(_players) do
|
||||
|
||||
local _groupId = ctld.getGroupId(_playerUnit)
|
||||
|
||||
if _groupId then
|
||||
missionCommands.removeItemForGroup(_groupId, ctld.jtacGroupSubMenuPath[_jtacGroupName])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ctld.jtacUnits[_jtacGroupName] = nil
|
||||
|
||||
ctld.jtacGroupSubMenuPath[_jtacGroupName] = nil
|
||||
end
|
||||
|
||||
|
||||
@@ -5430,7 +5642,7 @@ function ctld.createSmokeMarker(_enemyUnit, _colour)
|
||||
|
||||
-- move smoke 2 meters above target for ease
|
||||
local _enemyPoint = _enemyUnit:getPoint()
|
||||
trigger.action.smoke({ x = _enemyPoint.x, y = _enemyPoint.y + 2.0, z = _enemyPoint.z }, _colour)
|
||||
trigger.action.smoke({ x = _enemyPoint.x + ctld.JTAC_smokeOffset_x, y = _enemyPoint.y + ctld.JTAC_smokeOffset_y, z = _enemyPoint.z + ctld.JTAC_smokeOffset_z }, _colour)
|
||||
end
|
||||
|
||||
function ctld.cancelLase(_jtacGroupName)
|
||||
@@ -5460,63 +5672,100 @@ function ctld.cancelLase(_jtacGroupName)
|
||||
end
|
||||
end
|
||||
|
||||
-- used by the timer function
|
||||
function ctld.timerLaseUnit(_args)
|
||||
|
||||
ctld.laseUnit(_args[1], _args[2], _args[3], _args[4])
|
||||
end
|
||||
|
||||
function ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
|
||||
|
||||
--cancelLase(jtacGroupName)
|
||||
ctld.logTrace("ctld.laseUnit()")
|
||||
|
||||
local _spots = {}
|
||||
|
||||
local _enemyVector = _enemyUnit:getPoint()
|
||||
local _enemyVectorUpdated = { x = _enemyVector.x, y = _enemyVector.y + 2.0, z = _enemyVector.z }
|
||||
if _enemyUnit:isExist() then
|
||||
local _enemyVector = _enemyUnit:getPoint()
|
||||
local _enemyVectorUpdated = { x = _enemyVector.x, y = _enemyVector.y + 2.0, z = _enemyVector.z }
|
||||
|
||||
local _oldLase = ctld.jtacLaserPoints[_jtacGroupName]
|
||||
local _oldIR = ctld.jtacIRPoints[_jtacGroupName]
|
||||
if ctld.JTAC_laseSpotCorrections then
|
||||
local _enemySpeedVector = _enemyUnit:getVelocity()
|
||||
ctld.logTrace(string.format("_enemySpeedVector=%s", ctld.p(_enemySpeedVector)))
|
||||
|
||||
if _oldLase == nil or _oldIR == nil then
|
||||
local _WindSpeedVector = atmosphere.getWind(_enemyVectorUpdated)
|
||||
ctld.logTrace(string.format("_WindSpeedVector=%s", ctld.p(_WindSpeedVector)))
|
||||
|
||||
--if target speed is greater than 0, calculated using absolute value norm
|
||||
if math.abs(_enemySpeedVector.x) + math.abs(_enemySpeedVector.y) + math.abs(_enemySpeedVector.z) > 0 then
|
||||
local CorrectionFactor = 1 --correction factor in seconds applied to the target speed components to determine the lasing spot for a direct hit on a moving vehicle
|
||||
|
||||
-- create lase
|
||||
--correct in the direction of the movement
|
||||
_enemyVectorUpdated.x = _enemyVectorUpdated.x + _enemySpeedVector.x * CorrectionFactor
|
||||
_enemyVectorUpdated.y = _enemyVectorUpdated.y + _enemySpeedVector.y * CorrectionFactor
|
||||
_enemyVectorUpdated.z = _enemyVectorUpdated.z + _enemySpeedVector.z * CorrectionFactor
|
||||
end
|
||||
|
||||
local _status, _result = pcall(function()
|
||||
_spots['irPoint'] = Spot.createInfraRed(_jtacUnit, { x = 0, y = 2.0, z = 0 }, _enemyVectorUpdated)
|
||||
_spots['laserPoint'] = Spot.createLaser(_jtacUnit, { x = 0, y = 2.0, z = 0 }, _enemyVectorUpdated, _laserCode)
|
||||
return _spots
|
||||
end)
|
||||
--if wind speed is greater than 0, calculated using absolute value norm
|
||||
if math.abs(_WindSpeedVector.x) + math.abs(_WindSpeedVector.y) + math.abs(_WindSpeedVector.z) > 0 then
|
||||
local CorrectionFactor = 1.05 --correction factor in seconds applied to the wind speed components to determine the lasing spot for a direct hit in adverse conditions
|
||||
|
||||
--correct to the opposite of the wind direction
|
||||
_enemyVectorUpdated.x = _enemyVectorUpdated.x - _WindSpeedVector.x * CorrectionFactor
|
||||
_enemyVectorUpdated.y = _enemyVectorUpdated.y - _WindSpeedVector.y * CorrectionFactor --not sure about correcting altitude but that component is always 0 in testing
|
||||
_enemyVectorUpdated.z = _enemyVectorUpdated.z - _WindSpeedVector.z * CorrectionFactor
|
||||
end
|
||||
--combination of both should result in near perfect accuracy if the bomb doesn't stall itself following fast vehicles or correcting for heavy winds, correction factors can be adjusted but should work up to 40kn of wind for vehicles moving at 90kph (beware to drop the bomb in a way to not stall it, facing which ever is larger, target speed or wind)
|
||||
end
|
||||
|
||||
local _oldLase = ctld.jtacLaserPoints[_jtacGroupName]
|
||||
local _oldIR = ctld.jtacIRPoints[_jtacGroupName]
|
||||
|
||||
if _oldLase == nil or _oldIR == nil then
|
||||
|
||||
-- create lase
|
||||
|
||||
local _status, _result = pcall(function()
|
||||
_spots['irPoint'] = Spot.createInfraRed(_jtacUnit, { x = 0, y = 2.0, z = 0 }, _enemyVectorUpdated)
|
||||
_spots['laserPoint'] = Spot.createLaser(_jtacUnit, { x = 0, y = 2.0, z = 0 }, _enemyVectorUpdated, _laserCode)
|
||||
return _spots
|
||||
end)
|
||||
|
||||
if not _status then
|
||||
env.error('ERROR: ' .. _result, false)
|
||||
else
|
||||
if _result.irPoint then
|
||||
|
||||
-- env.info(jtacUnit:getName() .. ' placed IR Pointer on '..enemyUnit:getName())
|
||||
|
||||
ctld.jtacIRPoints[_jtacGroupName] = _result.irPoint --store so we can remove after
|
||||
end
|
||||
if _result.laserPoint then
|
||||
|
||||
-- env.info(jtacUnit:getName() .. ' is Lasing '..enemyUnit:getName()..'. CODE:'..laserCode)
|
||||
|
||||
ctld.jtacLaserPoints[_jtacGroupName] = _result.laserPoint
|
||||
end
|
||||
end
|
||||
|
||||
if not _status then
|
||||
env.error('ERROR: ' .. _result, false)
|
||||
else
|
||||
if _result.irPoint then
|
||||
|
||||
-- env.info(jtacUnit:getName() .. ' placed IR Pointer on '..enemyUnit:getName())
|
||||
-- update lase
|
||||
|
||||
ctld.jtacIRPoints[_jtacGroupName] = _result.irPoint --store so we can remove after
|
||||
if _oldLase ~= nil then
|
||||
_oldLase:setPoint(_enemyVectorUpdated)
|
||||
end
|
||||
if _result.laserPoint then
|
||||
|
||||
-- env.info(jtacUnit:getName() .. ' is Lasing '..enemyUnit:getName()..'. CODE:'..laserCode)
|
||||
|
||||
ctld.jtacLaserPoints[_jtacGroupName] = _result.laserPoint
|
||||
if _oldIR ~= nil then
|
||||
_oldIR:setPoint(_enemyVectorUpdated)
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- update lase
|
||||
|
||||
if _oldLase ~= nil then
|
||||
_oldLase:setPoint(_enemyVectorUpdated)
|
||||
end
|
||||
|
||||
if _oldIR ~= nil then
|
||||
_oldIR:setPoint(_enemyVectorUpdated)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- get currently selected unit and check they're still in range
|
||||
function ctld.getCurrentUnit(_jtacUnit, _jtacGroupName)
|
||||
|
||||
|
||||
local _unit = nil
|
||||
|
||||
if ctld.jtacCurrentTargets[_jtacGroupName] ~= nil then
|
||||
@@ -5562,6 +5811,7 @@ function ctld.findNearestVisibleEnemy(_jtacUnit, _targetType,_distance)
|
||||
|
||||
local _nearestDistance = _maxDistance
|
||||
|
||||
local _jtacGroupName = _jtacUnit:getName()
|
||||
local _jtacPoint = _jtacUnit:getPoint()
|
||||
local _coa = _jtacUnit:getCoalition()
|
||||
|
||||
@@ -5618,6 +5868,10 @@ function ctld.findNearestVisibleEnemy(_jtacUnit, _targetType,_distance)
|
||||
-- vehicle
|
||||
-- unit
|
||||
|
||||
|
||||
ctld.jtacTargetsList[_jtacGroupName] = _unitList
|
||||
--from the units in range, build the targets list, unsorted as to keep consistency between radio menu refreshes
|
||||
|
||||
local _sort = function( a,b ) return a.dist < b.dist end
|
||||
table.sort(_unitList,_sort)
|
||||
-- sort list
|
||||
@@ -5800,7 +6054,7 @@ function ctld.getJTACStatus(_args)
|
||||
|
||||
local _laserCode = ctld.jtacLaserPointCodes[_jtacGroupName]
|
||||
|
||||
local _start = _jtacGroupName
|
||||
local _start = "->" .. _jtacGroupName
|
||||
if (_jtacDetails.radio) then
|
||||
_start = _start .. ", available on ".._jtacDetails.radio.freq.." ".._jtacDetails.radio.mod ..","
|
||||
end
|
||||
@@ -5810,7 +6064,18 @@ function ctld.getJTACStatus(_args)
|
||||
end
|
||||
|
||||
if _enemyUnit ~= nil and _enemyUnit:getLife() > 0 and _enemyUnit:isActive() == true then
|
||||
_message = _message .. "" .. _start .. " targeting " .. _enemyUnit:getTypeName() .. " CODE: " .. _laserCode .. ctld.getPositionString(_enemyUnit) .. "\n"
|
||||
|
||||
local action = " targeting "
|
||||
|
||||
if ctld.jtacSelectedTarget[_jtacGroupName] == _enemyUnit:getName() then
|
||||
action = " targeting selected unit "
|
||||
else
|
||||
if ctld.jtacSelectedTarget[_jtacGroupName] ~= 1 then
|
||||
action = " attempting to find selected unit, temporarily targeting "
|
||||
end
|
||||
end
|
||||
|
||||
_message = _message .. "" .. _start .. action .. _enemyUnit:getTypeName() .. " CODE: " .. _laserCode .. ctld.getPositionString(_enemyUnit) .. "\n"
|
||||
|
||||
local _list = ctld.listNearbyEnemies(_jtacUnit)
|
||||
|
||||
@@ -5837,7 +6102,41 @@ function ctld.getJTACStatus(_args)
|
||||
ctld.notifyCoalition(_message, 10, _side)
|
||||
end
|
||||
|
||||
function ctld.setJTACTarget(_args)
|
||||
if _args then
|
||||
local _jtacGroupName = _args.jtacGroupName
|
||||
local targetName = _args.targetName
|
||||
|
||||
if _jtacGroupName and targetName and ctld.jtacSelectedTarget[_jtacGroupName] and ctld.jtacTargetsList[_jtacGroupName] then
|
||||
|
||||
--look for the unit's (target) name in the Targets List, create the required data structure for jtacCurrentTargets and then assign it to the JTAC called _jtacGroupName
|
||||
for _, target in pairs(ctld.jtacTargetsList[_jtacGroupName]) do
|
||||
|
||||
if target then
|
||||
|
||||
local ListedTargetUnit = target.unit
|
||||
local ListedTargetName = ListedTargetUnit:getName()
|
||||
|
||||
if ListedTargetName == targetName then
|
||||
|
||||
ctld.jtacSelectedTarget[_jtacGroupName] = targetName
|
||||
ctld.jtacCurrentTargets[_jtacGroupName] = { name = targetName, unitType = ListedTargetUnit:getTypeName(), unitId = ListedTargetUnit:getID() }
|
||||
|
||||
local message = _jtacGroupName .. ", targeting selected unit, " .. ListedTargetUnit:getTypeName()
|
||||
local fullMessage = message .. '. CODE: ' .. ctld.jtacLaserPointCodes[_jtacGroupName] .. ". POSITION: " .. ctld.getPositionString(ListedTargetUnit)
|
||||
ctld.notifyCoalition(fullMessage, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName], message)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif not targetName and ctld.jtacSelectedTarget[_jtacGroupName] ~= 1 then
|
||||
ctld.jtacSelectedTarget[_jtacGroupName] = 1
|
||||
ctld.jtacCurrentTargets[_jtacGroupName] = nil
|
||||
|
||||
local message = _jtacGroupName .. ", target selection reset."
|
||||
ctld.notifyCoalition(message, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ctld.isInfantry(_unit)
|
||||
|
||||
@@ -5877,7 +6176,7 @@ function ctld.generateLaserCode()
|
||||
ctld.jtacGeneratedLaserCodes = {}
|
||||
|
||||
-- generate list of laser codes
|
||||
local _code = 1111
|
||||
local _code = 1511
|
||||
|
||||
local _count = 1
|
||||
|
||||
@@ -6092,7 +6391,6 @@ function ctld.getPositionString(_unit)
|
||||
return " @ " .. _latLngStr .. " - MGRS " .. _mgrsString
|
||||
end
|
||||
|
||||
|
||||
-- ***************** SETUP SCRIPT ****************
|
||||
function ctld.initialize(force)
|
||||
ctld.logInfo(string.format("Initializing version %s", ctld.Version))
|
||||
|
||||
12
README.md
12
README.md
@@ -130,10 +130,12 @@ To use the real cargo sling behaviour, set the ```ctld.slingLoad``` option to ``
|
||||
-- ************************************************************************
|
||||
ctld.staticBugFix = true -- When statics are destroyed, DCS Crashes. Set this to FALSE when this bug is fixed or if you want to use REAL sling loads :)
|
||||
|
||||
ctld.disableAllSmoke = false -- if true, all smoke is diabled at pickup and drop off zones regardless of settings below. Leave false to respect settings below
|
||||
ctld.disableAllSmoke = false -- if true, all smoke is disabled at pickup and drop off zones regardless of settings below. Leave false to respect settings below
|
||||
|
||||
ctld.hoverPickup = true -- if set to false you can load crates with the F10 menu instead of hovering...!
|
||||
|
||||
ctld.loadCrateFromMenu = false -- if set to true, you can load crates with the F10 menu OR hovering, in case of using choppers and planes for example.
|
||||
|
||||
ctld.enableCrates = true -- if false, Helis will not be able to spawn or unpack crates so will be normal CTTS
|
||||
ctld.slingLoad = false -- if false, crates can be used WITHOUT slingloading, by hovering above the crate, simulating slingloading but not the weight...
|
||||
-- There are some bug with Sling-loading that can cause crashes, if these occur set slingLoad to false
|
||||
@@ -667,6 +669,10 @@ ctld.JTAC_smokeOn_BLUE = true -- enables marking of target with smoke for BLUE f
|
||||
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_jtacStatusF10 = false -- enables F10 JTAC Status menu
|
||||
|
||||
ctld.JTAC_location = false -- shows location of target in JTAC message
|
||||
@@ -687,6 +693,8 @@ The JTACs can be configured globally to target only vehicles or troops or all gr
|
||||
|
||||
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 smoke will be offset from the target by the distances declared in the `ctld.JTAC_smokeOffset_*` constants.
|
||||
|
||||
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
|
||||
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.
|
||||
@@ -839,7 +847,7 @@ Once you've loaded the crate, fly to where you want to drop it and drop using th
|
||||
|
||||
Once on the ground unpack as normal using the CTLD Commands Menu - CTLD->CTLD Commands->Unpack Crate
|
||||
|
||||
**Note: You can also set ```ctld.hoverPickup = false``` so you can load crates using the F10 menu instead of Hovering. **
|
||||
**Note: You can also set ```ctld.hoverPickup = false``` so you can load crates using the F10 menu instead of Hovering; or keep ```ctld.hoverPickup = true``` and set ```ctld.loadCrateFromMenu = true``` so you can load the crates by hovering OR from the F10 menu**
|
||||
|
||||
### Real Sling Loading
|
||||
|
||||
|
||||
BIN
test-mission.miz
BIN
test-mission.miz
Binary file not shown.
Reference in New Issue
Block a user