DML/modules/jtacGrpUI.lua
2022-01-19 20:55:23 +01:00

395 lines
12 KiB
Lua

jtacGrpUI = {}
jtacGrpUI.version = "1.0.2"
--[[-- VERSION HISTORY
- 1.0.2 - also include idling JTACS
- add positional info when using owned zones
--]]--
-- 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
jtacGrpUI.simpleCommands = true -- if true, f10 other invokes directly
--
-- C O N F I G H A N D L I N G
-- =============================
--
-- Each group has their own config block that can be used to
-- store group-private data and configuration items.
--
function jtacGrpUI.resetConfig(conf)
end
function jtacGrpUI.createDefaultConfig(theGroup)
local conf = {}
conf.theGroup = theGroup
conf.name = theGroup:getName()
conf.id = theGroup:getID()
conf.coalition = theGroup:getCoalition()
jtacGrpUI.resetConfig(conf)
conf.mainMenu = nil; -- this is where we store the main menu if we branch
conf.myCommands = nil; -- this is where we store the commands if we branch
return conf
end
-- getConfigFor group will allocate if doesn't exist in DB
-- and add to it
function jtacGrpUI.getConfigForGroup(theGroup)
if not theGroup then
trigger.action.outText("+++WARNING: jtacGrpUI nil group in getConfigForGroup!", 30)
return nil
end
local theName = theGroup:getName()
local c = jtacGrpUI.getConfigByGroupName(theName) -- we use central accessor
if not c then
c = jtacGrpUI.createDefaultConfig(theGroup)
jtacGrpUI.groupConfig[theName] = c -- should use central accessor...
end
return c
end
function jtacGrpUI.getConfigByGroupName(theName) -- DOES NOT allocate when not exist
if not theName then return nil end
return jtacGrpUI.groupConfig[theName]
end
function jtacGrpUI.getConfigForUnit(theUnit)
-- simple one-off step by accessing the group
if not theUnit then
trigger.action.outText("+++WARNING: jtacGrpUI nil unit in getConfigForUnit!", 30)
return nil
end
local theGroup = theUnit:getGroup()
return getConfigForGroup(theGroup)
end
--
--
-- M E N U H A N D L I N G
-- =========================
--
--
function jtacGrpUI.clearCommsSubmenus(conf)
if conf.myCommands then
for i=1, #conf.myCommands do
missionCommands.removeItemForGroup(conf.id, conf.myCommands[i])
end
end
conf.myCommands = {}
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 groups. may want to check if group
-- has disappeared
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
--
-- set main root in F10 Other. All sub menus click into this
--
function jtacGrpUI.isEligibleForMenu(theGroup)
return true
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)
-- depending on own load state, we set the command structure
-- it begins at 10-other, and has 'jtac' as main menu with submenus
-- as required
if not theGroup then return end
if not theGroup:isExist() then return end
-- we test here if this group qualifies for
-- the menu. if not, exit
if not jtacGrpUI.isEligibleForMenu(theGroup) then return end
local conf = jtacGrpUI.getConfigForGroup(theGroup)
conf.id = theGroup:getID(); -- we do this ALWAYS so it is current even after a crash
-- trigger.action.outText("+++ setting group <".. conf.theGroup:getName() .. "> jtac command", 30)
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,
nil,
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')
end
-- clear out existing commands
jtacGrpUI.clearCommsSubmenus(conf)
-- now we have a menu without submenus.
-- add our own submenus
jtacGrpUI.addSubMenus(conf)
end
function jtacGrpUI.addSubMenus(conf)
-- add menu items to choose from after
-- user clickedf on MAIN MENU. In this implementation
-- they all result invoked methods
local commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup(
conf.id,
commandTxt,
conf.myMainMenu,
jtacGrpUI.redirectCommandX,
{conf, "lasing report"}
)
table.insert(conf.myCommands, theCommand)
--[[--
commandTxt = "This is another important command"
theCommand = missionCommands.addCommandForGroup(
conf.id,
commandTxt,
conf.myMainMenu,
jtacGrpUI.redirectCommandX,
{conf, "Sub2"}
)
table.insert(conf.myCommands, theCommand)
--]]--
end
--
-- each menu item has a redirect and timed invoke to divorce from the
-- no-debug zone in the menu invocation. Delay is .1 seconds
--
function jtacGrpUI.redirectCommandX(args)
timer.scheduleFunction(jtacGrpUI.doCommandX, args, timer.getTime() + 0.1)
end
function jtacGrpUI.doCommandX(args)
local conf = args[1] -- < conf in here
local what = args[2] -- < second argument in here
local theGroup = conf.theGroup
-- trigger.action.outTextForGroup(conf.id, "+++ groupUI: processing comms menu for <" .. what .. ">", 30)
local targetList = jtacGrpUI.collectJTACtargets(conf, true)
-- iterate the list
if #targetList < 1 then
trigger.action.outTextForGroup(conf.id, "No targets are currently being lased", 30)
return
end
local desc = "JTAC Target Report:\n"
-- trigger.action.outTextForGroup(conf.id, "Target Report:", 30)
for i=1, #targetList do
local aTarget = targetList[i]
if aTarget.idle then
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo ..": no target"
else
desc = desc .. "\n" .. aTarget.jtacName .. aTarget.posInfo .." lasing " .. aTarget.lazeTargetType .. " [" .. aTarget.range .. "nm at " .. aTarget.bearing .. "°]"
end
end
trigger.action.outTextForGroup(conf.id, desc .. "\n", 30)
end
function jtacGrpUI.collectJTACtargets(conf, includeIdle)
-- iterate cfxGroundTroops.deployedTroops to retrieve all
-- 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
and troop.orders == "laze"
and troop.lazeTarget
and troop.lazeTarget:isExist()
then
table.insert(theJTACS, troop)
elseif troop.coalition == conf.coalition
and troop.orders == "laze"
and includeIdle
then
-- we also include idlers
table.insert(theJTACS, troop)
end
end
-- we now have a list of all ground troops that are lazing.
-- get bearing and range to targets, and sort them accordingly
local targetList = {}
local here = dcsCommon.getGroupLocation(conf.theGroup) -- this is me
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
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
if not troop.lazeTarget then
aTarget.idle = true
aTarget.range = math.huge
else
-- get the target we are lazing
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 = math.floor(aTarget.range * 10) / 10
aTarget.bearing = dcsCommon.bearingInDegreesFromAtoB(here, there)
--aTarget.jtacName = troop.name
aTarget.lazeTargetType = troop.lazeTargetType
end
table.insert(targetList, aTarget)
end
-- now sort by range
table.sort(targetList, function (left, right) return left.range < right.range end )
-- return list sorted by distance
return targetList
end
--
-- G R O U P M A N A G E M E N T
--
-- Group Management is required to make sure all groups
-- receive a comms menu and that they receive a clean-up
-- when required
--
-- Callbacks are provided by cfxPlayer module to which we
-- subscribe during init
--
function jtacGrpUI.playerChangeEvent(evType, description, player, data)
--trigger.action.outText("+++ groupUI: received <".. evType .. "> Event", 30)
if evType == "newGroup" then
-- initialized attributes are in data as follows
-- .group - new group
-- .name - new group's name
-- .primeUnit - the unit that trigggered new group appearing
-- .primeUnitName - name of prime unit
-- .id group ID
--theUnit = data.primeUnit
jtacGrpUI.setCommsMenu(data.group)
-- trigger.action.outText("+++ groupUI: added " .. theUnit:getName() .. " to comms menu", 30)
return
end
if evType == "removeGroup" then
-- data is the player record that no longer exists. it consists of
-- .name
-- we must remove the comms menu for this group else we try to add another one to this group later
local conf = jtacGrpUI.getConfigByGroupName(data.name)
if conf then
jtacGrpUI.removeCommsFromConfig(conf) -- remove menus
jtacGrpUI.resetConfig(conf) -- re-init this group for when it re-appears
else
trigger.action.outText("+++ jtacUI: can't retrieve group <" .. data.name .. "> config: not found!", 30)
end
return
end
if evType == "leave" then
-- player unit left. we don't care since we only work on group level
-- if they were the only, this is followed up by group disappeared
end
if evType == "unit" then
-- player changed units. almost never in MP, but possible in solo
-- because of 1 seconds timing loop
-- will result in a new group appearing and a group disappearing, so we are good
-- may need some logic to clean up old configs and/or menu items
end
end
--
-- Start
--
function jtacGrpUI.start()
-- iterate existing groups so we have a start situation
-- now iterate through all player groups and install the Assault Troop Menu
allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
-- contains per group player record. Does not resolve on unit level!
for gname, pgroup in pairs(allPlayerGroups) do
local theUnit = pgroup.primeUnit -- get any unit of that group
jtacGrpUI.setCommsMenuForUnit(theUnit) -- set up
end
-- now install the new group notifier to install Assault Troops menu
cfxPlayer.addMonitor(jtacGrpUI.playerChangeEvent)
trigger.action.outText("cf/x jtacGrpUI v" .. jtacGrpUI.version .. " started", 30)
end
--
-- GO GO GO
--
if not cfxGroundTroops then
trigger.action.outText("cf/x jtacGrpUI REQUIRES cfxGroundTroops to work.", 30)
else
jtacGrpUI.start()
end