Version 2.4.1

jtacGrpUI, lase code and CA integration.
Fogger supports local alt
This commit is contained in:
Christian Franz 2025-01-16 07:57:55 +01:00
parent 2274ba930d
commit 4e78dfcb65
13 changed files with 26016 additions and 24528 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,11 +1,11 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "4.5.0" cfxZones.version = "4.5.1"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- reads dcs zones and makes them accessible and mutable
-- by scripting. -- by scripting.
-- --
-- Copyright (c) 2021 - 2024 by Christian Franz and cf/x AG -- Copyright (c) 2021 - 2025 by Christian Franz and cf/x AG
-- --
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@ -39,6 +39,7 @@ cfxZones.version = "4.5.0"
-4.5.0 - corrected bug in getBoolFromZoneProperty for default = false and "rnd" -4.5.0 - corrected bug in getBoolFromZoneProperty for default = false and "rnd"
- rnd in bool can have = xxx param for percentage - rnd in bool can have = xxx param for percentage
- getSmokeColorNumberFromZoneProperty() - getSmokeColorNumberFromZoneProperty()
-4.5.1 - moved processSimpleZoneDynamics to common
--]]-- --]]--
@ -2934,6 +2935,9 @@ end
-- process <t>, <lat>, <lon>, <ele>, <mgrs> -- process <t>, <lat>, <lon>, <ele>, <mgrs>
function cfxZones.processSimpleZoneDynamics(inMsg, theZone, timeFormat, imperialUnits) function cfxZones.processSimpleZoneDynamics(inMsg, theZone, timeFormat, imperialUnits)
local p = theZone:getPoint()
return dcsCommon.processTimeLocWildCards(inMsg, p, timeFormat, imperialUnits)
--[[--
if not inMsg then return "<nil inMsg>" end if not inMsg then return "<nil inMsg>" end
-- replace <t> with current mission time HMS -- replace <t> with current mission time HMS
local absSecs = timer.getAbsTime()-- + env.mission.start_time local absSecs = timer.getAbsTime()-- + env.mission.start_time
@ -2962,6 +2966,7 @@ function cfxZones.processSimpleZoneDynamics(inMsg, theZone, timeFormat, imperial
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
outMsg = outMsg:gsub("<mgrs>", mgrs) outMsg = outMsg:gsub("<mgrs>", mgrs)
return outMsg return outMsg
--]]--
end end
-- process <v: flag>, <rsp: flag> <rrnd> -- process <v: flag>, <rsp: flag> <rrnd>

View File

@ -1,5 +1,5 @@
cloneZones = {} cloneZones = {}
cloneZones.version = "2.5.0" cloneZones.version = "2.5.2"
cloneZones.verbose = false cloneZones.verbose = false
cloneZones.requiredLibs = { cloneZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -60,6 +60,7 @@ cloneZones.respawnOnGroupID = true
2.5.0 - re-establish spawn zone in persistence to provide 2.5.0 - re-establish spawn zone in persistence to provide
empty! detection through saves (missed hasClones) empty! detection through saves (missed hasClones)
2.5.1 - f? and in? put on notice for depreciation 2.5.1 - f? and in? put on notice for depreciation
2.5.2 - removed bug when checking damaged! and no units cloned
--]]-- --]]--
-- --
@ -1715,9 +1716,10 @@ function cloneZones.update()
end end
-- handling of damaged! and #health -- handling of damaged! and #health
if aZone.damaged or aZone.health then if aZone.hasClones and (aZone.damaged or aZone.health) then
-- calculate current health -- calculate current health
local currSize = cloneZones.countLiveAIUnits(aZone) local currSize = cloneZones.countLiveAIUnits(aZone)
if not aZone.oSize then aZone.oSize = 0 end
if aZone.oSize < 1 then if aZone.oSize < 1 then
if aZone.verbose or cloneZones.verbose then if aZone.verbose or cloneZones.verbose then
trigger.action.outText("+++clnZ: Warning: zero oZize for cloner <" .. aZone.name .. ">, no health info, no damage alert", 30) trigger.action.outText("+++clnZ: Warning: zero oZize for cloner <" .. aZone.name .. ">, no health info, no damage alert", 30)

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "3.1.5" dcsCommon.version = "3.2.0"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false 3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option - point2text new intsOnly option
@ -34,12 +34,16 @@ dcsCommon.version = "3.1.5"
3.1.4 - new processStringWildcardsForUnit 3.1.4 - new processStringWildcardsForUnit
- integrated into std wildcard proccing, unit optional - integrated into std wildcard proccing, unit optional
3.1.5 - more verbosity on unitID2X 3.1.5 - more verbosity on unitID2X
3.2.0 - support for twn in processStringWildcardsForUnit()
- new processTimeLocWildCards()
- cfxZones links to processTimeLocWildCards
- new processAtoBWildCards()
- tons of new wildcards like <eleft>
--]]-- --]]--
-- dcsCommon is a library of common lua functions -- dcsCommon is a library of common lua functions
-- for easy access and simple mission programming -- for easy access and simple mission programming
-- (c) 2021 - 2024 by Christian Franz and cf/x AG -- (c) 2021 - 2025 by Christian Franz and cf/x AG
-- --
-- DCS API PATCHES FOR DCS-INTERNAL BUGS -- DCS API PATCHES FOR DCS-INTERNAL BUGS
@ -3396,7 +3400,7 @@ function dcsCommon.LSR(a, num)
end end
-- --
-- string wildcards -- string wildcard processing
-- --
function dcsCommon.processStringWildcards(inMsg, theUnit) function dcsCommon.processStringWildcards(inMsg, theUnit)
-- Replace STATIC bits of message like CR and zone name -- Replace STATIC bits of message like CR and zone name
@ -3414,18 +3418,17 @@ function dcsCommon.processStringWildcards(inMsg, theUnit)
return outMsg return outMsg
end end
-- <u>, <p>, <g>, <typ>, <c>, <e>, <twn>, <twnkm>, <twnnm>
function dcsCommon.processStringWildcardsForUnit(msg, theUnit) function dcsCommon.processStringWildcardsForUnit(msg, theUnit)
local uName = theUnit:getName() local uName = theUnit:getName()
msg = msg:gsub("<u>", uName) msg = msg:gsub("<u>", uName)
pName = "!AI!" pName = "AI"
if dcsCommon.isPlayerUnit(theUnit) then if dcsCommon.isPlayerUnit(theUnit) then
pName = theUnit:getPlayerName() pName = theUnit:getPlayerName()
else
return
end end
msg = msg:gsub("<p>", pName) msg = msg:gsub("<p>", pName)
msg = msg:gsub("<t>", theUnit:getTypeName()) msg = msg:gsub("<t>", theUnit:getTypeName()) -- WARNING! <t> is conflicted
msg = msg:gsub("<typ>", theUnit:getTypeName())
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
local gName = theGroup:getName() local gName = theGroup:getName()
msg = msg:gsub("<g>", gName) msg = msg:gsub("<g>", gName)
@ -3439,9 +3442,73 @@ function dcsCommon.processStringWildcardsForUnit(msg, theUnit)
e = e:lower() e = e:lower()
msg = msg:gsub("<c>", coa) msg = msg:gsub("<c>", coa)
msg = msg:gsub ("<e>", e) msg = msg:gsub ("<e>", e)
local locString = ""
local locStingnm = ""
local p = theUnit:getPoint()
if twn and towns then locString = " " .. twn.closestTownTo(p) end
msg = msg:gsub("<twn>", locString)
if twn and towns then
local name, data, dist = twn.closestTownTo(p)
local mdist= dist * 0.539957
dist = math.floor(dist/100) / 10
mdist = math.floor(mdist/100) / 10
local bear = dcsCommon.compassPositionOfARelativeToB(p, data.p)
locString = " " .. dist .. "km " .. bear .. " of " .. name
locStringnm = " " .. mdist .."nm " .. bear .. " of " .. name
end
msg = msg:gsub("<twnkm>", locString)
msg = msg:gsub("<twnnm>", locString)
return msg return msg
end end
-- process <tme>, <t>, <lat>, <lon>, <ele>, <mgrs>
function dcsCommon.processTimeLocWildCards(inMsg, p, timeFormat, imperialUnits)
if not inMsg then return "<nil inMsg tloc>" end
-- replace <t> with current mission time HMS
local absSecs = timer.getAbsTime()-- + env.mission.start_time
while absSecs > 86400 do
absSecs = absSecs - 86400 -- subtract out all days
end
if not timeFormat then timeFormat = "<:h>:<:m>:<:s>" end
local timeString = dcsCommon.processHMS(timeFormat, absSecs)
local outMsg = inMsg:gsub("<tme>", timeString)
local outMsg = inMsg:gsub("<t>", timeString) -- WARNING! <t> is conflicted
-- replace <lat> with lat and <lon> with lon of point
-- and <mgrs> with mgrs coords of point
local currPoint = p
local lat, lon = coord.LOtoLL(currPoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local alt = land.getHeight({x = currPoint.x, y = currPoint.z})
outMsg = outMsg:gsub("<elem>", alt)
local altft = math.floor(alt * 3.28084) -- feet
if imperialUnits then
alt = math.floor(alt * 3.28084) -- feet
else
alt = math.floor(alt) -- meters
end
outMsg = outMsg:gsub("<lat>", lat)
outMsg = outMsg:gsub("<lon>", lon)
outMsg = outMsg:gsub("<ele>", alt)
outMsg = outMsg:gsub("<eleft>", alt)
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
outMsg = outMsg:gsub("<mgrs>", mgrs)
return outMsg
end
-- process A to B: <rng>, <rngnm>, <bea>
function dcsCommon.processAtoBWildCards(inMsg, A, B)
if not inMsg then return "<nil inMsg AtB>" end
local outMsg = inMsg
local bea = dcsCommon.bearingInDegreesFromAtoB(A, B)
local range = dcsCommon.dist(A, B) / 1000 -- km
local rangenm = math.floor(range * 5.39957) / 10
range = math.floor(range * 10) / 10
outMsg = outMsg:gsub("<bea>", bea)
outMsg = outMsg:gsub("<rng>", range)
outMsg = outMsg:gsub("<rngnm>", rangenm)
return outMsg
end
-- --
-- phonetic alphabet -- phonetic alphabet
-- --

View File

@ -7,7 +7,7 @@ fogger.requiredLibs = {
fogger.zones = {} fogger.zones = {}
--[[-- Version history --[[-- Version history
A DML module (c) 2024 by Christian FRanz A DML module (c) 2024-25 by Christian FRanz
- 1.0.0 - Initial version - 1.0.0 - Initial version
- 1.1.0 - added lcl attribute - 1.1.0 - added lcl attribute
@ -27,18 +27,13 @@ function fogger.createFogZone(theZone)
theZone.lcl = theZone:getBoolFromZoneProperty("lcl", false) theZone.lcl = theZone:getBoolFromZoneProperty("lcl", false)
theZone.durMin, theZone.durMax = theZone:getPositiveRangeFromZoneProperty ("duration", 1, 1) theZone.durMin, theZone.durMax = theZone:getPositiveRangeFromZoneProperty ("duration", 1, 1)
if theZone:hasProperty("onStart") then if theZone:hasProperty("onStart") then
--trigger.action.outText("+++fog: zone <" .. theZone.name .. "> HAS 'onStart' attribute", 30)
theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false) theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false)
if theZone.onStart then if theZone.onStart then
if theZone.verbose then if theZone.verbose then
trigger.action.outText("+++fog: will schedule onStart fog in zone <" .. theZone.name .. ">", 30) trigger.action.outText("+++fog: will schedule onStart fog in zone <" .. theZone.name .. ">", 30)
end end
timer.scheduleFunction(fogger.doFog, theZone, timer.getTime() + 0.5) timer.scheduleFunction(fogger.doFog, theZone, timer.getTime() + 0.5)
else
--trigger.action.outText("+++ fog: onstart turned OFF", 30)
end end
else
--trigger.action.outText("+++fog: zone <" .. theZone.name .. "> no 'onStart' attribute, turned off", 30)
end end
if theZone.verbose then if theZone.verbose then
trigger.action.outText("+++fog: zone <" .. theZone.name .. "> processed.", 30) trigger.action.outText("+++fog: zone <" .. theZone.name .. "> processed.", 30)

View File

@ -1,5 +1,5 @@
cfxGroundTroops = {} cfxGroundTroops = {}
cfxGroundTroops.version = "2.2.1" cfxGroundTroops.version = "3.0.0"
cfxGroundTroops.ups = 0.25 -- every 4 seconds cfxGroundTroops.ups = 0.25 -- every 4 seconds
cfxGroundTroops.verbose = false cfxGroundTroops.verbose = false
cfxGroundTroops.requiredLibs = { cfxGroundTroops.requiredLibs = {
@ -26,15 +26,8 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
--[[-- --[[--
version history version history
3.0.0 - support for troop-individual laser codes
2.0.0 - dmlZones - support for isDrivable CA troop spawns
- jtacSound
- cleanup
- jtacVerbose
2.0.1 - small fiex ti checkPileUp()
2.1.0 - captureandhold - oneshot attackowned
2.2.0 - moveFormation support
2.2.1 - reduced verbosity
an entry into the deployed troop table has the following attributes an entry into the deployed troop table has the following attributes
- group - the group - group - the group
@ -55,8 +48,10 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
- signature - "cfx" to tell apart from dcs groups - signature - "cfx" to tell apart from dcs groups
- range = range to look for enemies. default is 300m. In "laze" orders, range to laze - range = range to look for enemies. default is 300m. In "laze" orders, range to laze
- lazeTarget - target currently lazing - lazeTarget - target currently lazing
- lazeCode - laser code. default is 1688 - ??lazeCode?? - laser code. default is 1688
- moving - has been given orders to move somewhere already. used for first movement order with attack orders - moving - has been given orders to move somewhere already. used for first movement order with attack orders
- code - laser code
- canDrive - for spawning if CA supported
-- reduced ups to 0.24, updating troops every 4 seconds is fast enough -- reduced ups to 0.24, updating troops every 4 seconds is fast enough
@ -469,11 +464,13 @@ function cfxGroundTroops.trackLazer(troop)
end end
if not troop.lazerPointer then if not troop.lazerPointer then
local code = troop.code
if not code then code = cfxGroundTroops.laseCode end -- default
local there = troop.lazeTarget:getPoint() local there = troop.lazeTarget:getPoint()
troop.lazerPointer = Spot.createLaser(troop.lazingUnit,{x = 0, y = 2, z = 0}, there, cfxGroundTroops.laseCode) troop.lazerPointer = Spot.createLaser(troop.lazingUnit,{x = 0, y = 2, z = 0}, there, code)
troop.lazeTargetType = troop.lazeTarget:getTypeName() troop.lazeTargetType = troop.lazeTarget:getTypeName()
if cfxGroundTroops.jtacVerbose then if cfxGroundTroops.jtacVerbose then
trigger.action.outTextForCoalition(troop.side, troop.name .. " tally target - lasing " .. troop.lazeTargetType .. ", code " .. cfxGroundTroops.laseCode .. "!", 30) trigger.action.outTextForCoalition(troop.side, troop.name .. " tally target - lasing " .. troop.lazeTargetType .. ", code " .. code .. "!", 30)
trigger.action.outSoundForCoalition(troop.side, cfxGroundTroops.jtacSound) -- "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav") trigger.action.outSoundForCoalition(troop.side, cfxGroundTroops.jtacSound) -- "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
end end
troop.lastLazerSpot = there -- remember last spot troop.lastLazerSpot = there -- remember last spot
@ -918,7 +915,7 @@ end
-- createGroundTroop -- createGroundTroop
-- use this to create a cfxGroundTroops from a dcs group -- use this to create a cfxGroundTroops from a dcs group
-- --
function cfxGroundTroops.createGroundTroops(inGroup, range, orders, moveFormation) function cfxGroundTroops.createGroundTroops(inGroup, range, orders, moveFormation, code, canDrive)
local newTroops = {} local newTroops = {}
if not orders then if not orders then
orders = "guard" orders = "guard"
@ -927,7 +924,6 @@ function cfxGroundTroops.createGroundTroops(inGroup, range, orders, moveFormatio
if orders:lower() == "lase" then if orders:lower() == "lase" then
orders = "laze" -- we use WRONG spelling here, cause we're cool. yeah, right. orders = "laze" -- we use WRONG spelling here, cause we're cool. yeah, right.
end end
-- trigger.action.outText("Enter createGT group <" .. inGroup:getName() .. "> with o=<" .. orders .. ">, mf=<" .. moveFormation .. ">", 30)
newTroops.insideDestination = false newTroops.insideDestination = false
newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled
newTroops.speedWarning = 0 newTroops.speedWarning = 0
@ -942,6 +938,10 @@ function cfxGroundTroops.createGroundTroops(inGroup, range, orders, moveFormatio
newTroops.signature = "cfx" -- to verify this is groundTroop group, not dcs groups newTroops.signature = "cfx" -- to verify this is groundTroop group, not dcs groups
if not range then range = 300 end if not range then range = 300 end
newTroops.range = range newTroops.range = range
if not code then code = cfxGroundTroops.laseCode end
newTroops.code = code
newTroops.canDrive = canDrive
-- trigger.action.outText("createGndT with code = <" .. code .. ">", 30)
return newTroops return newTroops
end end

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {} cfxHeloTroops = {}
cfxHeloTroops.version = "4.0.0" cfxHeloTroops.version = "4.2.0"
cfxHeloTroops.verbose = false cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false cfxHeloTroops.autoPickup = false
@ -8,28 +8,20 @@ cfxHeloTroops.requestRange = 500 -- meters
-- --
--[[-- --[[--
VERSION HISTORY VERSION HISTORY
3.0.0 - added requestable cloner support
- harmonized spawning invocations across cloners and spawners
- dmlZones
- requestRange attribute
3.0.1 - fixed a bug with legalTroops attribute
3.0.2 - fixed a typo in in-air menu
3.0.3 - pointInZone check for insertion rather than radius
3.0.4 - also handles picking up troops with orders "captureandhold"
3.0.5 - worked around a new issues accessing a unit's name
3.1.0 - compatible with DCS 2.9.6 dynamic spawning
3.1.1 - deployTroopsFromHelicopter() captureandhold
3.1.2 - doLoadGroup - test if group is still alive edge case handling
3.1.3 - decycled structures (destination zone) on save
- upcycled structures (destination) on load
- loadSound and disembarkSound
3.1.4 - guarding destination access in save
3.1.5 - more guarding of destination access
4.0.0 - added dropZones 4.0.0 - added dropZones
- enforceDropZones - enforceDropZones
- coalition for drop zones - coalition for drop zones
--]]-- 4.1.0 - troops dropped in dropZones with active autodespawn are
filtered from load menu
- updated eventhandler to new events and unitLost
- timeStamp to avoid double-dipping
- auto-pickup restricted as well
- code cleanup
4.2.0 - support for individual lase codes
- support for drivable
--]]--
cfxHeloTroops.minTime = 3 -- seconds beween tandings
cfxHeloTroops.requiredLibs = { cfxHeloTroops.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
@ -40,7 +32,7 @@ cfxHeloTroops.requiredLibs = {
cfxHeloTroops.unitConfigs = {} -- all configs are stored by unit's name cfxHeloTroops.unitConfigs = {} -- all configs are stored by unit's name
cfxHeloTroops.troopWeight = 100 -- kg average weight per trooper cfxHeloTroops.troopWeight = 100 -- kg average weight per trooper
cfxHeloTroops.dropZones = {} cfxHeloTroops.dropZones = {} -- dict
-- persistence support -- persistence support
cfxHeloTroops.deployedTroops = {} cfxHeloTroops.deployedTroops = {}
@ -70,18 +62,17 @@ function cfxHeloTroops.resetConfig(conf)
conf.troopsOnBoard = {} -- table with the following conf.troopsOnBoard = {} -- table with the following
conf.troopsOnBoard.name = "***reset***" conf.troopsOnBoard.name = "***reset***"
conf.dropFormation = "circle_out" -- may be chosen later? conf.dropFormation = "circle_out" -- may be chosen later?
conf.timeStamp = timer.getTime() -- to avoid double-dipping
end end
function cfxHeloTroops.createDefaultConfig(theUnit) function cfxHeloTroops.createDefaultConfig(theUnit)
local conf = {} local conf = {}
cfxHeloTroops.resetConfig(conf) cfxHeloTroops.resetConfig(conf)
conf.myMainMenu = nil -- this is where the main menu for group will be stored conf.myMainMenu = nil -- this is where the main menu for group will be stored
conf.myCommands = nil -- this is where we put all teh commands in conf.myCommands = nil -- this is where we put all teh commands in
return conf return conf
end end
function cfxHeloTroops.getUnitConfig(theUnit) -- will create new config if not existing function cfxHeloTroops.getUnitConfig(theUnit) -- will create new config if not existing
if not theUnit then if not theUnit then
trigger.action.outText("+++WARNING: nil unit in get config!", 30) trigger.action.outText("+++WARNING: nil unit in get config!", 30)
@ -99,11 +90,10 @@ function cfxHeloTroops.getConfigForUnitNamed(aName)
return cfxHeloTroops.unitConfigs[aName] return cfxHeloTroops.unitConfigs[aName]
end end
--
-- --
-- LANDED -- LANDED
-- --
--
function cfxHeloTroops.loadClosestGroup(conf) function cfxHeloTroops.loadClosestGroup(conf)
local p = conf.unit:getPosition().p local p = conf.unit:getPosition().p
local cat = Group.Category.GROUND local cat = Group.Category.GROUND
@ -113,6 +103,11 @@ function cfxHeloTroops.loadClosestGroup(conf)
-- for now we only load troops with legal type strings -- for now we only load troops with legal type strings
unitsToLoad = cfxHeloTroops.filterTroopsByType(unitsToLoad) unitsToLoad = cfxHeloTroops.filterTroopsByType(unitsToLoad)
-- filter all groups that are inside a dropZone with a
-- positive autoDespawn attribute
local mySide = conf.unit:getCoalition()
unitsToLoad = cfxHeloTroops.filterTroopsFromDropZones(unitsToLoad, mySide)
-- now limit the options to the five closest legal groups -- now limit the options to the five closest legal groups
local numUnits = #unitsToLoad local numUnits = #unitsToLoad
if numUnits < 1 then return false end -- on false will drop through if numUnits < 1 then return false end -- on false will drop through
@ -127,23 +122,32 @@ end
function cfxHeloTroops.heloLanded(theUnit) function cfxHeloTroops.heloLanded(theUnit)
-- when we have landed, -- when we have landed,
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end
local conf = cfxHeloTroops.getUnitConfig(theUnit) local conf = cfxHeloTroops.getUnitConfig(theUnit)
-- prevent double-dipping on land and depart
local now = timer.getTime()
local diff = now - conf.timeStamp
if diff < cfxHeloTroops.minTime then
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT-heloLanded: filtered for time restraint <" .. diff .. ">", 30)
end
return
end
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT-heloLanded: resetting timeStamp for delta <" .. diff .. ">", 30)
end
conf.timeStamp = now
conf.unit = theUnit conf.unit = theUnit
conf.currentState = 0 conf.currentState = 0
-- auto-unload
-- we look if we auto-unload
if conf.autoDrop then if conf.autoDrop then
if conf.troopsOnBoardNum > 0 then if conf.troopsOnBoardNum > 0 then
cfxHeloTroops.doDeployTroops({conf, "autodrop"}) cfxHeloTroops.doDeployTroops({conf, "autodrop"})
-- already called set menu, can exit directly -- doDeployTroops() invokes set menu and empties troopsOnBoard
return return
end end
-- when we get here, we have no troops to drop on board -- no troops to drop on board
-- so nothing to do really except look if we can pick up troops
-- set menu will do that for us
end end
if conf.autoPickup then if conf.autoPickup then
if conf.troopsOnBoardNum < 1 then if conf.troopsOnBoardNum < 1 then
-- load the closest group -- load the closest group
@ -152,49 +156,46 @@ function cfxHeloTroops.heloLanded(theUnit)
end end
end end
end end
-- when we get here, we simply set the newest menus and are done
-- reset menu -- reset menu
cfxHeloTroops.removeComms(conf.unit) cfxHeloTroops.removeComms(conf.unit)
cfxHeloTroops.setCommsMenu(conf.unit) cfxHeloTroops.setCommsMenu(conf.unit)
end end
--
-- --
-- Helo took off -- Helo took off
-- --
--
function cfxHeloTroops.heloDeparted(theUnit) function cfxHeloTroops.heloDeparted(theUnit)
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end
-- change the state to airborne, and update menus
-- when we take off, all that needs to be done is to change the state
-- to airborne, and then set the status flag
local conf = cfxHeloTroops.getUnitConfig(theUnit) local conf = cfxHeloTroops.getUnitConfig(theUnit)
-- prevent double-dipping on land and depart
local now = timer.getTime()
local diff = now - conf.timeStamp
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT-heloDeparted: resetting timeStamp for delta <" .. diff .. ">", 30)
end
conf.timeStamp = now
conf.currentState = 1 -- in the air conf.currentState = 1 -- in the air
cfxHeloTroops.removeComms(conf.unit) cfxHeloTroops.removeComms(conf.unit)
cfxHeloTroops.setCommsMenu(conf.unit) cfxHeloTroops.setCommsMenu(conf.unit)
end end
--
-- --
-- Helo Crashed -- Helo Crashed
-- --
--
function cfxHeloTroops.cleanHelo(theUnit) function cfxHeloTroops.cleanHelo(theUnit)
-- clean up -- clean up
local conf = cfxHeloTroops.getUnitConfig(theUnit) local conf = cfxHeloTroops.getUnitConfig(theUnit)
conf.unit = theUnit conf.unit = theUnit
conf.troopsOnBoardNum = 0 -- all dead conf.troopsOnBoardNum = 0 -- all dead
conf.currentState = -1 -- (we don't know) conf.currentState = -1 -- (we don't know)
-- check if we need to interface with groupTracker -- check if we need to interface with groupTracker
if conf.troopsOnBoard.name and groupTracker then if conf.troopsOnBoard.name and groupTracker then
local theName = conf.troopsOnBoard.name local theName = conf.troopsOnBoard.name
-- there was (possibly) a group on board. see if it was tracked -- there was (possibly) a group on board. see if it was tracked
local isTracking, numTracking, trackers = groupTracker.groupNameTrackedBy(theName) local isTracking, numTracking, trackers = groupTracker.groupNameTrackedBy(theName)
-- if so, remove it from limbo -- if so, remove it from limbo
if isTracking then if isTracking then
for idx, theTracker in pairs(trackers) do for idx, theTracker in pairs(trackers) do
@ -215,11 +216,10 @@ function cfxHeloTroops.heloCrashed(theUnit)
cfxHeloTroops.cleanHelo(theUnit) cfxHeloTroops.cleanHelo(theUnit)
end end
--
-- --
-- M E N U H A N D L I N G & R E S P O N S E -- M E N U H A N D L I N G & R E S P O N S E
-- --
--
function cfxHeloTroops.clearCommsSubmenus(conf) function cfxHeloTroops.clearCommsSubmenus(conf)
if conf.myCommands then if conf.myCommands then
for i=1, #conf.myCommands do for i=1, #conf.myCommands do
@ -231,7 +231,6 @@ end
function cfxHeloTroops.removeCommsFromConfig(conf) function cfxHeloTroops.removeCommsFromConfig(conf)
cfxHeloTroops.clearCommsSubmenus(conf) cfxHeloTroops.clearCommsSubmenus(conf)
if conf.myMainMenu then if conf.myMainMenu then
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu) missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
conf.myMainMenu = nil conf.myMainMenu = nil
@ -241,20 +240,16 @@ end
function cfxHeloTroops.removeComms(theUnit) function cfxHeloTroops.removeComms(theUnit)
if not theUnit then return end if not theUnit then return end
if not theUnit:isExist() then return end if not theUnit:isExist() then return end
local group = theUnit:getGroup() local group = theUnit:getGroup()
local id = group:getID() local id = group:getID()
local conf = cfxHeloTroops.getUnitConfig(theUnit) local conf = cfxHeloTroops.getUnitConfig(theUnit)
conf.id = id conf.id = id
conf.unit = theUnit conf.unit = theUnit
cfxHeloTroops.removeCommsFromConfig(conf) cfxHeloTroops.removeCommsFromConfig(conf)
end end
function cfxHeloTroops.addConfigMenu(conf) function cfxHeloTroops.addConfigMenu(conf)
-- we add the a menu showing current state -- we add a menu showing current configs
-- and the option to change fro auto drop
-- and auto pickup
local onOff = "OFF" local onOff = "OFF"
if conf.autoDrop then onOff = "ON" end if conf.autoDrop then onOff = "ON" end
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
@ -279,10 +274,12 @@ end
function cfxHeloTroops.setCommsMenu(theUnit) function cfxHeloTroops.setCommsMenu(theUnit)
-- compatible with DCS 2.9.6 dynamic spawns -- compatible with DCS 2.9.6 dynamic spawns
-- set F10 Other.. menu for group if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT: setComms for player unit <" .. theUnit:getName() .. ">: ENTER.", 30)
end
if not theUnit then return end if not theUnit then return end
if not theUnit:isExist() then return end if not theUnit:isExist() then return end
-- we only add this menu to troop carriers -- we only add this menu to troop carriers
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then
if cfxHeloTroops.verbose then if cfxHeloTroops.verbose then
@ -294,19 +291,16 @@ function cfxHeloTroops.setCommsMenu(theUnit)
local group = theUnit:getGroup() local group = theUnit:getGroup()
local id = group:getID() local id = group:getID()
local conf = cfxHeloTroops.getUnitConfig(theUnit) local conf = cfxHeloTroops.getUnitConfig(theUnit)
-- set time stamp to avoid double-dipping later
--conf.timeStamp = timer.getTime() -- to avoid double-dipping
conf.id = id; -- we ALWAYS do this so it is current even after a crash conf.id = id; -- we ALWAYS do this so it is current even after a crash
conf.unit = theUnit -- link back conf.unit = theUnit -- link back
-- if we don't have an F-10 menu, create one
-- ok, first, if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(id, 'Airlift Troops') conf.myMainMenu = missionCommands.addSubMenuForGroup(id, 'Airlift Troops')
end end
-- clear out existing commands, add new
-- clear out existing commands
cfxHeloTroops.clearCommsSubmenus(conf) cfxHeloTroops.clearCommsSubmenus(conf)
-- now we have a menu without submenus.
-- add our own submenus
cfxHeloTroops.addConfigMenu(conf) cfxHeloTroops.addConfigMenu(conf)
-- now see if we are on the ground or in the air -- now see if we are on the ground or in the air
@ -317,22 +311,18 @@ function cfxHeloTroops.setCommsMenu(theUnit)
conf.currentState = 1 conf.currentState = 1
end end
end end
if conf.currentState == 0 then if conf.currentState == 0 then
cfxHeloTroops.addGroundMenu(conf) cfxHeloTroops.addGroundMenu(conf)
else else
cfxHeloTroops.addAirborneMenu(conf) cfxHeloTroops.addAirborneMenu(conf)
end end
end end
function cfxHeloTroops.addAirborneMenu(conf) function cfxHeloTroops.addAirborneMenu(conf)
-- while we are airborne, there isn't much to do except add a status menu that does nothing -- while airborne, add a status menu
-- but we can add some instructions
-- let's begin by assuming no troops aboard
local commandTxt = "(To load troops, land in proximity to them)" local commandTxt = "(To load troops, land in proximity to them)"
if conf.troopsOnBoardNum > 0 then if conf.troopsOnBoardNum > 0 then
commandTxt = "(You are carrying " .. conf.troopsOnBoardNum .. " Assault Troops. Land to deploy them)" commandTxt = "(You are carrying " .. conf.troopsOnBoardNum .. " Infantry. Land to deploy them)"
end end
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
conf.id, conf.id,
@ -345,15 +335,21 @@ function cfxHeloTroops.addAirborneMenu(conf)
end end
function cfxHeloTroops.redirectNoAction(args) function cfxHeloTroops.redirectNoAction(args)
-- actually, we do not redirect since there is nothing to do -- we do not redirect since there is nothing to do
end end
function cfxHeloTroops.addGroundMenu(conf) function cfxHeloTroops.addGroundMenu(conf)
-- this is the most complex menu. Player can deploy troops when loaded -- Player can deploy troops when loaded
-- and load troops when they are in proximity -- or load troops when they are in proximity
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT: ENTER addGroundMenu for unit <" .. conf.unit:getName() .. "> with <" .. conf.troopsOnBoardNum .. "> troops on board", 30)
end
-- case 1: troops aboard -- case 1: troops aboard
if conf.troopsOnBoardNum > 0 then if conf.troopsOnBoardNum > 0 then
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT: unit <" .. conf.unit:getName() .. "> has <" .. conf.troopsOnBoardNum .. "> troops on board", 30)
end
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
conf.id, conf.id,
"Deploy Team <" .. conf.troopsOnBoard.name .. ">", "Deploy Team <" .. conf.troopsOnBoard.name .. ">",
@ -362,10 +358,10 @@ function cfxHeloTroops.addGroundMenu(conf)
{conf, "deploy"} {conf, "deploy"}
) )
table.insert(conf.myCommands, theCommand) table.insert(conf.myCommands, theCommand)
return return -- no loading
end end
-- case 2A: no troops aboard, and requestable spawners/cloners in range -- case 2A: no troops aboard. requestable spawners/cloners in range?
local p = conf.unit:getPosition().p local p = conf.unit:getPosition().p
local mySide = conf.unit:getCoalition() local mySide = conf.unit:getCoalition()
@ -385,10 +381,12 @@ function cfxHeloTroops.addGroundMenu(conf)
if cfxHeloTroops.legalTroops then if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, aType) then if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, aType) then
allLegal = false allLegal = false
-- trigger.action.outText("spawner <" .. aSpawner.name .. ">: troop type <" .. aType .. "> is illegal", 30)
end end
else else
if not dcsCommon.typeIsInfantry(aType) then if not dcsCommon.typeIsInfantry(aType) then
allLegal = false allLegal = false
-- trigger.action.outText("spawner <" .. aSpawner.name .. ">: troop type <" .. aType .. "> is not infantry", 30)
end end
end end
end end
@ -431,11 +429,10 @@ function cfxHeloTroops.addGroundMenu(conf)
local numSpawners = #availableSpawners local numSpawners = #availableSpawners
if numSpawners > 5 then numSpawners = 5 end if numSpawners > 5 then numSpawners = 5 end
while numSpawners > 0 do while numSpawners > 0 do
-- for each spawner in range, create a -- for each spawner in range, create a menu item
-- spawn menu item
local spawner = availableSpawners[numSpawners] local spawner = availableSpawners[numSpawners]
local theName = spawner.baseName local theName = spawner.baseName
local comm = "Request <" .. theName .. "> troops for transport" -- .. math.floor(aTeam.dist) .. "m away" local comm = "Request <" .. theName .. "> troops for transport"
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
conf.id, conf.id,
comm, comm,
@ -447,18 +444,20 @@ function cfxHeloTroops.addGroundMenu(conf)
numSpawners = numSpawners - 1 numSpawners = numSpawners - 1
end end
-- case 2B: no troops aboard. see if there are troops around -- Collect troops in range that we can load up
-- that we can load up
local cat = Group.Category.GROUND local cat = Group.Category.GROUND
local unitsToLoad = dcsCommon.getLivingGroupsAndDistInRangeToPoint(p, conf.pickupRange, conf.unit:getCoalition(), cat) local unitsToLoad = dcsCommon.getLivingGroupsAndDistInRangeToPoint(p, conf.pickupRange, conf.unit:getCoalition(), cat)
-- now, the groups may contain units that are not for transport. -- the groups may contain units that are not for transport.
-- later we can filter this by weight, or other cool stuff -- later we can filter this by weight, or other cool stuff
-- for now we simply only troopy with legal type strings -- for now we simply only troopy with legal type strings
-- TODO: add weight filtering -- TODO: add weight filtering
unitsToLoad = cfxHeloTroops.filterTroopsByType(unitsToLoad) unitsToLoad = cfxHeloTroops.filterTroopsByType(unitsToLoad)
-- filter all groups that are inside a dropZone with a
-- positive autoDespawn attribute
unitsToLoad = cfxHeloTroops.filterTroopsFromDropZones(unitsToLoad, mySide)
-- now limit the options to the five closest legal groups -- now limit the options to the five closest legal groups
local numUnits = #unitsToLoad local numUnits = #unitsToLoad
if numUnits > 5 then numUnits = 5 end if numUnits > 5 then numUnits = 5 end
@ -480,7 +479,7 @@ function cfxHeloTroops.addGroundMenu(conf)
local dist = aTeam.dist local dist = aTeam.dist
local group = aTeam.group local group = aTeam.group
local tNum = group:getSize() local tNum = group:getSize()
local comm = "Load <" .. group:getName() .. "> " .. tNum .. " Members" -- .. math.floor(aTeam.dist) .. "m away" local comm = "Load <" .. group:getName() .. "> " .. tNum .. " Members"
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
conf.id, conf.id,
comm, comm,
@ -530,10 +529,38 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
return filteredGroups return filteredGroups
end end
function cfxHeloTroops.filterTroopsFromDropZones(allTroops, mySide)
-- quick-out: no dropZones
if dcsCommon.getSizeOfTable(cfxHeloTroops.dropZones) < 1 then return allTroops end
local filtered = {}
for idx, theTeam in pairs(allTroops) do
-- theTeam is a table {group, dist}
local theGroup = theTeam.group
local firstUnit = theGroup:getUnit(1)
local include = true
if firstUnit and Unit.isExist(firstUnit) then
local p = firstUnit:getPoint()
for idy, theZone in pairs(cfxHeloTroops.dropZones) do
if theZone.autoDespawn > 0 and
(theZone:getCoalition() == 0 or theZone:getCoalition() == mySide)
then
-- see if the unit is inside this zone
if theZone:isPointInsideZone(p) then
include = false -- filter out
if theZone.verbose then
trigger.action.outText("+++helo: filtered group <" .. theGroup:getName() .. "> from 'load' menu. Reason: autoDespawn active in deploy zone <" .. theZone.name .. ">", 30)
end
end
end
end
end
if include then table.insert(filtered, theTeam) end
end
return filtered
end
-- --
-- T O G G L E S -- T O G G L E S
-- --
function cfxHeloTroops.redirectToggleConfig(args) function cfxHeloTroops.redirectToggleConfig(args)
timer.scheduleFunction(cfxHeloTroops.doToggleConfig, args, timer.getTime() + 0.1) timer.scheduleFunction(cfxHeloTroops.doToggleConfig, args, timer.getTime() + 0.1)
end end
@ -555,13 +582,10 @@ function cfxHeloTroops.doToggleConfig(args)
else else
trigger.action.outTextForGroup(conf.id, "Troops will now board only after being ordered to do so", 30) trigger.action.outTextForGroup(conf.id, "Troops will now board only after being ordered to do so", 30)
end end
end end
cfxHeloTroops.setCommsMenu(conf.unit) cfxHeloTroops.setCommsMenu(conf.unit)
end end
-- --
-- Deploying Troops -- Deploying Troops
-- --
@ -571,14 +595,13 @@ end
function cfxHeloTroops.scoreWhenCapturing(theUnit) function cfxHeloTroops.scoreWhenCapturing(theUnit)
if theUnit and Unit.isExist(theUnit) and theUnit.getPlayerName then if theUnit and Unit.isExist(theUnit) and theUnit.getPlayerName then
-- see if wer are inside a non-alinged zone -- see if we are inside a non-alinged zone (incl. neutral)
-- and this includes a neutral zone
local coa = theUnit:getCoalition() local coa = theUnit:getCoalition()
local p = theUnit:getPoint() local p = theUnit:getPoint()
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
local ID = theGroup:getID() local ID = theGroup:getID()
local nearestZone, dist = cfxOwnedZones.getNearestOwnedZoneToPoint(p) local nearestZone, dist = cfxOwnedZones.getNearestOwnedZoneToPoint(p)
if nearestZone and nearestZone:pointInZone(p) then -- dist < nearestZone.radius then if nearestZone and nearestZone:pointInZone(p) then
-- we are inside an owned zone! -- we are inside an owned zone!
if nearestZone.owner ~= coa then if nearestZone.owner ~= coa then
-- yup, combat drop! -- yup, combat drop!
@ -615,7 +638,6 @@ function cfxHeloTroops.doDeployTroops(args)
-- deploy the troops I have on board -- deploy the troops I have on board
cfxHeloTroops.deployTroopsFromHelicopter(conf) cfxHeloTroops.deployTroopsFromHelicopter(conf)
-- interface with playerscore if we dropped -- interface with playerscore if we dropped
-- inside an enemy-owned zone -- inside an enemy-owned zone
if cfxPlayerScore and cfxOwnedZones then if cfxPlayerScore and cfxOwnedZones then
@ -627,7 +649,10 @@ function cfxHeloTroops.doDeployTroops(args)
conf.troopsOnBoardNum = 0 conf.troopsOnBoardNum = 0
conf.troopsOnBoard = {} conf.troopsOnBoard = {}
conf.troopsOnBoard.name = "***wasdeployed***" conf.troopsOnBoard.name = "***wasdeployed***"
cfxHeloTroops.unitConfigs[theUnit:getName()] = conf -- forced write-back (strange...)
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT: doDeployTroops unit <" .. conf.unit:getName() .. "> reset to <" .. conf.troopsOnBoardNum .. "> troops on board", 30)
end
-- reset menu -- reset menu
cfxHeloTroops.removeComms(conf.unit) cfxHeloTroops.removeComms(conf.unit)
cfxHeloTroops.setCommsMenu(conf.unit) cfxHeloTroops.setCommsMenu(conf.unit)
@ -635,15 +660,13 @@ end
function cfxHeloTroops.deployTroopsFromHelicopter(conf) function cfxHeloTroops.deployTroopsFromHelicopter(conf)
-- we have troops, drop them now
local unitTypes = {} -- build type names local unitTypes = {} -- build type names
local theUnit = conf.unit local theUnit = conf.unit
local p = theUnit:getPoint() local p = theUnit:getPoint()
-- split the conf.troopsOnBoardTypes into an array of types -- split the conf.troopsOnBoardTypes into an array of types
unitTypes = dcsCommon.splitString(conf.troopsOnBoard.types, ",") unitTypes = dcsCommon.splitString(conf.troopsOnBoard.types, ",")
if #unitTypes < 1 then if #unitTypes < 1 then
table.insert(unitTypes, "Soldier M4") -- make it one m4 trooper as fallback table.insert(unitTypes, "Soldier M4") -- fallback
end end
local range = conf.troopsOnBoard.range local range = conf.troopsOnBoard.range
@ -651,6 +674,8 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local dest = conf.troopsOnBoard.destination local dest = conf.troopsOnBoard.destination
local theName = conf.troopsOnBoard.name local theName = conf.troopsOnBoard.name
local moveFormation = conf.troopsOnBoard.moveFormation local moveFormation = conf.troopsOnBoard.moveFormation
local code = conf.troopsOnBoard.code
local canDrive = conf.troopsOnBoard.canDrive
if not orders then orders = "guard" end if not orders then orders = "guard" end
orders = string.lower(orders) orders = string.lower(orders)
@ -671,7 +696,9 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
chopperZone, chopperZone,
unitTypes, unitTypes,
conf.dropFormation, conf.dropFormation,
90) 90,
nil, -- liveries not yet supported
canDrive)
-- persistence management -- persistence management
local troopData = {} local troopData = {}
troopData.groupData = theData troopData.groupData = theData
@ -681,7 +708,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
troopData.destination = dest -- only for attackzone orders troopData.destination = dest -- only for attackzone orders
cfxHeloTroops.deployedTroops[theData.name] = troopData cfxHeloTroops.deployedTroops[theData.name] = troopData
local troop = cfxGroundTroops.createGroundTroops(theGroup, range, orders, moveFormation) local troop = cfxGroundTroops.createGroundTroops(theGroup, range, orders, moveFormation, code, canDrive)
if orders == "captureandhold" then if orders == "captureandhold" then
-- we get the target zone NOW!!! before we flip the zone and -- we get the target zone NOW!!! before we flip the zone and
-- and make them run to the wrong zone -- and make them run to the wrong zone
@ -698,8 +725,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders
trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30) trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30)
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.disembarkSound) trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.disembarkSound)
-- see if this is tracked by a tracker, and pass them back so -- if tracked by a tracker, and pass them back for un-limbo
-- they can un-limbo
if groupTracker then if groupTracker then
local isTracking, numTracking, trackers = groupTracker.groupNameTrackedBy(theName) local isTracking, numTracking, trackers = groupTracker.groupNameTrackedBy(theName)
if isTracking then if isTracking then
@ -712,7 +738,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
end end
end end
-- bang on all dropZones that we can find -- bang on dropZones
for name, theZone in pairs(cfxHeloTroops.dropZones) do for name, theZone in pairs(cfxHeloTroops.dropZones) do
-- can employ coalition test here as well, maybe later? -- can employ coalition test here as well, maybe later?
if theZone:isPointInsideZone(p) then if theZone:isPointInsideZone(p) then
@ -766,9 +792,9 @@ function cfxHeloTroops.doLoadGroup(args)
conf.troopsOnBoard.name = gName conf.troopsOnBoard.name = gName
-- and put it all into the helicopter config -- and put it all into the helicopter config
-- now we need to destroy the group. Let's prepare: -- destroy the group:
-- if it was tracked, tell tracker to move it to limbo -- if it was tracked, tell tracker to move it to limbo
-- to remember it even if it's destroyed -- to remember it
if groupTracker then if groupTracker then
-- only if groupTracker is active -- only if groupTracker is active
local isTracking, numTracking, trackers = groupTracker.groupTrackedBy(group) local isTracking, numTracking, trackers = groupTracker.groupTrackedBy(group)
@ -786,7 +812,7 @@ function cfxHeloTroops.doLoadGroup(args)
-- then, remove it from the pool -- then, remove it from the pool
local pooledGroup = cfxGroundTroops.getGroundTroopsForGroup(group) local pooledGroup = cfxGroundTroops.getGroundTroopsForGroup(group)
if pooledGroup then if pooledGroup then
-- copy some important info from the troops -- copy important info from the troops
-- if they are set -- if they are set
conf.troopsOnBoard.orders = pooledGroup.orders conf.troopsOnBoard.orders = pooledGroup.orders
conf.troopsOnBoard.range = pooledGroup.range conf.troopsOnBoard.range = pooledGroup.range
@ -795,29 +821,26 @@ function cfxHeloTroops.doLoadGroup(args)
if pooledGroup.orders and pooledGroup.orders == "captureandhold" then if pooledGroup.orders and pooledGroup.orders == "captureandhold" then
conf.troopsOnBoard.destination = nil -- forget last destination so they can be helo-redeployed conf.troopsOnBoard.destination = nil -- forget last destination so they can be helo-redeployed
end end
conf.troopsOnBoard.code = pooledGroup.code
conf.troopsOnBoard.canDrive = pooledGroup.canDrive
cfxGroundTroops.removeTroopsFromPool(pooledGroup) cfxGroundTroops.removeTroopsFromPool(pooledGroup)
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' loaded and has orders <" .. conf.troopsOnBoard.orders .. ">", 30) trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' loaded and has orders <" .. conf.troopsOnBoard.orders .. ">", 30)
-- trigger.action.outText("and mf = <" .. conf.troopsOnBoard.moveFormation .. ">", 30)
--trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound) -- "Quest Snare 3.wav")
else else
if cfxHeloTroops.verbose then if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT: ".. conf.troopsOnBoard.name .." was not committed to ground troops", 30) trigger.action.outText("+++heloT: ".. conf.troopsOnBoard.name .." was not committed to ground troops", 30)
end end
end end
-- now simply destroy the group
-- we'll re-assemble it when we deploy it
-- TODO: add weight changing code -- TODO: add weight changing code
-- TODO: ensure compatibility with CSAR module -- TODO: ensure compatibility with CSAR module
group:destroy() group:destroy()
-- now immediately run a GC so this group is removed -- now immediately run a GC so this group is removed
-- from any save data -- from any save data
cfxHeloTroops.GC() cfxHeloTroops.GC()
-- say so -- say so
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' aboard, ready to go!", 30) trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' aboard, ready to go!", 30)
trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.loadSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.loadSound)
-- reset menu -- reset menu
cfxHeloTroops.removeComms(conf.unit) cfxHeloTroops.removeComms(conf.unit)
@ -840,9 +863,8 @@ end
function cfxHeloTroops.doSpawnGroup(args) function cfxHeloTroops.doSpawnGroup(args)
local conf = args[1] local conf = args[1]
local theSpawner = args[2] local theSpawner = args[2]
-- NOTE: theSpawner can be of type cfxSpawnZone !!!OR!!! cfxCloneZones -- theSpawner can be of type cfxSpawnZone !!!OR!!! cfxCloneZones
-- make sure cooldown on spawner has timed out, else -- make sure cooldown on spawner has timed out, else notify that you have to wait
-- notify that you have to wait
local now = timer.getTime() local now = timer.getTime()
if now < (theSpawner.lastSpawnTimeStamp + theSpawner.cooldown) then if now < (theSpawner.lastSpawnTimeStamp + theSpawner.cooldown) then
local delta = math.floor(theSpawner.lastSpawnTimeStamp + theSpawner.cooldown - now) local delta = math.floor(theSpawner.lastSpawnTimeStamp + theSpawner.cooldown - now)
@ -850,15 +872,13 @@ function cfxHeloTroops.doSpawnGroup(args)
return return
end end
--cfxSpawnZones.spawnWithSpawner(theSpawner) -- old code theSpawner.spawnWithSpawner(theSpawner) -- can be both spawner and cloner (Lua "polymorphism"
theSpawner.spawnWithSpawner(theSpawner) -- can be both spawner and cloner
trigger.action.outTextForGroup(conf.id, "Deploying <" .. theSpawner.baseName .. "> now...", 30) trigger.action.outTextForGroup(conf.id, "Deploying <" .. theSpawner.baseName .. "> now...", 30)
-- reset all comms so we can include new troops -- reset all comms so we can include new troops
-- into load menu -- into load menu
timer.scheduleFunction(cfxHeloTroops.delayedCommsResetForUnit, {conf.unit, "ignore"}, now + 1.0) timer.scheduleFunction(cfxHeloTroops.delayedCommsResetForUnit, {conf.unit, "ignore"}, now + 1.0)
end end
-- --
-- handle events -- handle events
-- --
@ -872,24 +892,20 @@ function cfxHeloTroops:onEvent(theEvent)
if not theUnit:getPlayerName() then return end -- not a player if not theUnit:getPlayerName() then return end -- not a player
local name = theUnit:getName() -- moved to a later local name = theUnit:getName() -- moved to a later
-- only for helicopters -- overridedden by troop carriers -- only for troop carriers (not just helos any more)
-- we don't check for cat any more, so any airframe
-- can be used as long as it's ok with isTroopCarrier()
-- only for troop carriers
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then
return return
end end
if theID == 4 then -- land if theID == 4 or theID == 55 then -- land
cfxHeloTroops.heloLanded(theUnit) cfxHeloTroops.heloLanded(theUnit)
end end
if theID == 3 then -- take off if theID == 3 or theID == 54 then -- take off
cfxHeloTroops.heloDeparted(theUnit) cfxHeloTroops.heloDeparted(theUnit)
end end
if theID == 5 then -- crash if theID == 5 or theID == 30 then -- crash or unitLost
cfxHeloTroops.heloCrashed(theUnit) cfxHeloTroops.heloCrashed(theUnit)
end end
@ -1030,6 +1046,18 @@ function cfxHeloTroops.loadData()
local cty = gData.cty local cty = gData.cty
local cat = gData.cat local cat = gData.cat
local dest = nil local dest = nil
local code = gdTroop.code
local canDrive = gdTroop.canDrive
local formation = gdTroop.moveFormation
local code = gdTroop.code
local canDrive = gdTroop.canDrive
if canDrive then -- restore canDrive to all units
local units = gData.units
for idx, theUnit in pairs(units) do
theUnit.playerCanDrive = drivable
end
end
-- synch destination from name to real zone -- synch destination from name to real zone
if gdTroop.destination then if gdTroop.destination then
@ -1045,7 +1073,7 @@ function cfxHeloTroops.loadData()
-- post-proccing for cfxGroundTroops -- post-proccing for cfxGroundTroops
-- add to groundTroops -- add to groundTroops
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders) local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders, moveFormation, code, canDrive)
newTroops.destination = dest newTroops.destination = dest
cfxGroundTroops.addGroundTroopsToPool(newTroops) cfxGroundTroops.addGroundTroopsToPool(newTroops)
end end

