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.version = "4.5.0"
cfxZones.version = "4.5.1"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
-- 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
@ -39,6 +39,7 @@ cfxZones.version = "4.5.0"
-4.5.0 - corrected bug in getBoolFromZoneProperty for default = false and "rnd"
- rnd in bool can have = xxx param for percentage
- getSmokeColorNumberFromZoneProperty()
-4.5.1 - moved processSimpleZoneDynamics to common
--]]--
@ -2934,6 +2935,9 @@ end
-- process <t>, <lat>, <lon>, <ele>, <mgrs>
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
-- replace <t> with current mission time HMS
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
outMsg = outMsg:gsub("<mgrs>", mgrs)
return outMsg
--]]--
end
-- process <v: flag>, <rsp: flag> <rrnd>

View File

@ -1,5 +1,5 @@
cloneZones = {}
cloneZones.version = "2.5.0"
cloneZones.version = "2.5.2"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
@ -60,6 +60,7 @@ cloneZones.respawnOnGroupID = true
2.5.0 - re-establish spawn zone in persistence to provide
empty! detection through saves (missed hasClones)
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
-- 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
local currSize = cloneZones.countLiveAIUnits(aZone)
if not aZone.oSize then aZone.oSize = 0 end
if aZone.oSize < 1 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)

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "3.1.5"
dcsCommon.version = "3.2.0"
--[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option
@ -34,12 +34,16 @@ dcsCommon.version = "3.1.5"
3.1.4 - new processStringWildcardsForUnit
- integrated into std wildcard proccing, unit optional
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
-- 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
@ -3396,7 +3400,7 @@ function dcsCommon.LSR(a, num)
end
--
-- string wildcards
-- string wildcard processing
--
function dcsCommon.processStringWildcards(inMsg, theUnit)
-- Replace STATIC bits of message like CR and zone name
@ -3414,18 +3418,17 @@ function dcsCommon.processStringWildcards(inMsg, theUnit)
return outMsg
end
-- <u>, <p>, <g>, <typ>, <c>, <e>, <twn>, <twnkm>, <twnnm>
function dcsCommon.processStringWildcardsForUnit(msg, theUnit)
local uName = theUnit:getName()
msg = msg:gsub("<u>", uName)
pName = "!AI!"
pName = "AI"
if dcsCommon.isPlayerUnit(theUnit) then
pName = theUnit:getPlayerName()
else
return
end
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 gName = theGroup:getName()
msg = msg:gsub("<g>", gName)
@ -3439,9 +3442,73 @@ function dcsCommon.processStringWildcardsForUnit(msg, theUnit)
e = e:lower()
msg = msg:gsub("<c>", coa)
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
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
--

View File

@ -7,7 +7,7 @@ fogger.requiredLibs = {
fogger.zones = {}
--[[-- 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.1.0 - added lcl attribute
@ -27,18 +27,13 @@ function fogger.createFogZone(theZone)
theZone.lcl = theZone:getBoolFromZoneProperty("lcl", false)
theZone.durMin, theZone.durMax = theZone:getPositiveRangeFromZoneProperty ("duration", 1, 1)
if theZone:hasProperty("onStart") then
--trigger.action.outText("+++fog: zone <" .. theZone.name .. "> HAS 'onStart' attribute", 30)
theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false)
if theZone.onStart then
if theZone.verbose then
trigger.action.outText("+++fog: will schedule onStart fog in zone <" .. theZone.name .. ">", 30)
end
timer.scheduleFunction(fogger.doFog, theZone, timer.getTime() + 0.5)
else
--trigger.action.outText("+++ fog: onstart turned OFF", 30)
end
else
--trigger.action.outText("+++fog: zone <" .. theZone.name .. "> no 'onStart' attribute, turned off", 30)
end
if theZone.verbose then
trigger.action.outText("+++fog: zone <" .. theZone.name .. "> processed.", 30)

View File

@ -1,5 +1,5 @@
cfxGroundTroops = {}
cfxGroundTroops.version = "2.2.1"
cfxGroundTroops.version = "3.0.0"
cfxGroundTroops.ups = 0.25 -- every 4 seconds
cfxGroundTroops.verbose = false
cfxGroundTroops.requiredLibs = {
@ -26,15 +26,8 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
--[[--
version history
2.0.0 - dmlZones
- 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
3.0.0 - support for troop-individual laser codes
- support for isDrivable CA troop spawns
an entry into the deployed troop table has the following attributes
- group - the group
@ -55,8 +48,10 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
- signature - "cfx" to tell apart from dcs groups
- range = range to look for enemies. default is 300m. In "laze" orders, range to laze
- 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
- code - laser code
- canDrive - for spawning if CA supported
-- reduced ups to 0.24, updating troops every 4 seconds is fast enough
@ -469,11 +464,13 @@ function cfxGroundTroops.trackLazer(troop)
end
if not troop.lazerPointer then
local code = troop.code
if not code then code = cfxGroundTroops.laseCode end -- default
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()
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")
end
troop.lastLazerSpot = there -- remember last spot
@ -918,7 +915,7 @@ end
-- createGroundTroop
-- 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 = {}
if not orders then
orders = "guard"
@ -927,7 +924,6 @@ function cfxGroundTroops.createGroundTroops(inGroup, range, orders, moveFormatio
if orders:lower() == "lase" then
orders = "laze" -- we use WRONG spelling here, cause we're cool. yeah, right.
end
-- trigger.action.outText("Enter createGT group <" .. inGroup:getName() .. "> with o=<" .. orders .. ">, mf=<" .. moveFormation .. ">", 30)
newTroops.insideDestination = false
newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled
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
if not range then range = 300 end
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
end

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "4.0.0"
cfxHeloTroops.version = "4.2.0"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -8,28 +8,20 @@ cfxHeloTroops.requestRange = 500 -- meters
--
--[[--
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
- enforceDropZones
- 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 = {
"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.troopWeight = 100 -- kg average weight per trooper
cfxHeloTroops.dropZones = {}
cfxHeloTroops.dropZones = {} -- dict
-- persistence support
cfxHeloTroops.deployedTroops = {}
@ -70,18 +62,17 @@ function cfxHeloTroops.resetConfig(conf)
conf.troopsOnBoard = {} -- table with the following
conf.troopsOnBoard.name = "***reset***"
conf.dropFormation = "circle_out" -- may be chosen later?
conf.timeStamp = timer.getTime() -- to avoid double-dipping
end
function cfxHeloTroops.createDefaultConfig(theUnit)
local conf = {}
cfxHeloTroops.resetConfig(conf)
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
return conf
end
function cfxHeloTroops.getUnitConfig(theUnit) -- will create new config if not existing
if not theUnit then
trigger.action.outText("+++WARNING: nil unit in get config!", 30)
@ -99,11 +90,10 @@ function cfxHeloTroops.getConfigForUnitNamed(aName)
return cfxHeloTroops.unitConfigs[aName]
end
--
--
-- LANDED
--
--
function cfxHeloTroops.loadClosestGroup(conf)
local p = conf.unit:getPosition().p
local cat = Group.Category.GROUND
@ -113,6 +103,11 @@ function cfxHeloTroops.loadClosestGroup(conf)
-- for now we only load troops with legal type strings
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
local numUnits = #unitsToLoad
if numUnits < 1 then return false end -- on false will drop through
@ -127,23 +122,32 @@ end
function cfxHeloTroops.heloLanded(theUnit)
-- when we have landed,
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end
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.currentState = 0
-- we look if we auto-unload
-- auto-unload
if conf.autoDrop then
if conf.troopsOnBoardNum > 0 then
cfxHeloTroops.doDeployTroops({conf, "autodrop"})
-- already called set menu, can exit directly
-- doDeployTroops() invokes set menu and empties troopsOnBoard
return
end
-- when we get here, we have 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
-- no troops to drop on board
end
if conf.autoPickup then
if conf.troopsOnBoardNum < 1 then
-- load the closest group
@ -152,49 +156,46 @@ function cfxHeloTroops.heloLanded(theUnit)
end
end
end
-- when we get here, we simply set the newest menus and are done
-- reset menu
cfxHeloTroops.removeComms(conf.unit)
cfxHeloTroops.setCommsMenu(conf.unit)
end
--
--
-- Helo took off
--
--
function cfxHeloTroops.heloDeparted(theUnit)
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end
-- when we take off, all that needs to be done is to change the state
-- to airborne, and then set the status flag
-- change the state to airborne, and update menus
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
cfxHeloTroops.removeComms(conf.unit)
cfxHeloTroops.setCommsMenu(conf.unit)
end
--
--
-- Helo Crashed
--
--
function cfxHeloTroops.cleanHelo(theUnit)
-- clean up
local conf = cfxHeloTroops.getUnitConfig(theUnit)
conf.unit = theUnit
conf.troopsOnBoardNum = 0 -- all dead
conf.currentState = -1 -- (we don't know)
-- check if we need to interface with groupTracker
if conf.troopsOnBoard.name and groupTracker then
local theName = conf.troopsOnBoard.name
-- there was (possibly) a group on board. see if it was tracked
local isTracking, numTracking, trackers = groupTracker.groupNameTrackedBy(theName)
-- if so, remove it from limbo
if isTracking then
for idx, theTracker in pairs(trackers) do
@ -215,11 +216,10 @@ function cfxHeloTroops.heloCrashed(theUnit)
cfxHeloTroops.cleanHelo(theUnit)
end
--
--
-- M E N U H A N D L I N G & R E S P O N S E
--
--
function cfxHeloTroops.clearCommsSubmenus(conf)
if conf.myCommands then
for i=1, #conf.myCommands do
@ -231,7 +231,6 @@ end
function cfxHeloTroops.removeCommsFromConfig(conf)
cfxHeloTroops.clearCommsSubmenus(conf)
if conf.myMainMenu then
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
conf.myMainMenu = nil
@ -241,20 +240,16 @@ end
function cfxHeloTroops.removeComms(theUnit)
if not theUnit then return end
if not theUnit:isExist() then return end
local group = theUnit:getGroup()
local id = group:getID()
local conf = cfxHeloTroops.getUnitConfig(theUnit)
conf.id = id
conf.unit = theUnit
cfxHeloTroops.removeCommsFromConfig(conf)
end
function cfxHeloTroops.addConfigMenu(conf)
-- we add the a menu showing current state
-- and the option to change fro auto drop
-- and auto pickup
-- we add a menu showing current configs
local onOff = "OFF"
if conf.autoDrop then onOff = "ON" end
local theCommand = missionCommands.addCommandForGroup(
@ -279,10 +274,12 @@ end
function cfxHeloTroops.setCommsMenu(theUnit)
-- 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:isExist() then return end
if not theUnit:isExist() then return end
-- we only add this menu to troop carriers
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then
if cfxHeloTroops.verbose then
@ -294,19 +291,16 @@ function cfxHeloTroops.setCommsMenu(theUnit)
local group = theUnit:getGroup()
local id = group:getID()
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.unit = theUnit -- link back
-- ok, first, if we don't have an F-10 menu, create one
-- if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(id, 'Airlift Troops')
end
-- clear out existing commands
-- clear out existing commands, add new
cfxHeloTroops.clearCommsSubmenus(conf)
-- now we have a menu without submenus.
-- add our own submenus
cfxHeloTroops.addConfigMenu(conf)
-- now see if we are on the ground or in the air
@ -317,22 +311,18 @@ function cfxHeloTroops.setCommsMenu(theUnit)
conf.currentState = 1
end
end
if conf.currentState == 0 then
cfxHeloTroops.addGroundMenu(conf)
else
cfxHeloTroops.addAirborneMenu(conf)
end
end
end
function cfxHeloTroops.addAirborneMenu(conf)
-- while we are airborne, there isn't much to do except add a status menu that does nothing
-- but we can add some instructions
-- let's begin by assuming no troops aboard
-- while airborne, add a status menu
local commandTxt = "(To load troops, land in proximity to them)"
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
local theCommand = missionCommands.addCommandForGroup(
conf.id,
@ -345,15 +335,21 @@ function cfxHeloTroops.addAirborneMenu(conf)
end
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
function cfxHeloTroops.addGroundMenu(conf)
-- this is the most complex menu. Player can deploy troops when loaded
-- and load troops when they are in proximity
-- Player can deploy troops when loaded
-- 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
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(
conf.id,
"Deploy Team <" .. conf.troopsOnBoard.name .. ">",
@ -362,10 +358,10 @@ function cfxHeloTroops.addGroundMenu(conf)
{conf, "deploy"}
)
table.insert(conf.myCommands, theCommand)
return
return -- no loading
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 mySide = conf.unit:getCoalition()
@ -385,10 +381,12 @@ function cfxHeloTroops.addGroundMenu(conf)
if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, aType) then
allLegal = false
-- trigger.action.outText("spawner <" .. aSpawner.name .. ">: troop type <" .. aType .. "> is illegal", 30)
end
else
if not dcsCommon.typeIsInfantry(aType) then
allLegal = false
-- trigger.action.outText("spawner <" .. aSpawner.name .. ">: troop type <" .. aType .. "> is not infantry", 30)
end
end
end
@ -431,11 +429,10 @@ function cfxHeloTroops.addGroundMenu(conf)
local numSpawners = #availableSpawners
if numSpawners > 5 then numSpawners = 5 end
while numSpawners > 0 do
-- for each spawner in range, create a
-- spawn menu item
-- for each spawner in range, create a menu item
local spawner = availableSpawners[numSpawners]
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(
conf.id,
comm,
@ -447,23 +444,25 @@ function cfxHeloTroops.addGroundMenu(conf)
numSpawners = numSpawners - 1
end
-- case 2B: no troops aboard. see if there are troops around
-- that we can load up
-- Collect troops in range that we can load up
local cat = Group.Category.GROUND
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
-- for now we simply only troopy with legal type strings
-- TODO: add weight filtering
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
local numUnits = #unitsToLoad
if numUnits > 5 then numUnits = 5 end
if numUnits < 1 then
local theCommand = missionCommands.addCommandForGroup(
local theCommand = missionCommands.addCommandForGroup(
conf.id,
"(No units in range)",
conf.myMainMenu,
@ -480,7 +479,7 @@ function cfxHeloTroops.addGroundMenu(conf)
local dist = aTeam.dist
local group = aTeam.group
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(
conf.id,
comm,
@ -530,10 +529,38 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
return filteredGroups
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
--
function cfxHeloTroops.redirectToggleConfig(args)
timer.scheduleFunction(cfxHeloTroops.doToggleConfig, args, timer.getTime() + 0.1)
end
@ -555,13 +582,10 @@ function cfxHeloTroops.doToggleConfig(args)
else
trigger.action.outTextForGroup(conf.id, "Troops will now board only after being ordered to do so", 30)
end
end
cfxHeloTroops.setCommsMenu(conf.unit)
end
--
-- Deploying Troops
--
@ -571,14 +595,13 @@ end
function cfxHeloTroops.scoreWhenCapturing(theUnit)
if theUnit and Unit.isExist(theUnit) and theUnit.getPlayerName then
-- see if wer are inside a non-alinged zone
-- and this includes a neutral zone
-- see if we are inside a non-alinged zone (incl. neutral)
local coa = theUnit:getCoalition()
local p = theUnit:getPoint()
local theGroup = theUnit:getGroup()
local ID = theGroup:getID()
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!
if nearestZone.owner ~= coa then
-- yup, combat drop!
@ -615,7 +638,6 @@ function cfxHeloTroops.doDeployTroops(args)
-- deploy the troops I have on board
cfxHeloTroops.deployTroopsFromHelicopter(conf)
-- interface with playerscore if we dropped
-- inside an enemy-owned zone
if cfxPlayerScore and cfxOwnedZones then
@ -627,7 +649,10 @@ function cfxHeloTroops.doDeployTroops(args)
conf.troopsOnBoardNum = 0
conf.troopsOnBoard = {}
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
cfxHeloTroops.removeComms(conf.unit)
cfxHeloTroops.setCommsMenu(conf.unit)
@ -635,15 +660,13 @@ end
function cfxHeloTroops.deployTroopsFromHelicopter(conf)
-- we have troops, drop them now
local unitTypes = {} -- build type names
local theUnit = conf.unit
local p = theUnit:getPoint()
-- split the conf.troopsOnBoardTypes into an array of types
unitTypes = dcsCommon.splitString(conf.troopsOnBoard.types, ",")
if #unitTypes < 1 then
table.insert(unitTypes, "Soldier M4") -- make it one m4 trooper as fallback
table.insert(unitTypes, "Soldier M4") -- fallback
end
local range = conf.troopsOnBoard.range
@ -651,6 +674,8 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local dest = conf.troopsOnBoard.destination
local theName = conf.troopsOnBoard.name
local moveFormation = conf.troopsOnBoard.moveFormation
local code = conf.troopsOnBoard.code
local canDrive = conf.troopsOnBoard.canDrive
if not orders then orders = "guard" end
orders = string.lower(orders)
@ -666,12 +691,14 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m radius around choppa
local theCoalition = theUnit:getGroup():getCoalition() -- make it chopper's COALITION
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
theCoalition,
theName, -- group name, may be tracked
chopperZone,
unitTypes,
unitTypes,
conf.dropFormation,
90)
90,
nil, -- liveries not yet supported
canDrive)
-- persistence management
local troopData = {}
troopData.groupData = theData
@ -681,7 +708,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
troopData.destination = dest -- only for attackzone orders
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
-- we get the target zone NOW!!! before we flip the zone and
-- and make them run to the wrong zone
@ -698,8 +725,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
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.outSoundForGroup(conf.id, cfxHeloTroops.disembarkSound)
-- see if this is tracked by a tracker, and pass them back so
-- they can un-limbo
-- if tracked by a tracker, and pass them back for un-limbo
if groupTracker then
local isTracking, numTracking, trackers = groupTracker.groupNameTrackedBy(theName)
if isTracking then
@ -712,7 +738,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
end
end
-- bang on all dropZones that we can find
-- bang on dropZones
for name, theZone in pairs(cfxHeloTroops.dropZones) do
-- can employ coalition test here as well, maybe later?
if theZone:isPointInsideZone(p) then
@ -766,9 +792,9 @@ function cfxHeloTroops.doLoadGroup(args)
conf.troopsOnBoard.name = gName
-- 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
-- to remember it even if it's destroyed
-- to remember it
if groupTracker then
-- only if groupTracker is active
local isTracking, numTracking, trackers = groupTracker.groupTrackedBy(group)
@ -786,7 +812,7 @@ function cfxHeloTroops.doLoadGroup(args)
-- then, remove it from the pool
local pooledGroup = cfxGroundTroops.getGroundTroopsForGroup(group)
if pooledGroup then
-- copy some important info from the troops
-- copy important info from the troops
-- if they are set
conf.troopsOnBoard.orders = pooledGroup.orders
conf.troopsOnBoard.range = pooledGroup.range
@ -795,29 +821,26 @@ function cfxHeloTroops.doLoadGroup(args)
if pooledGroup.orders and pooledGroup.orders == "captureandhold" then
conf.troopsOnBoard.destination = nil -- forget last destination so they can be helo-redeployed
end
conf.troopsOnBoard.code = pooledGroup.code
conf.troopsOnBoard.canDrive = pooledGroup.canDrive
cfxGroundTroops.removeTroopsFromPool(pooledGroup)
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
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT: ".. conf.troopsOnBoard.name .." was not committed to ground troops", 30)
end
end
-- now simply destroy the group
-- we'll re-assemble it when we deploy it
-- TODO: add weight changing code
-- TODO: ensure compatibility with CSAR module
group:destroy()
group:destroy()
-- now immediately run a GC so this group is removed
-- from any save data
cfxHeloTroops.GC()
-- say so
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
cfxHeloTroops.removeComms(conf.unit)
@ -840,9 +863,8 @@ end
function cfxHeloTroops.doSpawnGroup(args)
local conf = args[1]
local theSpawner = args[2]
-- NOTE: theSpawner can be of type cfxSpawnZone !!!OR!!! cfxCloneZones
-- make sure cooldown on spawner has timed out, else
-- notify that you have to wait
-- theSpawner can be of type cfxSpawnZone !!!OR!!! cfxCloneZones
-- make sure cooldown on spawner has timed out, else notify that you have to wait
local now = timer.getTime()
if now < (theSpawner.lastSpawnTimeStamp + theSpawner.cooldown) then
local delta = math.floor(theSpawner.lastSpawnTimeStamp + theSpawner.cooldown - now)
@ -850,15 +872,13 @@ function cfxHeloTroops.doSpawnGroup(args)
return
end
--cfxSpawnZones.spawnWithSpawner(theSpawner) -- old code
theSpawner.spawnWithSpawner(theSpawner) -- can be both spawner and cloner
theSpawner.spawnWithSpawner(theSpawner) -- can be both spawner and cloner (Lua "polymorphism"
trigger.action.outTextForGroup(conf.id, "Deploying <" .. theSpawner.baseName .. "> now...", 30)
-- reset all comms so we can include new troops
-- into load menu
timer.scheduleFunction(cfxHeloTroops.delayedCommsResetForUnit, {conf.unit, "ignore"}, now + 1.0)
end
--
-- handle events
--
@ -871,25 +891,21 @@ 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
-- only for helicopters -- overridedden by troop carriers
-- 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
-- only for troop carriers (not just helos any more)
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then
return
end
if theID == 4 then -- land
if theID == 4 or theID == 55 then -- land
cfxHeloTroops.heloLanded(theUnit)
end
if theID == 3 then -- take off
if theID == 3 or theID == 54 then -- take off
cfxHeloTroops.heloDeparted(theUnit)
end
if theID == 5 then -- crash
if theID == 5 or theID == 30 then -- crash or unitLost
cfxHeloTroops.heloCrashed(theUnit)
end
@ -1030,6 +1046,18 @@ function cfxHeloTroops.loadData()
local cty = gData.cty
local cat = gData.cat
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
if gdTroop.destination then
@ -1045,7 +1073,7 @@ function cfxHeloTroops.loadData()
-- post-proccing for cfxGroundTroops
-- add to groundTroops
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders, moveFormation, code, canDrive)
newTroops.destination = dest
cfxGroundTroops.addGroundTroopsToPool(newTroops)
end

View File

@ -1,26 +1,25 @@
jtacGrpUI = {}
jtacGrpUI.version = "3.1.0"
jtacGrpUI.version = "4.0.0"
jtacGrpUI.requiredLibs = {
"dcsCommon", -- always
"cfxZones",
"cfxGroundTroops",
}
--[[-- VERSION HISTORY
- 2.0.0 - dmlZones
- sanity checks upon load
- eliminated cfxPlayer dependence
- clean-up
- jtacSound
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.simpleCommands = true -- if true, f10 other invokes directly
function jtacGrpUI.resetConfig(conf)
end
@ -43,8 +42,7 @@ function jtacGrpUI.createDefaultConfig(theGroup)
return conf
end
-- getConfigFor group will allocate if doesn't exist in DB
-- and add to it
-- lazy init: allocate if doesn't exist in DB
function jtacGrpUI.getConfigForGroup(theGroup)
if not theGroup or (not Group.isExist(theGroup))then
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
function jtacGrpUI.getConfigForUnit(theUnit)
-- simple one-off step by accessing the group
function jtacGrpUI.getConfigForUnit(theUnit) -- lazy alloc
if not theUnit then
trigger.action.outText("+++WARNING: jtacGrpUI nil unit in getConfigForUnit!", 30)
return nil
@ -90,105 +87,49 @@ end
function jtacGrpUI.removeCommsFromConfig(conf)
jtacGrpUI.clearCommsSubmenus(conf)
if conf.myMainMenu then
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
conf.myMainMenu = nil
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)
if jtacGrpUI.jtacTypes == "all" or
jtacGrpUI.jtacTypes == "any" then return true end
if dcsCommon.stringStartsWith(jtacGrpUI.jtacTypes, "hel", true) then
if dcsCommon.stringStartsWith(jtacGrpUI.jtacTypes, "hel", true) then
local cat = theGroup:getCategory()
return cat == 1
end
if dcsCommon.stringStartsWith(jtacGrpUI.jtacTypes, "plan", true) then
if dcsCommon.stringStartsWith(jtacGrpUI.jtacTypes, "plan", true) then
local cat = theGroup:getCategory()
return cat == 0
end
-- note: no option for ground troops now
if jtacGrpUI.verbose then
trigger.action.outText("+++jGUI: unknown jtacTypes <" .. jtacGrpUI.jtacTypes .. "> -- allowing access to group <" .. theGroup:getName() ..">", 30)
end
return true -- for later expansion
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)
if not theGroup then return end
if not Group.isExist(theGroup) then return end
if not jtacGrpUI.isEligibleForMenu(theGroup) then return end
local mainMenu = nil
if jtacGrpUI.mainMenu then
mainMenu = radioMenu.getMainMenuFor(jtacGrpUI.mainMenu) -- nilling both next params will return menus[0]
mainMenu = radioMenu.getMainMenuFor(jtacGrpUI.mainMenu)
end
local conf = jtacGrpUI.getConfigForGroup(theGroup)
conf.id = theGroup:getID(); -- we always do this
if jtacGrpUI.simpleCommands then
-- 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
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)
local commandTxt = jtacGrpUI.menuName -- "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup(conf.id, commandTxt, mainMenu, jtacGrpUI.redirectCommandX, {conf, "lasing report"})
conf.myMainMenu = theCommand
end
function jtacGrpUI.addSubMenus(conf)
local commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup(
conf.id,
commandTxt,
conf.myMainMenu,
jtacGrpUI.redirectCommandX,
{conf, "lasing report"}
)
local theCommand = missionCommands.addCommandForGroup(conf.id, commandTxt, conf.myMainMenu, jtacGrpUI.redirectCommandX, {conf, "lasing report"})
table.insert(conf.myCommands, theCommand)
end
@ -197,8 +138,8 @@ function jtacGrpUI.redirectCommandX(args)
end
function jtacGrpUI.doCommandX(args)
local conf = args[1] -- < conf in here
local what = args[2] -- < second argument in here
local conf = args[1] -- conf
local what = args[2] -- "xyz" -- not used here
local theGroup = conf.theGroup
local targetList = jtacGrpUI.collectJTACtargets(conf, true)
-- iterate the list
@ -207,14 +148,33 @@ function jtacGrpUI.doCommandX(args)
trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound)
return
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
local aTarget = targetList[i]
if aTarget.idle then
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo ..": no target"
local theUnit = aTarget.source -- lazing unit
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
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
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
-- have an active (non-nil) lazeTarget and 'laze' orders
if not includeIdle then includeIdle = false end
local theJTACS = {}
for idx, troop in pairs(cfxGroundTroops.deployedTroops) do
if troop.coalition == conf.coalition
@ -252,31 +211,20 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
for idx, troop in pairs (theJTACS) do
local aTarget = {}
-- establish our location
aTarget.jtacName = troop.name
aTarget.posInfo = ""
if cfxOwnedZones and cfxOwnedZones.hasOwnedZones() then
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
aTarget.jtacName = troop.name -- group name
aTarget.code = troop.code
aTarget.source = dcsCommon.getFirstLivingUnit(troop.group)
-- we may get idlers, catch them now
if not troop.lazeTarget then
aTarget.idle = true
aTarget.range = math.huge
else
-- get the target we are lazing
-- proc target
local there = troop.lazeTarget:getPoint()
aTarget.idle = false
aTarget.range = dcsCommon.dist(here, there)
aTarget.range = aTarget.range * 0.000621371 -- meter to miles
aTarget.range = dcsCommon.dist(here, there) * 0.000621371 -- meter to miles
aTarget.range = math.floor(aTarget.range * 10) / 10
aTarget.bearing = dcsCommon.bearingInDegreesFromAtoB(here, there)
aTarget.lazeTargetType = troop.lazeTargetType
aTarget.lazeTarget = troop.lazeTarget
end
table.insert(targetList, aTarget)
end
@ -289,7 +237,7 @@ function jtacGrpUI.collectJTACtargets(conf, includeIdle)
end
--
-- event handler - simplified, only for player birth
-- event handler
--
function jtacGrpUI:onEvent(theEvent)
if not theEvent then return end
@ -315,12 +263,29 @@ function jtacGrpUI:onEvent(theEvent)
jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
end
jtacGrpUI.setCommsMenu(theGroup)
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
--
-- Start
--
@ -335,6 +300,7 @@ function jtacGrpUI.readConfigZone()
jtacGrpUI.jtacTypes = string.lower(jtacGrpUI.jtacTypes)
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
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
@ -350,6 +316,9 @@ function jtacGrpUI.readConfigZone()
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
end
@ -362,15 +331,7 @@ function jtacGrpUI.start()
if not dcsCommon.libCheck("cfx jtac GUI", jtacGrpUI.requiredLibs) then
return false
end
jtacGrpUI.readConfigZone()
local allPlayerUnits = dcsCommon.getAllExistingPlayerUnitsRaw()
for unitName, theUnit in pairs(allPlayerUnits) do
jtacGrpUI.setCommsMenuForUnit(theUnit)
end
-- now install event handler
world.addEventHandler(jtacGrpUI)
trigger.action.outText("cf/x jtacGrpUI v" .. jtacGrpUI.version .. " started", 30)
return true
@ -381,9 +342,3 @@ if not jtacGrpUI.start() then
trigger.action.outText("JTAC GUI failed to start up.", 30)
jtacGrpUI = nil
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
-- 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
--
@ -658,7 +658,7 @@ function cfxOwnedZones.collectZones(mode)
end
end
-- getNearestOwnedZoneToPoint invoked by heloTroops
-- getNearestOwnedZoneToPoint invoked by heloTroops and jtacGUI
function cfxOwnedZones.getNearestOwnedZoneToPoint(p)
local allZones = cfxOwnedZones.collectZones()
return cfxZones.getClosestZone(p, allZones)

View File

@ -1,5 +1,5 @@
cfxSpawnZones = {}
cfxSpawnZones.version = "2.2.0"
cfxSpawnZones.version = "3.0.0"
cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we
@ -18,20 +18,12 @@ cfxSpawnZones.spawnedGroups = {}
--
-- 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
2.0.0 - dmlZones
- moved "types" to spawner
- 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
3.0.0 - supports zone-individual laser code for "lase" orders
- drivable attribute passed to groundTroops
--]]--
@ -176,6 +168,12 @@ function cfxSpawnZones.createSpawner(inZone)
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
trigger.action.outText("+++spwn: created spawner for <" .. inZone.name .. ">", 30)
end
@ -207,19 +205,24 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
if not aSide then aSide = 0 end
if not aRange then aRange = 200 end
if not aPoint then return {} end
local theSpawners = {}
for aZone, aSpawner in pairs(cfxSpawnZones.allSpawners) do
-- iterate all zones and collect those that match
local hasMatch = true
local reasons = ""
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
-- check if side is correct for owned zone
if not cfxSpawnZones.verifySpawnOwnership(aSpawner) then
-- failed ownership test. owner of master
-- is not my own zone
hasMatch = false
-- reasons = reasons .. "[sawnOwnership] "
end
end
@ -227,6 +230,7 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
-- only return spawners with this side
-- note: this will NOT work with neutral players
hasMatch = false
-- reasons = reasons .. "[rawOwner] "
end
if not aSpawner.requestable then
@ -235,6 +239,9 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
if hasMatch then
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
@ -327,6 +334,9 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
troopData.target = aSpawner.target -- can be nil!
troopData.tracker = theZone.trackWith -- taken from ZONE!!, can be nil
troopData.range = aSpawner.range
troopData.code = aSpawner.code
troopData.drivable = aSpawner.drivable
cfxSpawnZones.spawnedGroups[theData.name] = troopData
-- remember: orders are always lower case only
@ -345,7 +355,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
AI.Option.Ground.val.ROE.WEAPON_HOLD,
1.0)
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)
-- see if we have defined a target zone as destination
@ -610,6 +620,7 @@ function cfxSpawnZones.loadData()
local range = gdTroop.range
local cty = gData.cty
local cat = gData.cat
local code = gdTroop.code
-- now spawn, but first
-- add to my own attacker queue so we can save later

Binary file not shown.