View File

@ -1,26 +1,25 @@
jtacGrpUI = {} jtacGrpUI = {}
jtacGrpUI.version = "3.1.0" jtacGrpUI.version = "4.0.0"
jtacGrpUI.requiredLibs = { jtacGrpUI.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", "cfxZones",
"cfxGroundTroops", "cfxGroundTroops",
} }
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 2.0.0 - dmlZones
- sanity checks upon load
- eliminated cfxPlayer dependence
- clean-up
- jtacSound
3.0.0 - support for attachTo: 3.0.0 - support for attachTo:
3.1.0 - support for DCS 2.0.6 dynamic player spwans 3.1.0 - support for DCS 2.9.6 jul-11 2024 dynamic player spwans
3.2.0 - better guarding access to ownedZones in collectJTACtargets()
4.0.0 - added support for twn when present
- made report more clear that all pos are requestor-relative
- support for CA (event 20 (enter unit) on ground vehicle)
- report now supports wildcards
- reports inm two parts: why and what
- "no target" for right side when no "what"
- comms mechanic simplification
- lase code from spawner support via ground troops
--]]-- --]]--
-- find & command cfxGroundTroops-based jtacs
-- UI installed via OTHER for all groups with players
-- module based on xxxGrpUI
jtacGrpUI.groupConfig = {} -- all inited group private config data, indexed by group name. jtacGrpUI.groupConfig = {} -- all inited group private config data, indexed by group name.
jtacGrpUI.simpleCommands = true -- if true, f10 other invokes directly
function jtacGrpUI.resetConfig(conf) function jtacGrpUI.resetConfig(conf)
end end
@ -43,8 +42,7 @@ function jtacGrpUI.createDefaultConfig(theGroup)
return conf return conf
end end
-- getConfigFor group will allocate if doesn't exist in DB -- lazy init: allocate if doesn't exist in DB
-- and add to it
function jtacGrpUI.getConfigForGroup(theGroup) function jtacGrpUI.getConfigForGroup(theGroup)
if not theGroup or (not Group.isExist(theGroup))then if not theGroup or (not Group.isExist(theGroup))then
trigger.action.outText("+++WARNING: jtacGrpUI nil group in getConfigForGroup!", 30) trigger.action.outText("+++WARNING: jtacGrpUI nil group in getConfigForGroup!", 30)
@ -65,8 +63,7 @@ function jtacGrpUI.getConfigByGroupName(theName) -- DOES NOT allocate when not e
end end
function jtacGrpUI.getConfigForUnit(theUnit) function jtacGrpUI.getConfigForUnit(theUnit) -- lazy alloc
-- simple one-off step by accessing the group
if not theUnit then if not theUnit then
trigger.action.outText("+++WARNING: jtacGrpUI nil unit in getConfigForUnit!", 30) trigger.action.outText("+++WARNING: jtacGrpUI nil unit in getConfigForUnit!", 30)
return nil return nil
@ -90,29 +87,12 @@ end
function jtacGrpUI.removeCommsFromConfig(conf) function jtacGrpUI.removeCommsFromConfig(conf)
jtacGrpUI.clearCommsSubmenus(conf) jtacGrpUI.clearCommsSubmenus(conf)
if conf.myMainMenu then if conf.myMainMenu then
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu) missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
conf.myMainMenu = nil conf.myMainMenu = nil
end end
end end
-- this only works in single-unit player groups.
function jtacGrpUI.removeCommsForUnit(theUnit)
if not theUnit then return end
if not theUnit:isExist() then return end
-- perhaps add code: check if group is empty
local conf = jtacGrpUI.getConfigForUnit(theUnit)
jtacGrpUI.removeCommsFromConfig(conf)
end
function jtacGrpUI.removeCommsForGroup(theGroup)
if not theGroup then return end
if not theGroup:isExist() then return end
local conf = jtacGrpUI.getConfigForGroup(theGroup)
jtacGrpUI.removeCommsFromConfig(conf)
end
function jtacGrpUI.isEligibleForMenu(theGroup) function jtacGrpUI.isEligibleForMenu(theGroup)
if jtacGrpUI.jtacTypes == "all" or if jtacGrpUI.jtacTypes == "all" or
jtacGrpUI.jtacTypes == "any" then return true end jtacGrpUI.jtacTypes == "any" then return true end
@ -124,71 +104,32 @@ function jtacGrpUI.isEligibleForMenu(theGroup)
local cat = theGroup:getCategory() local cat = theGroup:getCategory()
return cat == 0 return cat == 0
end end
-- note: no option for ground troops now
if jtacGrpUI.verbose then if jtacGrpUI.verbose then
trigger.action.outText("+++jGUI: unknown jtacTypes <" .. jtacGrpUI.jtacTypes .. "> -- allowing access to group <" .. theGroup:getName() ..">", 30) trigger.action.outText("+++jGUI: unknown jtacTypes <" .. jtacGrpUI.jtacTypes .. "> -- allowing access to group <" .. theGroup:getName() ..">", 30)
end end
return true -- for later expansion return true -- for later expansion
end end
function jtacGrpUI.setCommsMenuForUnit(theUnit)
if not theUnit then
trigger.action.outText("+++WARNING: jtacGrpUI nil UNIT in setCommsMenuForUnit!", 30)
return
end
if not theUnit:isExist() then return end
local theGroup = theUnit:getGroup()
jtacGrpUI.setCommsMenu(theGroup)
end
function jtacGrpUI.setCommsMenu(theGroup) function jtacGrpUI.setCommsMenu(theGroup)
if not theGroup then return end if not theGroup then return end
if not Group.isExist(theGroup) then return end if not Group.isExist(theGroup) then return end
if not jtacGrpUI.isEligibleForMenu(theGroup) then return end if not jtacGrpUI.isEligibleForMenu(theGroup) then return end
local mainMenu = nil local mainMenu = nil
if jtacGrpUI.mainMenu then if jtacGrpUI.mainMenu then
mainMenu = radioMenu.getMainMenuFor(jtacGrpUI.mainMenu) -- nilling both next params will return menus[0] mainMenu = radioMenu.getMainMenuFor(jtacGrpUI.mainMenu)
end end
local conf = jtacGrpUI.getConfigForGroup(theGroup) local conf = jtacGrpUI.getConfigForGroup(theGroup)
conf.id = theGroup:getID(); -- we always do this conf.id = theGroup:getID(); -- we always do this
local commandTxt = jtacGrpUI.menuName -- "jtac Lasing Report"
if jtacGrpUI.simpleCommands then local theCommand = missionCommands.addCommandForGroup(conf.id, commandTxt, mainMenu, jtacGrpUI.redirectCommandX, {conf, "lasing report"})
-- we install directly in F-10 other
if not conf.myMainMenu then
local commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup(
conf.id, commandTxt, mainMenu, jtacGrpUI.redirectCommandX, {conf, "lasing report"})
conf.myMainMenu = theCommand conf.myMainMenu = theCommand
end
return
end
-- ok, first, if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'jtac', mainMenu)
end
-- clear out existing commands
jtacGrpUI.clearCommsSubmenus(conf)
-- now we have a menu without submenus.
-- add our own submenus
jtacGrpUI.addSubMenus(conf)
end end
function jtacGrpUI.addSubMenus(conf) function jtacGrpUI.addSubMenus(conf)
local commandTxt = "jtac Lasing Report" local commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(conf.id, commandTxt, conf.myMainMenu, jtacGrpUI.redirectCommandX, {conf, "lasing report"})
conf.id,
commandTxt,
conf.myMainMenu,
jtacGrpUI.redirectCommandX,
{conf, "lasing report"}
)
table.insert(conf.myCommands, theCommand) table.insert(conf.myCommands, theCommand)
end end
@ -197,8 +138,8 @@ function jtacGrpUI.redirectCommandX(args)
end end
function jtacGrpUI.doCommandX(args) function jtacGrpUI.doCommandX(args)
local conf = args[1] -- < conf in here local conf = args[1] -- conf
local what = args[2] -- < second argument in here local what = args[2] -- "xyz" -- not used here
local theGroup = conf.theGroup local theGroup = conf.theGroup
local targetList = jtacGrpUI.collectJTACtargets(conf, true) local targetList = jtacGrpUI.collectJTACtargets(conf, true)
-- iterate the list -- iterate the list
@ -207,14 +148,33 @@ function jtacGrpUI.doCommandX(args)
trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound) trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound)
return return
end end
local here = dcsCommon.getGroupLocation(conf.theGroup) -- pos of pilot!
local desc = "JTAC Target Report:\n" local desc = "JTAC Target Report:\nTargets being laser-designated for " .. conf.name .. ":\n"
for i=1, #targetList do for i=1, #targetList do
local aTarget = targetList[i] local aTarget = targetList[i]
if aTarget.idle then local theUnit = aTarget.source -- lazing unit
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo ..": no target" local code = aTarget.code
if not Unit.isExist(theUnit) then
desc = desc .. "\n" .. aTarget.jtacName .. ": lost contact."
elseif aTarget.idle then
local lWho = jtacGrpUI.who
lWho = dcsCommon.processStringWildcardsForUnit(lWho, theUnit)
local there = theUnit:getPoint()
lWho = dcsCommon.processAtoBWildCards(lWho, here, there)
lWho = lWho:gsub("<code>", code)
desc = desc .. "\n" .. lWho ..": no target"
else else
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo .." lasing " .. aTarget.lazeTargetType .. " [" .. aTarget.range .. "nm at " .. aTarget.bearing .. "°]," .. " code=" .. cfxGroundTroops.laseCode local lWho = dcsCommon.processStringWildcardsForUnit(jtacGrpUI.who, theUnit)
local there = theUnit:getPoint()
lWho = dcsCommon.processAtoBWildCards(lWho, here, there)
lWho = lWho:gsub("<code>", code)
local lWhat = dcsCommon.processStringWildcardsForUnit(jtacGrpUI.what, aTarget.lazeTarget) -- does unit type
there = aTarget.lazeTarget:getPoint()
lWhat = dcsCommon.processTimeLocWildCards(lWhat, there)
lWhat = dcsCommon.processAtoBWildCards(lWhat, here, there)
lWhat = lWhat:gsub("<code>", code)
desc = desc .. "\n" .. lWho .. lWhat
end end
end end
trigger.action.outTextForGroup(conf.id, desc .. "\n", 30) trigger.action.outTextForGroup(conf.id, desc .. "\n", 30)
@ -226,7 +186,6 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
-- troops that are lazing. 'Lazing' are all groups that -- troops that are lazing. 'Lazing' are all groups that
-- have an active (non-nil) lazeTarget and 'laze' orders -- have an active (non-nil) lazeTarget and 'laze' orders
if not includeIdle then includeIdle = false end if not includeIdle then includeIdle = false end
local theJTACS = {} local theJTACS = {}
for idx, troop in pairs(cfxGroundTroops.deployedTroops) do for idx, troop in pairs(cfxGroundTroops.deployedTroops) do
if troop.coalition == conf.coalition if troop.coalition == conf.coalition
@ -252,31 +211,20 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
for idx, troop in pairs (theJTACS) do for idx, troop in pairs (theJTACS) do
local aTarget = {} local aTarget = {}
-- establish our location -- establish our location
aTarget.jtacName = troop.name aTarget.jtacName = troop.name -- group name
aTarget.posInfo = "" aTarget.code = troop.code
if cfxOwnedZones and cfxOwnedZones.hasOwnedZones() then aTarget.source = dcsCommon.getFirstLivingUnit(troop.group)
local jtacLoc = dcsCommon.getGroupLocation(troop.group)
local nearestZone = cfxOwnedZones.getNearestOwnedZoneToPoint(jtacLoc)
if nearestZone then
local ozRange = dcsCommon.dist(jtacLoc, nearestZone.point) * 0.000621371 -- meters to nm
ozRange = math.floor(ozRange * 10) / 10
local relPos = dcsCommon.compassPositionOfARelativeToB(jtacLoc, nearestZone.point)
aTarget.posInfo = " (" .. ozRange .. "nm " .. relPos .. " of " .. nearestZone.name .. ")"
end
end
-- we may get idlers, catch them now -- we may get idlers, catch them now
if not troop.lazeTarget then if not troop.lazeTarget then
aTarget.idle = true aTarget.idle = true
aTarget.range = math.huge aTarget.range = math.huge
else else
-- get the target we are lazing -- proc target
local there = troop.lazeTarget:getPoint() local there = troop.lazeTarget:getPoint()
aTarget.idle = false aTarget.idle = false
aTarget.range = dcsCommon.dist(here, there) aTarget.range = dcsCommon.dist(here, there) * 0.000621371 -- meter to miles
aTarget.range = aTarget.range * 0.000621371 -- meter to miles
aTarget.range = math.floor(aTarget.range * 10) / 10 aTarget.range = math.floor(aTarget.range * 10) / 10
aTarget.bearing = dcsCommon.bearingInDegreesFromAtoB(here, there) aTarget.lazeTarget = troop.lazeTarget
aTarget.lazeTargetType = troop.lazeTargetType
end end
table.insert(targetList, aTarget) table.insert(targetList, aTarget)
end end
@ -289,7 +237,7 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
end end
-- --
-- event handler - simplified, only for player birth -- event handler
-- --
function jtacGrpUI:onEvent(theEvent) function jtacGrpUI:onEvent(theEvent)
if not theEvent then return end if not theEvent then return end
@ -315,12 +263,29 @@ function jtacGrpUI:onEvent(theEvent)
jtacGrpUI.removeCommsFromConfig(conf) -- remove menus jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
end end
jtacGrpUI.setCommsMenu(theGroup) jtacGrpUI.setCommsMenu(theGroup)
end end
-- maybe collapse event 15 into 20?
if id == 20 then -- CA player enter event???
local theGroup = theUnit:getGroup()
if not theGroup then return end
local gName = theGroup:getName()
if not gName then return end
local cat = theGroup:getCategory()
if cat == 2 then -- ground! we are in a CA unit!
local pName = theUnit:getPlayerName()
if jtacGrpUI.verbose then
trigger.action.outText("+++jGUI: CA player unit take-over. installing JTAC for <" .. pName .. "> on unit <" .. uName .. ">", 30)
end
local conf = jtacGrpUI.getConfigByGroupName(gName)
if conf then
jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
end
jtacGrpUI.setCommsMenu(theGroup)
end
end
end end
-- --
-- Start -- Start
-- --
@ -335,6 +300,7 @@ function jtacGrpUI.readConfigZone()
jtacGrpUI.jtacTypes = string.lower(jtacGrpUI.jtacTypes) jtacGrpUI.jtacTypes = string.lower(jtacGrpUI.jtacTypes)
jtacGrpUI.jtacSound = theZone:getStringFromZoneProperty("jtacSound", "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav") jtacGrpUI.jtacSound = theZone:getStringFromZoneProperty("jtacSound", "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
jtacGrpUI.menuName = theZone:getStringFromZoneProperty("menuName", "jtac Lasing Report")
if theZone:hasProperty("attachTo:") then if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>") local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
@ -350,6 +316,9 @@ function jtacGrpUI.readConfigZone()
end end
end end
jtacGrpUI.who = theZone:getStringFromZoneProperty("who", "<g><twnnm>")
--jtacGrpUI.what = theZone:getStringFromZoneProperty("what", " - lasing <typ> [<lat>:<lon>], code=<code>")
jtacGrpUI.what = theZone:getStringFromZoneProperty("what", " - lasing <typ> [<rngnm>nm, bearing <bea>°], code=<code>")
jtacGrpUI.verbose = theZone.verbose jtacGrpUI.verbose = theZone.verbose
end end
@ -362,15 +331,7 @@ function jtacGrpUI.start()
if not dcsCommon.libCheck("cfx jtac GUI", jtacGrpUI.requiredLibs) then if not dcsCommon.libCheck("cfx jtac GUI", jtacGrpUI.requiredLibs) then
return false return false
end end
jtacGrpUI.readConfigZone() jtacGrpUI.readConfigZone()
local allPlayerUnits = dcsCommon.getAllExistingPlayerUnitsRaw()
for unitName, theUnit in pairs(allPlayerUnits) do
jtacGrpUI.setCommsMenuForUnit(theUnit)
end
-- now install event handler
world.addEventHandler(jtacGrpUI) world.addEventHandler(jtacGrpUI)
trigger.action.outText("cf/x jtacGrpUI v" .. jtacGrpUI.version .. " started", 30) trigger.action.outText("cf/x jtacGrpUI v" .. jtacGrpUI.version .. " started", 30)
return true return true
@ -381,9 +342,3 @@ if not jtacGrpUI.start() then
trigger.action.outText("JTAC GUI failed to start up.", 30) trigger.action.outText("JTAC GUI failed to start up.", 30)
jtacGrpUI = nil jtacGrpUI = nil
end end
--[[--
TODO:
callback into GroundTroops lazing
what is 'simpleCommand' really for? remove or refine
--]]--

View File

@ -607,7 +607,7 @@ end
-- getting closest owned zones etc -- getting closest owned zones etc
-- required for groundTroops and factory attackers -- required for groundTroops and factory attackers
-- methods provided only for other modules (e.g. cfxGroundTroops or -- methods provided only for other modules (e.g. cfxGroundTroops, jtacGrpGUI or
-- factoryZone -- factoryZone
-- --
@ -658,7 +658,7 @@ function cfxOwnedZones.collectZones(mode)
end end
end end
-- getNearestOwnedZoneToPoint invoked by heloTroops -- getNearestOwnedZoneToPoint invoked by heloTroops and jtacGUI
function cfxOwnedZones.getNearestOwnedZoneToPoint(p) function cfxOwnedZones.getNearestOwnedZoneToPoint(p)
local allZones = cfxOwnedZones.collectZones() local allZones = cfxOwnedZones.collectZones()
return cfxZones.getClosestZone(p, allZones) return cfxZones.getClosestZone(p, allZones)

View File

@ -1,5 +1,5 @@
cfxSpawnZones = {} cfxSpawnZones = {}
cfxSpawnZones.version = "2.2.0" cfxSpawnZones.version = "3.0.0"
cfxSpawnZones.requiredLibs = { cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
@ -18,20 +18,12 @@ cfxSpawnZones.spawnedGroups = {}
-- --
-- Zones that conform with this requirements spawn toops automatically -- Zones that conform with this requirements spawn toops automatically
-- *** DOES NOT EXTEND ZONES *** LINKED OWNER via masterOwner *** -- *** DOES !NOT! EXTEND ZONES *** LINKED OWNER via masterOwner ***
-- --
--[[-- --[[--
-- version history -- version history
2.0.0 - dmlZones 3.0.0 - supports zone-individual laser code for "lase" orders
- moved "types" to spawner - drivable attribute passed to groundTroops
- baseName defaults to zone name, as it is safe for naming
- spawnWithSpawner direct link in spawner to spawnZones
2.0.1 - fix in verifySpawnOwnership() when not master zone found
2.0.2 - new "moveFormation" attribute
2.0.3 - corrected type in spawnUnits? attribute
2.1.0 - masterOwner update for dmlZones.
since spawners don't extend zones, this is still old-school
2.2.0 - "drivable" spawner attribute
--]]-- --]]--
@ -176,6 +168,12 @@ function cfxSpawnZones.createSpawner(inZone)
end end
end end
if inZone:hasProperty("code") then -- lase code
theSpawner.code = inZone:getNumberFromZoneProperty("code", 1688)
elseif inZone:hasProperty("lcode") then -- lase code
theSpawner.code = inZone:getNumberFromZoneProperty("lcode", 1688)
end
if cfxSpawnZones.verbose or inZone.verbose then if cfxSpawnZones.verbose or inZone.verbose then
trigger.action.outText("+++spwn: created spawner for <" .. inZone.name .. ">", 30) trigger.action.outText("+++spwn: created spawner for <" .. inZone.name .. ">", 30)
end end
@ -212,14 +210,19 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
for aZone, aSpawner in pairs(cfxSpawnZones.allSpawners) do for aZone, aSpawner in pairs(cfxSpawnZones.allSpawners) do
-- iterate all zones and collect those that match -- iterate all zones and collect those that match
local hasMatch = true local hasMatch = true
local reasons = ""
local delta = dcsCommon.distFlat(aPoint, cfxZones.getPoint(aZone)) local delta = dcsCommon.distFlat(aPoint, cfxZones.getPoint(aZone))
if delta>aRange then hasMatch = false end if delta>aRange then
hasMatch = false
-- reasons = reasons .. "[distance " .. math.floor(delta) .. "]
end
if aSide ~= 0 then if aSide ~= 0 then
-- check if side is correct for owned zone -- check if side is correct for owned zone
if not cfxSpawnZones.verifySpawnOwnership(aSpawner) then if not cfxSpawnZones.verifySpawnOwnership(aSpawner) then
-- failed ownership test. owner of master -- failed ownership test. owner of master
-- is not my own zone -- is not my own zone
hasMatch = false hasMatch = false
-- reasons = reasons .. "[sawnOwnership] "
end end
end end
@ -227,6 +230,7 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
-- only return spawners with this side -- only return spawners with this side
-- note: this will NOT work with neutral players -- note: this will NOT work with neutral players
hasMatch = false hasMatch = false
-- reasons = reasons .. "[rawOwner] "
end end
if not aSpawner.requestable then if not aSpawner.requestable then
@ -235,6 +239,9 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
if hasMatch then if hasMatch then
table.insert(theSpawners, aSpawner) table.insert(theSpawners, aSpawner)
-- trigger.action.outText("+++Spwn: ELIGIBLE spawner <" .. aSpawner.name .. ">", 30)
-- else
-- trigger.action.outText("+++Spwn: spawner <" .. aSpawner.name .. "> not eligible because " .. reasons, 30)
end end
end end
@ -327,6 +334,9 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
troopData.target = aSpawner.target -- can be nil! troopData.target = aSpawner.target -- can be nil!
troopData.tracker = theZone.trackWith -- taken from ZONE!!, can be nil troopData.tracker = theZone.trackWith -- taken from ZONE!!, can be nil
troopData.range = aSpawner.range troopData.range = aSpawner.range
troopData.code = aSpawner.code
troopData.drivable = aSpawner.drivable
cfxSpawnZones.spawnedGroups[theData.name] = troopData cfxSpawnZones.spawnedGroups[theData.name] = troopData
-- remember: orders are always lower case only -- remember: orders are always lower case only
@ -345,7 +355,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
AI.Option.Ground.val.ROE.WEAPON_HOLD, AI.Option.Ground.val.ROE.WEAPON_HOLD,
1.0) 1.0)
else else
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, aSpawner.range, aSpawner.orders, aSpawner.moveFormation) local newTroops = cfxGroundTroops.createGroundTroops(theGroup, aSpawner.range, aSpawner.orders, aSpawner.moveFormation, aSpawner.code, aSpawner.drivable)
cfxGroundTroops.addGroundTroopsToPool(newTroops) cfxGroundTroops.addGroundTroopsToPool(newTroops)
-- see if we have defined a target zone as destination -- see if we have defined a target zone as destination
@ -610,6 +620,7 @@ function cfxSpawnZones.loadData()
local range = gdTroop.range local range = gdTroop.range
local cty = gData.cty local cty = gData.cty
local cat = gData.cat local cat = gData.cat
local code = gdTroop.code
-- now spawn, but first -- now spawn, but first
-- add to my own attacker queue so we can save later -- add to my own attacker queue so we can save later

Binary file not shown.