My_Mission_Cau/Range_UI.1.3.lua
2025-11-10 16:13:45 +00:00

2345 lines
92 KiB
Lua

UI = {}
UI.Version = '1.3.03'
--###################################################################################################################################################
-- ############# DCS - Range Control User Interface for Target Impact Tracker Script 2.0 #################
-- ############# by Draken35 #################
--###################################################################################################################################################
--[[ Requirements:
>>> MIST 4.5.107 or above <https://github.com/mrSkortch/MissionScriptingTools/tree/master>
>>> TITS 2.3.00 or above
--]]
-----------------------------------------------------------------------------------------------------------------------------------------------------
--[[ Version history
1.0.00 04/06/2022 Development starts
1.0.01 05/03/2022 First release
1.0.02 05/08/2022 -fixed Apache automatic coordinates system detection
1.1.00 05/10/2022 - Uses TITS 2.1.x results API to report results instead of raw data tables
1.1.01 05/11/2022 - Fixed issue with popup script and new result reports
- replaced UI.MaxWFindRPT by I.MinWFgrpRPT
1.1.02 05/12/2022 - Added optional abort message when shooting before starting a pass
1.1.10 05/14/2022 - No mark designation will display the UI.Text1,UI.Text2 and UI.Text3, so they can be used to give a talk-on for the target.
- Coordinates are now optional in the No-mark and laser/IR designation designation. Toggle option in designation menu
1.2.00 05/15/2022 - Reorganization of Setting Menu
- Added optional Auto Pass Start and End
1.2.01 05/18/2022 - Attack Radial added to designation
- Added release heading to reports
- Added reports based on designated target
- Fixed and readded Pass BDA report
1.2.02 05/25/2022 - Fixed call to designated/grouped report from menu
1.2.03 07/10/2022 - Range comms improved for MP games
- display on coordenates in 3 systems at the same time
1.2.04 07/22/2022 - Fixed a crash in IR designation
1.3.00 07/27/2022 - Added support for roll-in position tracking
- Added illumination zones
1.3.01 07/30/2022 - Added talk-on fields to the laser/IR designation
1.3.02 08/04/2022 - improved initialization of Auto pass start and end
1.3.03 09/11/2022 - Lest we forget!
- Fixed issue with strafing reporting in missions with unlimited weapons.
A limitation is that it will only report passed with hits on target and it cannot report rate of hits.\
- Fixed issue with automatic range clearance trigger for AI units spawned inside the zones
--]]
-----------------------------------------------------------------------------------------------------------------------------------------------------
--[[ Pass Data return tables
Tables:
shellsFired[ShellType] = {
init = Shell Count at pass start
, fired = Shells fired during the pass
}
shellHits -- per target per shell type
shellHits[Target][ShellType] = Shell Hits Count
weaponsFired -- and their release params & designated target at the time, updated by shot event if TITS.onPass[_unitName] == true
weaponsFired[WeaponID] = {
weaponType
, releasePitch
, releaseYaw
, releaseRoll
, releaseHeading
, releaseSpeed
, releasePosition
, timeStamp
, inFlight
}
weaponHits -- per target per weapon , updated by hit event if TITS.onPass[_unitName] == true
weaponHits[WeaponID][Target] = timeStamp
impactData -- per target per weapon
impactData[WeaponID][Target] = {
targetPos
, impactPos
, impactDistance
, timeStamp
}
targetHealth -- per target. Health at the start and end of the pass
targetHealth[Target] = {
start_health
, end_health
}
--]]
--[[ Target List table
TargetList[name] = {
name -- UNIQUE! Target name
, displayName -- display name, accepts duplicates, if empty _TargetName will be used
, type -- allowed values [ 'unit' | 'zone' | 'static' ] must be in lower case
, respawn -- Only for units. if true, unit can be respawned. Respwan will only happen when ALL the units in the group are destroyed
, impact -- use target to calculate distance to impact
, strafing -- count strafing hits on target
, bda -- reports splash damage from weapons
, des_zone -- designation zone
, des_wp -- Allow use of smoke designation for target
, des_laser -- Allow use of laser designation for target
, des_ir -- Allow use of IR designation for target
, des_nomark -- Allow use of target designation with no marks (provides coordinates)
, list_coor -- Allow list of target coordinates
, uiNum1 -- Numeric value for custom UI use
, uiNum2 -- Numeric value for custom UI use
, uiNum3 -- Numeric value for custom UI use
, uiBool1 -- Boolean value for custom UI use
, uiBool2 -- Boolean value for custom UI use
, uiBool3 -- Boolean value for custom UI use
, uiText1 -- Text value for custom UI use
, uiText2 -- Text value for custom UI use
, uiText3 -- Text value for custom UI use
}
--]]
--###################################################################################################################################################
-- ############# Configuration section #################
--###################################################################################################################################################
UI.AutoPassStart = false
UI.AutoPassStartCoolDown = 30 -- seconds
UI.AutoPassEndTimeOut = 20 -- seconds
UI.AutoPassEnd = false -- If true, pass will automatically end if there aren't any weapons in the air and
-- UI.AutoPassEndTimeOut seconds had passed since the last shell was fired or weapon impact
-- was recorded
UI.MagneticVar = 0 -- Magnetic variation for the map and date, used for designation attack radial calculation. East is +
UI.SpeedUnitsDefault = 1 -- 1 = Knots, 2 = Mph, 3 = Kph
UI.DistanceUnitsDefault = 3 -- 1 = feet, 2 = yards, 3 = meters
UI.AltitudeUnitsDefault = 1 -- 1 = feet, 3 = meters ( 2 is not used)
UI.CoordinateFormatDefault = 2 -- 1 = MGRS, 2 = DMS, 3 = DM.mm
UI.DisplayReleaseDataDefault = true -- default for the option to display the release parameters in results
UI.DisplayAbortMsgDefault = true -- if true, display abort message when shooting before starting a pass
UI.DisplayCoordsDefault = true -- if true, display coordinates in designation messages
UI.desUseAttackRadialDefault = false -- add an attack radial requiment if true
UI.DefaultResultReport = 0 -- 0 = Automatic
-- 1 = Impact results (closest/individual)
-- 2 = Impact results (closest/grouped)
-- 3 = BDA Summary
-- 4 = Impact results (designated/individual)
UI.MinWFgrpRPT = 6 -- Minimun number of weapons fired in pass to display grouped report in automatic mode
UI.minWPmarkDist = 50 -- meters. Minimum distance from target to mark using WP
UI.maxWPmarkDist = 150 -- meters. Maximum distance from target to mark using WP
UI.AutoSetCoordinates = true -- if true use player's unit type to set coordinates, else use MPT.CoordinateFormatDefault
UI.coordXtype = {}
UI.coordXtype['AV8BNA'] = 1 -- 1 = MGRS, 2 = DMS, 3 = DM.mm
UI.coordXtype['A-10C_2'] = 1
UI.coordXtype['AH-64D_BLK_II'] = 1
UI.coordXtype['F-16C_50'] = 3
UI.coordXtype['FA-18C_hornet'] = 1
UI.coordXtype['M-2000C'] = 3
UI.messageBeep = '204521__redoper__roger-beep.ogg' -- Message alert sound. leave empty for no sound
--#################################################################################################################################################
-- ############# Initialization of globals ###############
--#################################################################################################################################################
UI.unitConfig = {}
UI.menuAddedToGroup = {}
UI.debugMarkcenter = false
UI.DesignationZones = {}
UI.DesignationZones[1] = { id = 1, name = 'Default'}
UI.Designation = {
Designator = '' -- unit name
, Designated = '' -- target name
, type = 0 -- 0 = none, 1 = WP , 2 = laser, 3 = IR, 4 = No mark, 23 = laser/IR
, zone = 1
, briefing = ''
, ray1 = nil
, ray2 = nil
, AttackRadial = 0
}
UI.Designators = {}
UI.engagementZones = {}
UI.illuminationZones = {}
UI.illuminating = false
--#################################################################################################################################################
-- ############# UI code ###############
--#################################################################################################################################################
UI.EventHandler = {}
function UI.EventHandler:onEvent(_eventDCS)
if _eventDCS == nil or _eventDCS.initiator == nil then
return true
end
local status, err = pcall(function(_event)
if (_event.id == world.event.S_EVENT_BIRTH or _event.id == world.event.S_EVENT_TAKEOFF
or _event.id == world.event.S_EVENT_ENGINE_STARTUP) and _event.initiator:getPlayerName() then
UI.initUnit(_event.initiator:getName())
elseif ((_event.id == world.event.S_EVENT_SHOT) or (_event.id == world.event.S_EVENT_SHOOTING_START) ) and _event.initiator:getPlayerName() then -- id == 1 Shot
local _unitName = _event.initiator:getName()
local cfg = UI.unitConfig[_unitName]
if not TITS.onPass[_unitName] and cfg.abortMSG then
UI.messageToUnit(_unitName,"Abort! You are not cleared to fired!",{},10)
end
end --_event.id ==
-- ### End event processing
return true
end, _eventDCS) -- local status, err = pcall(function(_event)
if (not status) then
local msg = string.format("Target Impact Tracker UI (v%s): Error while handling event %s",UI.Version, err)
env.error(msg,false)
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.initUnit(unitName)
local unit = Unit.getByName(unitName)
local groupID = unit:getGroup():getID()
local rConfig = {
SpeedUOM = UI.SpeedUnitsDefault
, DistanceUOM = UI.DistanceUnitsDefault
, AltitudeUOM = UI.AltitudeUnitsDefault
, CoordFormat = UI.CoordinateFormatDefault
, DisplayReleaseData = UI.DisplayReleaseDataDefault
, rootPath = nil
, rollInPath = nil
, offPath = nil
, resultsRPT = UI.DefaultResultReport
, unitType = unit:getTypeName()
, abortMSG = UI.DisplayAbortMsgDefault
, desDisplayCoords = UI.DisplayCoordsDefault
, desUseAttackRadial = UI.desUseAttackRadialDefault
, lastPassEnd = timer.getTime()
, firstPass = true
}
if UI.coordXtype[rConfig.unitType] and UI.AutoSetCoordinates then
rConfig.CoordFormat = UI.coordXtype[rConfig.unitType]
end
if UI.menuAddedToGroup[groupID] == {} then
UI.menuAddedToGroup[groupID] = false
end
if not UI.menuAddedToGroup[groupID] then
-- Add Menu
rConfig.rootPath = missionCommands.addSubMenuForGroup(groupID, "Range") -- Root level
local _reportsPath = missionCommands.addSubMenuForGroup(groupID,"Reports", rConfig.rootPath)
missionCommands.addCommandForGroup(groupID,"List coordinates" , _reportsPath, UI.rptListCoord , unitName)
missionCommands.addCommandForGroup(groupID,"Results (closest/individual)" , _reportsPath, UI.rptImpactResults , unitName)
missionCommands.addCommandForGroup(groupID,"Results (closest/grouped)" , _reportsPath, UI.rptImpactResultsGrouped , unitName)
missionCommands.addCommandForGroup(groupID,"Results (designated/individual)" , _reportsPath, UI.rptImpactResultsDes , unitName)
missionCommands.addCommandForGroup(groupID,"Results (designated/grouped)" , _reportsPath, UI.rptImpactResultsDesGrouped , unitName)
missionCommands.addCommandForGroup(groupID,"BDA Pass summary" , _reportsPath, UI.rptBDAsummary , unitName)
missionCommands.addCommandForGroup(groupID,"Weapons in flight" , _reportsPath, UI.rptWeaponsInFlight , unitName)
--missionCommands.addCommandForGroup(groupID,"Debug: Inspect Data" , _reportsPath, UI.debugPassData , unitName)
local _designationPath = missionCommands.addSubMenuForGroup(groupID, "Designation", rConfig.rootPath)
missionCommands.addCommandForGroup(groupID,"New smoke (WP) designation" , _designationPath, UI.desWP , {unitName, true})
missionCommands.addCommandForGroup(groupID,"Repeat smoke (WP) mark" , _designationPath, UI.desWP , {unitName, false})
missionCommands.addCommandForGroup(groupID,"New Laser designation" , _designationPath, UI.desLaserIR , {unitName,'laser'})
missionCommands.addCommandForGroup(groupID,"New IR pointer designation" , _designationPath, UI.desLaserIR , {unitName,'IR'})
missionCommands.addCommandForGroup(groupID,"New Laser/IR designation" , _designationPath, UI.desLaserIR , {unitName,'laser/IR'})
missionCommands.addCommandForGroup(groupID,"New No Mark designation" , _designationPath, UI.desNM , unitName)
missionCommands.addCommandForGroup(groupID,"Cancel designation" , _designationPath, UI.desCancel , unitName)
missionCommands.addCommandForGroup(groupID,"Repeat briefing" , _designationPath, UI.desRPTBrief , unitName)
----------------------------------------------------------------------------------------
local _settingsPath = missionCommands.addSubMenuForGroup(groupID, "Settings", rConfig.rootPath)
missionCommands.addCommandForGroup(groupID,"Display current settings" , _settingsPath, UI.Settings , {unitName,0})
missionCommands.addCommandForGroup(groupID,"Toggle illumination" , _settingsPath, UI.Settings , {unitName,70})
missionCommands.addCommandForGroup(groupID,"Respawn targets" , _settingsPath, UI.Settings , {unitName,66})
missionCommands.addCommandForGroup(groupID,"Toggle abort message" , _settingsPath, UI.Settings , {unitName,1})
missionCommands.addCommandForGroup(groupID,"Toggle display of Release data" , _settingsPath, UI.Settings , {unitName,2})
local _settings6Path = missionCommands.addSubMenuForGroup(groupID, "Designation", _settingsPath)
local _designation1Path = missionCommands.addSubMenuForGroup(groupID, "Select designation zone", _settings6Path)
for _,z in pairs(UI.DesignationZones) do
local n = string.format('%s', z.name)
missionCommands.addCommandForGroup(groupID,n , _designation1Path, UI.desSetZone , {unitName,z.id})
end
missionCommands.addCommandForGroup(groupID,"Toggle attack radial" , _settings6Path, UI.Settings , {unitName,31})
missionCommands.addCommandForGroup(groupID,"Toggle coods. display" , _settings6Path, UI.Settings , {unitName,30})
local _settings1Path = missionCommands.addSubMenuForGroup(groupID, "Distance units", _settingsPath)
missionCommands.addCommandForGroup(groupID,"Distance: set to meters" , _settings1Path, UI.Settings , {unitName,3})
missionCommands.addCommandForGroup(groupID,"Distance: set to feet" , _settings1Path, UI.Settings , {unitName,4})
missionCommands.addCommandForGroup(groupID,"Distance: set to yards" , _settings1Path, UI.Settings , {unitName,5})
local _settings2Path = missionCommands.addSubMenuForGroup(groupID, "Altitude units", _settingsPath)
missionCommands.addCommandForGroup(groupID,"Altitude: set to meters" , _settings2Path, UI.Settings , {unitName,6})
missionCommands.addCommandForGroup(groupID,"Altitude: set to feet" , _settings2Path, UI.Settings , {unitName,7})
local _settings3Path = missionCommands.addSubMenuForGroup(groupID, "Speed units", _settingsPath)
missionCommands.addCommandForGroup(groupID,"Speed: set to kph" , _settings3Path, UI.Settings , {unitName,8})
missionCommands.addCommandForGroup(groupID,"Speed: set to knots" , _settings3Path, UI.Settings , {unitName,9})
missionCommands.addCommandForGroup(groupID,"Speed: set to mph" , _settings3Path, UI.Settings , {unitName,10})
--local _settings4Path = missionCommands.addSubMenuForGroup(groupID, "Coordinates format", _settingsPath)
-- missionCommands.addCommandForGroup(groupID,"Coord. format set to MGRS" , _settings4Path, UI.Settings , {unitName,11})
-- missionCommands.addCommandForGroup(groupID,"Coord. format set to DMS" , _settings4Path, UI.Settings , {unitName,12})
-- missionCommands.addCommandForGroup(groupID,"Coord. format set to DM.mm" , _settings4Path, UI.Settings , {unitName,13})
local _settings5Path = missionCommands.addSubMenuForGroup(groupID, "Results report", _settingsPath)
missionCommands.addCommandForGroup(groupID,"Automatic selection" , _settings5Path, UI.Settings , {unitName,20})
missionCommands.addCommandForGroup(groupID,"Impact results (individual)" , _settings5Path, UI.Settings , {unitName,21})
missionCommands.addCommandForGroup(groupID,"Impact results (grouped)" , _settings5Path, UI.Settings , {unitName,22})
missionCommands.addCommandForGroup(groupID,"BDA Pass summary" , _settings5Path, UI.Settings , {unitName,23})
----------------------------------------------------------------------------------------
rConfig.rollInPath = missionCommands.addCommandForGroup(groupID,"Rolling in" , rConfig.rootPath , UI.passStartEnd, {unitName,'in'})
UI.menuAddedToGroup[groupID] = true
UI.unitConfig[unitName] = rConfig
UI.Settings({unitName,0})
if UI.AutoPassStart then
-- launch pass end checker
local params = {unitName = unitName}
timer.scheduleFunction(UI.passStartCheck, params, timer.getTime() + 1 )
end
if UI.AutoPassEnd then
-- launch pass end checker
local params = {unitName = unitName}
timer.scheduleFunction(UI.passEndCheck, params, timer.getTime() + 1 )
end
end --if not UI.menuAddedToGroup[groupID] then
return true
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.passStartCheck(params, time)
if not TITS.onPass[params.unitName] then
if UI.rowCount(UI.engagementZones) > 0 then
local inZone = false
for z,t in pairs(UI.engagementZones) do
local ut = mist.makeUnitTable({params.unitName})
if t == 'zone' then
local u = mist.getUnitsInZones(ut, {z},'cylinder')
inZone = inZone or (UI.rowCount(u) > 0)
else
local p = mist.getGroupPoints(z)
local u = mist.getUnitsInPolygon(ut, p)
inZone = inZone or (UI.rowCount(u) > 0)
end
end -- for z,t in pairs(UI.engagementZones) do
if inZone then
local cfg = UI.unitConfig[params.unitName]
if (timer.getTime() - cfg.lastPassEnd ) >= UI.AutoPassStartCoolDown or cfg.firstPass then
cfg.firstPass = false
UI.passStartEnd({params.unitName, 'in'})
end
end
end --if UI.rowCount(UI.engagementZones) > 0 then
end -- if not TITS.onPass[params.unitName] then
return timer.getTime() + 1
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.passEndCheck(params, time)
if TITS.onPass[params.unitName] then
local passData = TITS.passGetData(params.unitName)
local wf = TITS.getWeponsFired(params.unitName)
local sf = TITS.getShellsFired(params.unitName)
if wf.count > 0 or sf.total > 0 then
if not TITS.weaponsInFlight(params.unitName) then
local cTime = timer.getTime()
if (cTime - passData.lastShellFired) > UI.AutoPassEndTimeOut and
(cTime - passData.lastImpact) > UI.AutoPassEndTimeOut then
UI.passStartEnd({params.unitName, 'off'})
end -- if (cTime - passData.lastShellFired) > UI.AutoPassEndTimeOut and
end -- if not TITS.weaponsInFlight(params.unitName) then
end -- if wf.count > 0 or sf.count > 0 then
end -- if TITS.onPass[params.unitName] then
return timer.getTime() + 1
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.AddEngagementZone(_name, _type)
UI.engagementZones[_name] = _type
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.Settings(_args)
local unitName = _args[1]
local setting = _args[2]
local cfg = UI.unitConfig[unitName]
if setting == 0 then -- Display settings
local _msg = ''
_msg = string.format ('Flying: %s',cfg.unitType)
if cfg.abortMSG then
_msg = string.format ('%s\nAbort message is ON',_msg)
else
_msg = string.format ('%s\nAbort message is OFF',_msg)
end
if cfg.DisplayReleaseData then
_msg = string.format ('%s\nRelease data display is ON',_msg)
else
_msg = string.format ('%s\nRelease data display is OFF',_msg)
end
if cfg.DistanceUOM == 3 then -- Distance meters
_msg = string.format ('%s\nDistance: Using meters',_msg)
elseif cfg.DistanceUOM == 2 then --Distance feet
_msg = string.format ('%s\nDistance: Using feet',_msg)
else -- Distance yards
_msg = string.format ('%s\nDistance: Using yards',_msg)
end
if cfg.AltitudeUOM == 3 then -- Altitude meters
_msg = string.format ('%s\nAltitude: Using meters',_msg)
else
_msg = string.format ('%s\nAltitude: Using feet',_msg)
end
if cfg.SpeedUOM == 3 then -- Speed kph
_msg = string.format ('%s\nSpeed: Using kph',_msg)
elseif cfg.SpeedUOM == 1 then -- Speed kt
_msg = string.format ('%s\nSpeed: Using knots',_msg)
else -- Speed mph
_msg = string.format ('%s\nSpeed: Using mph',_msg)
end
--[[
if cfg.CoordFormat == 1 then -- Coordinate format MGRS
_msg = string.format ('%s\nCoordinate format: Using MGRS',_msg)
elseif cfg.CoordFormat == 2 then -- Coordinate format DMS
_msg = string.format ('%s\nCoordinate format: Using DMS',_msg)
else -- Coordinate format DM.mm
_msg = string.format ('%s\nCoordinate format: Using DM.mm',_msg)
end
--]]
if cfg.resultsRPT == 0 then
_msg = string.format ('%s\nResults using Automatic selection',_msg)
elseif cfg.resultsRPT == 1 then --
_msg = string.format ('%s\nResults using Impact results (individual) report',_msg)
elseif cfg.resultsRPT == 2 then
_msg = string.format ('%s\nResults using Impact results (grouped) report',_msg)
elseif cfg.resultsRPT == 3 then
_msg = string.format ('%s\nResults using BDA Pass Summary report',_msg)
end
_msg = string.format ('%s\nDesignation zone: %s',_msg,UI.DesignationZones[UI.Designation.zone].name)
if cfg.desDisplayCoords then
_msg = string.format ('%s\nDisplay coordinates in designation msgs is ON',_msg)
else
_msg = string.format ('%s\nDisplay coordinates in designation msgs is OFF',_msg)
end
if cfg.desUseAttackRadial then
_msg = string.format ('%s\nRequire attack radial in designation is ON',_msg)
else
_msg = string.format ('%s\nRequire attack radial in designation is OFF',_msg)
end
UI.messageToUnit(unitName,_msg,{},10,false)
elseif setting == 1 then -- Toggle display of Abort message
cfg.abortMSG = not cfg.abortMSG
if cfg.abortMSG then
UI.messageToUnit(unitName,'Abort message is ON',false)
else
UI.messageToUnit(unitName,'Abort message is OFF',false)
end
elseif setting == 2 then -- Toggle display of Release data
cfg.DisplayReleaseData = not cfg.DisplayReleaseData
if cfg.DisplayReleaseData then
UI.messageToUnit(unitName,'Release data display is ON',false)
else
UI.messageToUnit(unitName,'Release data display is OFF',false)
end
-- Distancce
elseif setting == 3 then -- Distance meters
cfg.DistanceUOM = 3 -- 1 = feet, 2 = yards, 3 = meters
UI.messageToUnit(unitName,'Distance: Using meters',false)
elseif setting == 4 then --Distance feet
cfg.DistanceUOM = 1 -- 1 = feet, 2 = yards, 3 = meters
UI.messageToUnit(unitName,'Distance: Using feet',false)
elseif setting == 5 then -- Distance yards
cfg.DistanceUOM = 2 --1 = feet, 2 = yards, 3 = meters
UI.messageToUnit(unitName,'Distance: Using yards',false)
-- Altitude
elseif setting == 6 then -- Altitude meters
cfg.AltitudeUOM = 3 -- 1 = feet, 3 = meters
UI.messageToUnit(unitName,'Altitude: Using meters',false)
elseif setting == 7 then -- Altitude feet
cfg.AltitudeUOM = 1 -- 1 = feet, 3 = meters
UI.messageToUnit(unitName,'Altitude: Using feet',false)
-- Speed
elseif setting == 8 then -- Speed kph
cfg.SpeedUOM = 3 -- 1 = Knots, 2 = Mph, 3 = Kph
UI.messageToUnit(unitName,'Speed: Using kph',false)
elseif setting == 9 then -- Speed kt
cfg.SpeedUOM= 1 -- 1 = Knots, 2 = Mph, 3 = Kph
UI.messageToUnit(unitName,'Speed: Using knots',false)
elseif setting == 10 then -- Speed mph
cfg.SpeedUOM = 2 -- 1 = Knots, 2 = Mph, 3 = Kph
UI.messageToUnit(unitName,'Speed: Using mph',false)
-- Coordinate format
--[[
elseif setting == 11 then -- Coordinate format MGRS
cfg.CoordFormat = 1 -- 1 = MGRS, 2 = DMS, 3 = DM.mm
UI.messageToUnit(unitName,'Coordinate format: Using MGRS',false)
elseif setting == 12 then -- Coordinate format DMS
cfg.CoordFormat = 2 -- 1 = MGRS, 2 = DMS, 3 = DM.mm
UI.messageToUnit(unitName,'Coordinate format: Using DMS',false)
elseif setting == 13 then -- Coordinate format DM.mm
cfg.CoordFormat = 3 -- 1 = MGRS, 2 = DMS, 3 = DM.mm
UI.messageToUnit(unitName,'Coordinate format: Using DM.mm',false)
--]]
elseif setting == 20 then -- Results reports
cfg.resultsRPT = 0
UI.messageToUnit(unitName,'Results using Automatic selection',false)
elseif setting == 21 then -- Results reports
cfg.resultsRPT = 1
UI.messageToUnit(unitName,'Results using Impact results (individual) report',false)
elseif setting == 22 then -- Results reports
cfg.resultsRPT = 2
UI.messageToUnit(unitName,'Results using Impact results (grouped) report',false)
elseif setting == 23 then -- Results reports
cfg.resultsRPT = 3
UI.messageToUnit(unitName,'Results using BDA Pass Summary report',false)
elseif setting == 30 then -- Toggle coords display in designation
cfg.desDisplayCoords = not cfg.desDisplayCoords
if cfg.desDisplayCoords then
UI.messageToUnit(unitName,'Display coordinates in designation msgs is ON',false)
else
UI.messageToUnit(unitName,'Display coordinates in designation msgs is OFF',false)
end
elseif setting == 31 then -- Toggle coords display in designation
cfg.desUseAttackRadial = not cfg.desUseAttackRadial
if cfg.desUseAttackRadial then
UI.messageToUnit(unitName,'Require attack radial in designation is ON',false)
else
UI.messageToUnit(unitName,'Require attack radial in designation is OFF',false)
end
elseif setting == 66 then -- respawn test
for u,_ in pairs(TITS.TargetList) do
TITS.respawnTarget(u)
end -- for u,v in pairs(TITS.TargetList) do
elseif setting == 70 then -- toggle illumination
UI.illuminating = not UI.illuminating
if UI.illuminating then
trigger.action.outText( 'Starting illumination', 10 )
timer.scheduleFunction(UI.illuminate, {}, timer.getTime() + 1 )
else
trigger.action.outText( 'Stoping illumination', 10 )
end
end -- main if
return true
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.messageToUnit(_unitName,_messageText,_soundTable,_displayTime,_displayToAll)
--[[
local msg = {}
msg.text = _messageText
msg.msgFor = {units = {_unitName}}
if TITS.messageBeep ~= '' then
msg.sound = TITS.messageBeep
end
if _soundTable ~= nil then
msg.multSound = _soundTable
end
if _displayTime == nil then
_displayTime = 5
end
msg.displayTime = _displayTime
mist.message.add(msg)
--]]
-- bypass mist because of message history spamming
if _displayToAll == nil then
_displayToAll = true
end
if _displayTime == nil then
_displayTime = 5
end
local _unit = Unit.getByName(_unitName)
local _groupId = _unit:getGroup():getID()
local _callSign = _unit:getCallsign()
if UI.messageBeep ~= '' then
if _displayToAll then
trigger.action.outSound(UI.messageBeep)
else
trigger.action.outSoundForGroup(_groupId,UI.messageBeep)
end
end
local _msg = string.format('(%s): %s',_callSign,_messageText)
if _displayToAll then
trigger.action.outText( _msg, _displayTime)
else
trigger.action.outTextForGroup(_groupId, _msg, _displayTime)
end
return true
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.passStartEnd(_args)
local unitName = _args[1]
local value = _args[2]
--local groupID = Unit.getByName(unitName):getGroup():getID()
local unit = Unit.getByName(unitName)
local groupID = unit:getGroup():getID()
if unit:getPlayerName() then
if value == 'in' then
UI.messageToUnit(unitName,'Cleared in hot',{},10)
-- reset prior attack results
TITS.passInit(unitName)
TITS.passStart(unitName)
UI.toggleAttackMenu(unitName, "in")
if pcall(function() return PTC.Version end ) then
PTC.passTargetsUP = 0
PTC.passTargetsHIT = 0
end
else -- value == 'off'
local cfg = UI.unitConfig[unitName]
local canEnd = TITS.passEnd(unitName)
cfg.lastPassEnd = timer.getTime()
if canEnd then
local cfg = UI.unitConfig[unitName]
UI.messageToUnit(unitName,'Standby for report',{},10)
-- Show results
if cfg.resultsRPT == 0 then -- automatic
local passData = TITS.passGetData(unitName)
local wf = passData.weaponsFired
if UI.rowCount(wf) >= UI.MinWFgrpRPT then
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
UI.rptImpactResultsDesGrouped(unitName)
else
UI.rptImpactResultsGrouped(unitName)
end
else
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
UI.rptImpactResultsDes(unitName)
else
UI.rptImpactResults(unitName)
end
end
elseif cfg.resultsRPT == 1 then
UI.rptImpactResults(unitName)
elseif cfg.resultsRPT == 2 then
UI.rptImpactResultsGrouped(unitName)
elseif cfg.resultsRPT == 3 then
UI.rptBDAsummary(unitName)
end -- if UI.DefaultResultReport
UI.toggleAttackMenu(unitName, "off")
else
UI.messageToUnit(unitName,'Standby, weapons still in the air',{},10)
end
end
end --if unit:getPlayerName() then
return true
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.toggleAttackMenu(unitName, action)
local unit = Unit.getByName(unitName)
local groupID = unit:getGroup():getID()
local cfg = UI.unitConfig[unitName]
if action == 'in' then
missionCommands.removeItemForGroup(groupID, cfg.rollInPath)
cfg.offPath = missionCommands.addCommandForGroup(groupID,"Off - attack completed" , cfg.rootPath, UI.passStartEnd , {unitName,'off'})
else
missionCommands.removeItemForGroup(groupID, cfg.offPath)
cfg.rollInPath= missionCommands.addCommandForGroup(groupID,"Rolling in" , cfg.rootPath, UI.passStartEnd , {unitName,'in'})
end -- if action...
return true
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.debugPassData(_unitName)
local d = TITS.passGetData(_unitName)
--local unit = Unit.getByName(_unitName)
--local ammoTable = unit:getAmmo()
--trigger.action.outText("ammoTable", 1)
--trigger.action.outText(mist.utils.tableShow(ammoTable), 1)
--trigger.action.outText("TargetList", 1)
--trigger.action.outText(mist.utils.tableShow(TITS.TargetList), 1)
--trigger.action.outText("targetHealth", 1)
--trigger.action.outText(mist.utils.tableShow(d.targetHealth), 1)
--trigger.action.outText("pass data", 1)
--trigger.action.outText(mist.utils.tableShow(d), 1)
trigger.action.outText("shellsFired", 1)
trigger.action.outText(mist.utils.tableShow(d.shellsFired), 1)
trigger.action.outText("shellHits", 1)
trigger.action.outText(mist.utils.tableShow(d.shellHits), 1)
trigger.action.outText("shellDisplayName", 1)
trigger.action.outText(mist.utils.tableShow(d.shellDisplayName), 1)
trigger.action.outText("getShellsFired", 1)
local shellsFired = TITS.getShellsFired(_unitName)
trigger.action.outText(mist.utils.tableShow(shellsFired), 1)
--trigger.action.outText("weaponHits", 1)
--trigger.action.outText(mist.utils.tableShow(d.weaponHits), 1)
--trigger.action.outText("weaponsFired", 1)
--trigger.action.outText(mist.utils.tableShow(d.weaponsFired), 1)
--trigger.action.outText("impactData", 1)
--trigger.action.outText(mist.utils.tableShow(d.impactData), 1)
--trigger.action.outText("groups", 1)
--trigger.action.outText(mist.utils.tableShow(d.groups), 1)
return true
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.rptWeaponsInFlight(_unitName)
local passData = TITS.passGetData(_unitName)
local weaponsFired = passData.weaponsFired
local rpt = ''
local t = timer.getTime()
for k,v in pairs(weaponsFired) do
if v.inFlight then
et = t - v.timeStamp
rpt = string.format('%s\n %s (%s s)',rpt, v.weaponType,mist.utils.round(et,0))
end
end
if rpt == '' then
rpt = 'No weapons in flight'
else
rpt = string.format('Weapons in flight:%s',rpt)
end
UI.messageToUnit(_unitName,rpt,{},30)
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.rptBDAsummary(_unitName)
if TITS.onPass[_unitName] then
UI.messageToUnit(_unitName,'Data not available yet, finish the pass',{},5)
else
local passData = TITS.passGetData(_unitName)
local shellHits = passData.shellHits
local weaponHits = passData.weaponHits
local weaponsFired = passData.weaponsFired
local targetHealth = passData.targetHealth
local rptHitsData = {}
local rptTargetsHit = {}
local config = UI.unitConfig[_unitName]
-- Get targets hit by shells and count of hits per shell type
if #shellHits > 0 then
for target,_ in pairs(shellHits) do
if not rptTargetsHit[target] then
rptTargetsHit[target] = true
end
for shellType,count in pairs(shellHits[target]) do
if not rptHitsData[target] then
rptHitsData[target] = {}
end -- if not rptHitsData[target] then
if not rptHitsData[target][shellType] then
rptHitsData[target][shellType] = count
else
rptHitsData[target][shellType] = rptHitsData[target][shellType] + count
end
end -- for _,shellType in pairs(shellHits[target]) do
end -- for target,_ in pairs(shellHits) do
end -- if #shellHits > 0 then
-- Get targets hit by weapons and count of hits by weapon type
if UI.rowCount(weaponHits) > 0 then
for weaponID, _ in pairs(weaponHits) do
local weaponType = weaponsFired[weaponID].weaponType
for target,_ in pairs (weaponHits[weaponID]) do
if not rptTargetsHit[target] then
rptTargetsHit[target] = true
end
if not rptHitsData[target] then
rptHitsData[target] = {}
end -- if not rptHitsData[target] then
if not rptHitsData[target][weaponType] then
rptHitsData[target][weaponType] = 1
else
rptHitsData[target][weaponType] = rptHitsData[target][weaponType] + 1
end
end -- for target,_ in pairs (weaponHits[weaponID] do
end -- for weaponID, _ in pairs(weaponHits) do
end -- if #weaponHits > 0 then
----- Show number of rows per table
-- Process the data and generate report
local rpt = ''
if UI.rowCount(rptTargetsHit) > 0 then
for target,_ in pairs(rptTargetsHit) do
local section = ''
local t_row = TITS.TargetList[target]
local t_health = targetHealth[target]
local hits = rptHitsData[target]
local max_h = TITS.getMaxTargetLife(target)
local sp_h = t_health.start_health
local ep_h = t_health.end_health
local pass_delta = (sp_h - ep_h)*100/max_h
local overall_delta = ep_h*100/max_h
local t_status = ''
if overall_delta >= 0 and overall_delta < 10 then
t_status = 'destroyed'
elseif overall_delta >= 10 and overall_delta < 25 then
t_status = 'heavily damaged'
elseif overall_delta >= 25 and overall_delta < 50 then
t_status = 'moderately damaged'
elseif overall_delta >= 50 and overall_delta < 75 then
t_status = 'slightly damaged'
else
t_status = 'operational'
end -- if overall_delta >= 0 and overall_delta < 10 then
section = string.format(" >>> %s is %s (pass damage: %s%%)",t_row.displayName,t_status,mist.utils.round(pass_delta,1))
-- hit details?
rpt = string.format("%s\n%s",rpt,section)
end -- for target,_ in pairs(rptHitsData) do
end --if #rptHitsData > 0 then
if rpt == '' then
rpt = 'No targets hit in this pass'
end
if TITS.onPass[_unitName] then
rpt = string.format('Pass BDA Summary (partial:)\n%s',rpt)
else
rpt = string.format('Pass BDA Summary:\n%s',rpt)
end
UI.messageToUnit(_unitName,rpt,{},30)
end --if TITS.onPass[_unitName] then
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.rptListCoord(_unitName)
local _msg = ''
local cfg = UI.unitConfig[_unitName]
for t,v in pairs(TITS.TargetList) do
if v.list_coor then
local pos = TITS.getTargetPos(t)
--local _coord, alt, auom = UI.getCoords(_unitName,pos)
local _coord_mgrs,_coord_dms,_coord_dm, alt, auom = UI.getCoords(_unitName,pos)
_msg = _msg..string.format('[%s]-> Elev: %i%s MSL\n %s\n %s\n %s\n',v.displayName,alt,auom,_coord_mgrs,_coord_dms,_coord_dm)
end
end -- if v.list_coor then
if _msg ~= '' then
_msg = string.format('Coordinates:\n%s',_msg)
else
_msg = 'Coordinates not available'
end
UI.messageToUnit(_unitName, _msg,{},30)
return true
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.rptImpactResults(_unitName)
local config = UI.unitConfig[_unitName]
local weaponsFired = TITS.getWeponsFired(_unitName)
local shellsFired = TITS.getShellsFired(_unitName)
local shellHits = TITS.getShellHits(_unitName)
local title = '\nIndividual Impact Report (closest target)'
if TITS.onPass[_unitName] then
title = title..' (partial)'
end
if weaponsFired.count > 0 or shellsFired.total > 0 or shellHits.total > 0 then
local rpt = ''
local sec = ''
--
-- Shell Hits ==========================================================
--
if shellsFired.total > 0 or shellHits.total > 0 then
local ROT = 0
if shellsFired.total > 0 then
ROT = mist.utils.round(100*shellHits.total/shellsFired.total,1)
sec = string.format(' %s Rounds fired / hits: %s%%\n',shellsFired.total,ROT)
end
--loop thru shells
for _, d in pairs(shellHits.list) do
local targetName = TITS.TargetList[d.target].displayName
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
if d.target == UI.Designation.Designated then
sec = string.format('%s -> %s hit %s rnds: %s - On Target!',sec,targetName, d.hits, d.shellType)
else
sec = string.format('%s -> %s hit %s rnds: %s - Wrong target!',sec,targetName, d.hits, d.shellType)
end
if config.desUseAttackRadial and UI.Designation.Designated == d.target then
local delta = mist.utils.round((shellsFired.shootHeading - UI.MagneticVar) - UI.Designation.AttackRadial, 0)
if delta >= 180 then
delta = 360-delta
end
if delta == 0 then
sec = sec..' <on radial>\n'
else
sec = string.format('%s <off %s°>\n', sec,delta)
end
end
else
sec = string.format('%s -> %s hit %s rnds: %s\n',sec,targetName, d.hits, d.shellType)
end
end -- for shellType, count in pairs(shellsFired.list) do
end --if shellsFired.total > 0 then
rpt = sec
sec = ''
--
-- Weapon Hits ==========================================================
--
if weaponsFired.count > 0 then
for _,wID in pairs(weaponsFired.list) do
local weaponData = TITS.getWeaponData(_unitName, wID)
local targetLN = string.format(" -> %s :",weaponData.weaponType)
if weaponData.miss then
local missDist,missDistUOM = UI.convertDistance(_unitName,TITS.MaxMissDistance)
missDist = mist.utils.round(missDist,0)
targetLN = string.format("%s no targets within %s%s from the impact",targetLN,missDist,missDistUOM)
else
local ct = weaponData.closestTarget
local ctd = weaponData.impacts[ct]
local ctDN= TITS.TargetList[ct].displayName
local hitTXT = 'No effect'
if ctd.hit then
hitTXT ='Good effect'
end
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
if UI.Designation.Designated == ct then
hitTXT =hitTXT..' on designated target!'
else
hitTXT =hitTXT..' on wrong target!'
end
if config.desUseAttackRadial and UI.Designation.Designated == ct then
local delta = mist.utils.round((weaponData.releaseHeading - UI.MagneticVar) - UI.Designation.AttackRadial, 0)
if delta >= 180 then
delta = 360-delta
end
if delta == 0 then
hitTXT = hitTXT..' <on radial>'
else
hitTXT = string.format('%s <off %s°>', hitTXT,delta)
end
end
else
hitTXT =hitTXT..' on target!'
end
targetLN = string.format("%s %s\n",targetLN,hitTXT)
-- Impact data
local dist,distUOM = UI.convertDistance(_unitName,ctd.distance)
dist = mist.utils.round(dist,0)
local direction = UI.getRelativeDirection(weaponData.releaseHeading ,ctd.targetPos, ctd.impactPos)
direction = UI.getClockDirection(direction)
local slantRange = mist.utils.get3DDist(weaponData.releasePosition, ctd.targetPos)
local angDist,angDistUOM = UI.convertAngDistance(_unitName,ctd.distance/(slantRange * 0.1))
angDist = mist.utils.round(angDist,2)
targetLN = string.format("%s %s %s from %s @ %s o'clock (%s %s)\n"
, targetLN
, dist
, distUOM
, ctDN
, direction
, angDist
, angDistUOM
)
--Release params
if config.DisplayReleaseData then
local pos = weaponData.releasePosition
local height = land.getHeight({x = pos.x, y = pos.z})
local releaseAlt = pos.y - height -- in meters. Release AGL
local alt, altUOM = UI.convertAltitude(_unitName,releaseAlt)
alt = mist.utils.round(alt,0)
local speed, speedUOM = UI.convertSpeed(_unitName,weaponData.releaseSpeed)
speed = mist.utils.round(speed,0)
local slant,slantUOM = UI.convertDistance(_unitName,slantRange)
slant = mist.utils.round(slant,0)
local pitch = mist.utils.round(weaponData.releasePitch,1)
local roll = mist.utils.round(weaponData.releaseRoll,1)
local yaw = mist.utils.round(weaponData.releaseYaw,1)
local RIdistance,RIdistUOM = UI.convertDistance(_unitName,weaponData.rollInDistance)
local RIat,RIatUOM = UI.convertAltitude(_unitName,weaponData.rollInAltitudeAT)
RIdistance = mist.utils.round(RIdistance,0)
RIat = mist.utils.round(RIat,0)
local relP = string.format(" Rel. Params: Alt: %s%s AGL Speed: %s%s Hdg:%s°\n"
, alt , altUOM
, speed , speedUOM
,mist.utils.round(weaponData.releaseHeading,0)
)
relP = string.format("%s S.RNG: %s%s P: %s R:%s Y:%s\n"
, relP
, slant , slantUOM
, pitch
, roll
, yaw
)
relP = string.format("%s Roll-in: Dist: %s%s Alt: %s%s (above target)\n",relP,RIdistance,RIdistUOM,RIat,RIatUOM)
targetLN = string.format("%s%s",targetLN,relP)
end -- if cfg.DisplayReleaseData then
end -- if weaponData.miss then
sec = sec..targetLN
end -- for _,wID in pairs(weaponsFired.list) do
end --if weaponsFired.count > 0
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
--
-- Popups Hits ==========================================================
--
if pcall(function() return PTC.Version end ) then
if PTC.passTargetsUP > 0 then
sec = string.format("%s Popups hit: %s/%s",sec,PTC.passTargetsHIT ,PTC.passTargetsUP )
end
end
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
--
-- Show report ==========================================================
--
rpt = string.format ('%s\n%s',title,rpt)
UI.messageToUnit(_unitName,rpt,{},30)
else -- if weaponsFired.count > 0 or shellsFired.total > 0 then
UI.messageToUnit(_unitName,title..'\nThere is nothing to report.',{},5)
end -- if weaponsFired.count > 0 or shellsFired.total > 0 then
end --function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.rptImpactResultsGrouped(_unitName)
local config = UI.unitConfig[_unitName]
local weaponsFired = TITS.getWeponsFired(_unitName)
local shellsFired = TITS.getShellsFired(_unitName)
local shellHits = TITS.getShellHits(_unitName)
local title = '\nGrouped Impact Report (closest target)'
if TITS.onPass[_unitName] then
title = title..' (partial)'
end
if weaponsFired.count > 0 or shellsFired.total > 0 or shellHits.total > 0 then
local rpt = ''
local sec = ''
--
-- Shell Hits ==========================================================
--
if shellsFired.total > 0 or shellHits.total > 0 then
local ROT = 0
if shellsFired.total > 0 then
ROT = mist.utils.round(100*shellHits.total/shellsFired.total,1)
sec = string.format(' %s Rounds fired / hits: %s%%\n',shellsFired.total,ROT)
end
--loop thru shells
for _, d in pairs(shellHits.list) do
local targetName = TITS.TargetList[d.target].displayName
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
if d.target == UI.Designation.Designated then
sec = string.format('%s -> %s hit %s rnds: %s - On Target!',sec,targetName, d.hits, d.shellType)
else
sec = string.format('%s -> %s hit %s rnds: %s - Wrong target!',sec,targetName, d.hits, d.shellType)
end
if config.desUseAttackRadial and UI.Designation.Designated == d.target then
local delta = mist.utils.round((shellsFired.shootHeading - UI.MagneticVar) - UI.Designation.AttackRadial, 0)
if delta >= 180 then
delta = 360-delta
end
if delta == 0 then
sec = sec..' <on radial>\n'
else
sec = string.format('%s <off %s°>\n', sec,delta)
end
end
else
sec = string.format('%s -> %s hit %s rnds: %s\n',sec,targetName, d.hits, d.shellType)
end
end -- for shellType, count in pairs(shellsFired.list) do
end --if shellsFired.total > 0 then
rpt = sec
sec = ''
--
-- Groups ==========================================================
--
local groups = TITS.getGroups(_unitName)
for _ , groupID in pairs(groups.list) do
local groupData = TITS.getGroupData(_unitName, groupID)
local targetLN = string.format(" -> %s :",groupData.weaponType)
if groupData.miss then
local missDist,missDistUOM = UI.convertDistance(_unitName,TITS.MaxMissDistance)
missDist = mist.utils.round(missDist,0)
targetLN = string.format("%s no targets within %s%s from the impact",targetLN,missDist,missDistUOM)
else
local ct = groupData.closestTarget
local ctDN= TITS.TargetList[ct].displayName
local hitTXT = 'No effect'
if groupData.closestTargetHit then
hitTXT ='Good effect'
end
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
if UI.Designation.Designated == ct then
hitTXT =hitTXT..' on designated target!'
else
hitTXT =hitTXT..' on wrong target!'
end
if config.desUseAttackRadial and UI.Designation.Designated == ct then
local delta = mist.utils.round((weaponData.releaseHeading - UI.MagneticVar) - UI.Designation.AttackRadial, 0)
if delta >= 180 then
delta = 360-delta
end
if delta == 0 then
hitTXT = hitTXT..' <on radial>'
else
hitTXT = string.format('%s <off %s°>', hitTXT,delta)
end
end
else
hitTXT =hitTXT..' on target!'
end
targetLN = string.format("%s %s (%s/%s hits)\n",targetLN,hitTXT,groupData.closestTargetHitCount, groupData.weaponCount)
-- Impact data
local dist,distUOM = UI.convertDistance(_unitName,groupData.closestTargetDist)
dist = mist.utils.round(dist,0)
local direction = UI.getRelativeDirection(groupData.releaseHeading ,groupData.closestTargetPos, groupData.impactPos)
direction = UI.getClockDirection(direction)
local slantRange = mist.utils.get3DDist(groupData.releasePosition, groupData.closestTargetPos)
local angDist,angDistUOM = UI.convertAngDistance(_unitName,groupData.closestTargetDist/(slantRange * 0.1))
angDist = mist.utils.round(angDist,2)
local grpSize,grpSizeUOM = UI.convertAngDistance(_unitName,groupData.size)
grpSize = mist.utils.round(grpSize,2)
-- hit count / weapon count
targetLN = string.format("%s %s %s from %s @ %s o'clock (%s %s)\n"
, targetLN
, dist
, distUOM
, ctDN
, direction
, angDist
, angDistUOM
)
targetLN = string.format("%s Group size: %s%s\n"
, targetLN
, grpSize
, grpSizeUOM
)
--Release params
if config.DisplayReleaseData then
local pos = groupData.releasePosition
local height = land.getHeight({x = pos.x, y = pos.z})
local releaseAlt = pos.y - height -- in meters. Release AGL
local alt, altUOM = UI.convertAltitude(_unitName,releaseAlt)
alt = mist.utils.round(alt,0)
local speed, speedUOM = UI.convertSpeed(_unitName,groupData.releaseSpeed)
speed = mist.utils.round(speed,0)
local slant,slantUOM = UI.convertDistance(_unitName,slantRange)
slant = mist.utils.round(slant,0)
local pitch = mist.utils.round(groupData.releasePitch,1)
local roll = mist.utils.round(groupData.releaseRoll,1)
local yaw = mist.utils.round(groupData.releaseYaw,1)
local RIdistance,RIdistUOM = UI.convertDistance(_unitName,groupData.rollInDistance)
local RIat,RIatUOM = UI.convertAltitude(_unitName,groupData.rollInAltitudeAT)
RIdistance = mist.utils.round(RIdistance,0)
RIat = mist.utils.round(RIat,0)
local relP = string.format(" Rel. Params: Alt: %s%s AGL Speed: %s%s Hdg:%s°\n"
, alt , altUOM
, speed , speedUOM
,mist.utils.round(groupData.releaseHeading,0)
)
relP = string.format("%s S.RNG: %s%s P: %s R:%s Y:%s\n"
, relP
, slant , slantUOM
, pitch
, roll
, yaw
)
relP = string.format("%s Roll-in: Dist: %s%s Alt: %s%s (above target)\n",relP,RIdistance,RIdistUOM,RIat,RIatUOM)
targetLN = string.format("%s%s",targetLN,relP)
end -- if cfg.DisplayReleaseData then
end -- if weaponData.miss then
sec = sec..targetLN
end -- for _,groupID in pairs(groups) do
--
-- Popups Hits ==========================================================
--
if pcall(function() return PTC.Version end ) then
if PTC.passTargetsUP > 0 then
sec = string.format("%s Popups hit: %s/%s",sec,PTC.passTargetsHIT ,PTC.passTargetsUP )
end
end
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
--
-- Show report ==========================================================
--
rpt = string.format ('%s\n%s',title,rpt)
UI.messageToUnit(_unitName,rpt,{},30)
else -- if weaponsFired.count > 0 or shellsFired.total > 0 then
UI.messageToUnit(_unitName,title..'\nThere is nothing to report.',{},5)
end -- if weaponsFired.count > 0 or shellsFired.total > 0 then
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.rptImpactResultsDes(_unitName)
local config = UI.unitConfig[_unitName]
local weaponsFired = TITS.getWeponsFired(_unitName)
local shellsFired = TITS.getShellsFired(_unitName)
local shellHits = TITS.getShellHits(_unitName)
local title = '\nIndividual Impact Report (designated target)'
if TITS.onPass[_unitName] then
title = title..' (partial)'
end
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
if weaponsFired.count > 0 or shellsFired.total > 0 or shellHits.total > 0 then
local rpt = ''
local sec = ''
-- Header
local ct = UI.Designation.Designated
local ctDN= TITS.TargetList[ct].displayName
rpt = string.format(' Target: %s',ctDN)
if config.desUseAttackRadial then
rpt = string.format('%s / Attack radial: %s°',rpt,UI.Designation.AttackRadial)
end
--
-- Shell Hits ==========================================================
--
if shellsFired.total > 0 or shellHits.total > 0 then
local ROT = 0
if shellsFired.total > 0 then
ROT = mist.utils.round(100*shellHits.total/shellsFired.total,1)
sec = string.format(' %s Rounds fired / hits: %s%%\n',shellsFired.total,ROT)
end
--loop thru shells
for _, d in pairs(shellHits.list) do
local targetName = TITS.TargetList[d.target].displayName
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
if d.target == UI.Designation.Designated then
sec = string.format('%s -> %s hit %s rnds: %s - On Target!',sec,targetName, d.hits, d.shellType)
else
sec = string.format('%s -> %s hit %s rnds: %s - Wrong target!',sec,targetName, d.hits, d.shellType)
end
if config.desUseAttackRadial and UI.Designation.Designated == d.target then
local delta = mist.utils.round((shellsFired.shootHeading - UI.MagneticVar) - UI.Designation.AttackRadial, 0)
if delta >= 180 then
delta = 360-delta
end
if delta == 0 then
sec = sec..' <on radial>\n'
else
sec = string.format('%s <off %s°>\n', sec,delta)
end
end
else
sec = string.format('%s -> %s hit %s rnds: %s\n',sec,targetName, d.hits, d.shellType)
end
end -- for shellType, count in pairs(shellsFired.list) do
end --if shellsFired.total > 0 then
if sec ~= '' then
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
end
--
-- Weapon Hits ==========================================================
--
if weaponsFired.count > 0 then
for _,wID in pairs(weaponsFired.list) do
local weaponData = TITS.getWeaponData(_unitName, wID)
local targetLN = string.format(" -> %s :",weaponData.weaponType)
local missDist,missDistUOM = UI.convertDistance(_unitName,TITS.MaxMissDistance)
if weaponData.miss then
missDist = mist.utils.round(missDist,0)
targetLN = string.format("%s no targets within %s%s from the impact",targetLN,missDist,missDistUOM)
else
local ctd = weaponData.impacts[ct]
local hitTXT = 'No effect'
if ctd then
if ctd.hit then
hitTXT ='Good effect'
end
if config.desUseAttackRadial and UI.Designation.Designated == ct then
local delta = mist.utils.round((weaponData.releaseHeading - UI.MagneticVar) - UI.Designation.AttackRadial, 0)
if delta >= 180 then
delta = 360-delta
end
if delta == 0 then
hitTXT = hitTXT..' <on radial>'
else
hitTXT = string.format('%s <off %s°>', hitTXT,delta)
end
end
targetLN = string.format("%s %s\n",targetLN,hitTXT)
-- Impact data
local dist,distUOM = UI.convertDistance(_unitName,ctd.distance)
dist = mist.utils.round(dist,0)
local direction = UI.getRelativeDirection(weaponData.releaseHeading ,ctd.targetPos, ctd.impactPos)
direction = UI.getClockDirection(direction)
local slantRange = mist.utils.get3DDist(weaponData.releasePosition, ctd.targetPos)
local angDist,angDistUOM = UI.convertAngDistance(_unitName,ctd.distance/(slantRange * 0.1))
angDist = mist.utils.round(angDist,2)
targetLN = string.format("%s %s %s from target @ %s o'clock (%s %s)\n"
, targetLN
, dist
, distUOM
, direction
, angDist
, angDistUOM
)
--Release params
if config.DisplayReleaseData then
local pos = weaponData.releasePosition
local height = land.getHeight({x = pos.x, y = pos.z})
local releaseAlt = pos.y - height -- in meters. Release AGL
local alt, altUOM = UI.convertAltitude(_unitName,releaseAlt)
alt = mist.utils.round(alt,0)
local speed, speedUOM = UI.convertSpeed(_unitName,weaponData.releaseSpeed)
speed = mist.utils.round(speed,0)
local slant,slantUOM = UI.convertDistance(_unitName,slantRange)
slant = mist.utils.round(slant,0)
local pitch = mist.utils.round(weaponData.releasePitch,1)
local roll = mist.utils.round(weaponData.releaseRoll,1)
local yaw = mist.utils.round(weaponData.releaseYaw,1)
local relP = string.format(" Rel. Params: Alt: %s%s AGL Speed: %s%s Hdg:%s°\n"
, alt , altUOM
, speed , speedUOM
,mist.utils.round(weaponData.releaseHeading,0)
)
relP = string.format("%s S.RNG: %s%s P: %s R:%s Y:%s\n"
, relP
, slant , slantUOM
, pitch
, roll
, yaw
)
targetLN = string.format("%s%s",targetLN,relP)
end -- if cfg.DisplayReleaseData then
else -- if ctd then
targetLN = string.format("%s no impact within %s%s of the designated target",targetLN,missDist,missDistUOM)
end -- if ctd then
end -- if weaponData.miss then
sec = sec..targetLN
end -- for _,wID in pairs(weaponsFired.list) do
end --if weaponsFired.count > 0
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
--
-- Popups Hits ==========================================================
--
if pcall(function() return PTC.Version end ) then
if PTC.passTargetsUP > 0 then
sec = string.format("%s Popups hit: %s/%s",sec,PTC.passTargetsHIT ,PTC.passTargetsUP )
end
end
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
--
-- Show report ==========================================================
--
rpt = string.format ('%s\n%s',title,rpt)
UI.messageToUnit(_unitName,rpt,{},30)
else -- if weaponsFired.count > 0 or shellsFired.total > 0 then
UI.messageToUnit(_unitName,title..'\nThere is nothing to report.',{},5)
end -- if weaponsFired.count > 0 or shellsFired.total > 0 then
else
UI.messageToUnit(_unitName,title..'\nThere is no designated target.',{},5)
end --if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
end --function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.rptImpactResultsDesGrouped(_unitName)
local config = UI.unitConfig[_unitName]
local weaponsFired = TITS.getWeponsFired(_unitName)
local shellsFired = TITS.getShellsFired(_unitName)
local shellHits = TITS.getShellHits(_unitName)
local title = '\nGrouped Impact Report (designated target)'
if TITS.onPass[_unitName] then
title = title..' (partial)'
end
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
if weaponsFired.count > 0 or shellsFired.total > 0 or shellHits.total > 0 then
local rpt = ''
local sec = ''
-- Header
local ct = UI.Designation.Designated
local ctDN= TITS.TargetList[ct].displayName
rpt = string.format(' Target: %s',ctDN)
if config.desUseAttackRadial then
rpt = string.format('%s / Attack radial: %s°',rpt,UI.Designation.AttackRadial)
end
--
-- Shell Hits ==========================================================
--
if shellsFired.total > 0 or shellHits.total > 0 then
local ROT = 0
if shellsFired.total > 0 then
ROT = mist.utils.round(100*shellHits.total/shellsFired.total,1)
sec = string.format(' %s Rounds fired / hits: %s%%\n',shellsFired.total,ROT)
end
--loop thru shells
for _, d in pairs(shellHits.list) do
local targetName = TITS.TargetList[d.target].displayName
if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
if d.target == UI.Designation.Designated then
sec = string.format('%s -> %s hit %s rnds: %s - On Target!',sec,targetName, d.hits, d.shellType)
else
sec = string.format('%s -> %s hit %s rnds: %s - Wrong target!',sec,targetName, d.hits, d.shellType)
end
if config.desUseAttackRadial and UI.Designation.Designated == d.target then
local delta = mist.utils.round((shellsFired.shootHeading - UI.MagneticVar) - UI.Designation.AttackRadial, 0)
if delta >= 180 then
delta = 360-delta
end
if delta == 0 then
sec = sec..' <on radial>\n'
else
sec = string.format('%s <off %s°>\n', sec,delta)
end
end
else
sec = string.format('%s -> %s hit %s rnds: %s\n',sec,targetName, d.hits, d.shellType)
end
end -- for shellType, count in pairs(shellsFired.list) do
end --if shellsFired.total > 0 then
if sec ~= '' then
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
end
--
-- Groups ==========================================================
--
if weaponsFired.count > 0 then
local groups = TITS.getGroups(_unitName)
for _,groupID in pairs(groups.list) do
local groupData = TITS.getGroupData(_unitName, groupID)
local targetLN = string.format(" -> %s :",groupData.weaponType)
local missDist,missDistUOM = UI.convertDistance(_unitName,TITS.MaxMissDistance)
if groupData.miss then
missDist = mist.utils.round(missDist,0)
targetLN = string.format("%s no targets within %s%s from the impact",targetLN,missDist,missDistUOM)
else
-- get all the weapons in the group with an impact on the target
local ctd = {}
local ctPos = {}
local ctHits = 0
for _,row in pairs(groupData.TargetImpacts) do
if row.target == UI.Designation.Designated then
table.insert(ctd,row.targetPos)
ctPos =row.targetPos
if row.hit then
ctHits = ctHits + 1
end
end
end
--local ctd = weaponData.impacts[ct]
local hitTXT = 'No effect'
if UI.rowCount(ctd) > 0 then
if UI.inTable(UI.Designation.Designated,groupData.targetsHit) then
hitTXT ='Good effect'
end
if config.desUseAttackRadial and UI.Designation.Designated == ct then
local delta = mist.utils.round((groupData.releaseHeading - UI.MagneticVar) - UI.Designation.AttackRadial, 0)
if delta >= 180 then
delta = 360-delta
end
if delta == 0 then
hitTXT = hitTXT..' <on radial>'
else
hitTXT = string.format('%s <off %s°>', hitTXT,delta)
end
end
targetLN = string.format("%s %s (%s/%s hits)\n",targetLN,hitTXT,ctHits, groupData.weaponCount)
--targetLN = string.format("%s %s\n",targetLN,hitTXT)
-- Impact data from group center
local distance = mist.utils.get3DDist(groupData.impactPos,ctPos)
local dist,distUOM = UI.convertDistance(_unitName,distance)
dist = mist.utils.round(dist,0)
local direction = UI.getRelativeDirection(groupData.releaseHeading ,ctPos, groupData.impactPos)
direction = UI.getClockDirection(direction)
local slantRange = mist.utils.get3DDist(groupData.releasePosition, ctPos)
local angDist,angDistUOM = UI.convertAngDistance(_unitName,distance/(slantRange * 0.1))
angDist = mist.utils.round(angDist,2)
local grpSize,grpSizeUOM = UI.convertAngDistance(_unitName,groupData.size)
grpSize = mist.utils.round(grpSize,2)
targetLN = string.format("%s %s %s from target @ %s o'clock (%s %s)\n"
, targetLN
, dist
, distUOM
, direction
, angDist
, angDistUOM
)
targetLN = string.format("%s Group size: %s%s\n"
, targetLN
, grpSize
, grpSizeUOM
)
--Release params
if config.DisplayReleaseData then
local pos = groupData.releasePosition
local height = land.getHeight({x = pos.x, y = pos.z})
local releaseAlt = pos.y - height -- in meters. Release AGL
local alt, altUOM = UI.convertAltitude(_unitName,releaseAlt)
alt = mist.utils.round(alt,0)
local speed, speedUOM = UI.convertSpeed(_unitName,groupData.releaseSpeed)
speed = mist.utils.round(speed,0)
local slant,slantUOM = UI.convertDistance(_unitName,slantRange)
slant = mist.utils.round(slant,0)
local pitch = mist.utils.round(groupData.releasePitch,1)
local roll = mist.utils.round(groupData.releaseRoll,1)
local yaw = mist.utils.round(groupData.releaseYaw,1)
local relP = string.format(" Rel. Params: Alt: %s%s AGL Speed: %s%s Hdg:%s°\n"
, alt , altUOM
, speed , speedUOM
, mist.utils.round(groupData.releaseHeading,0)
)
relP = string.format("%s S.RNG: %s%s P: %s R:%s Y:%s\n"
, relP
, slant , slantUOM
, pitch
, roll
, yaw
)
targetLN = string.format("%s%s",targetLN,relP)
end -- if cfg.DisplayReleaseData then
else -- if ctd then
targetLN = string.format("%s no impact within %s%s of the designated target",targetLN,missDist,missDistUOM)
end -- if ctd then
end -- if weaponData.miss then
sec = sec..targetLN
end -- for _,wID in pairs(weaponsFired.list) do
end --if weaponsFired.count > 0
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
--
-- Popups Hits ==========================================================
--
if pcall(function() return PTC.Version end ) then
if PTC.passTargetsUP > 0 then
sec = string.format("%s Popups hit: %s/%s",sec,PTC.passTargetsHIT ,PTC.passTargetsUP )
end
end
if rpt ~= '' then
rpt = string.format ('%s\n%s',rpt,sec)
else
rpt = sec
end
sec = ''
--
-- Show report ==========================================================
--
rpt = string.format ('%s\n%s',title,rpt)
UI.messageToUnit(_unitName,rpt,{},30)
else -- if weaponsFired.count > 0 or shellsFired.total > 0 then
UI.messageToUnit(_unitName,title..'\nThere is nothing to report.',{},5)
end -- if weaponsFired.count > 0 or shellsFired.total > 0 then
else
UI.messageToUnit(_unitName,title..'\nThere is no designated target.',{},5)
end --if UI.Designation.type ~=0 and UI.Designation.Designated ~= '' then
end --function
--
--#################################################################################################################################################
-- ############# Illumination ###############
--#################################################################################################################################################
--
function UI.AddIlluminationZone(_zoneName, _Rounds)
UI.illuminationZones[_zoneName] = _Rounds
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.illuminate(params, time)
if UI.illuminating then
if UI.rowCount(UI.illuminationZones) > 0 then
for z,r in pairs(UI.illuminationZones) do
for i=1,r do
local pos = mist.utils.makeVec3(mist.getRandomPointInZone(z),mist.random(450,550))
trigger.action.illuminationBomb(pos,7500)
end
end
end
return timer.getTime() + 150
else
return nil
end
end -- function
--------------------
--
--#################################################################################################################################################
-- ############# Designation ###############
--#################################################################################################################################################
--
function UI.AddDesignationZone(_ID, _Name)
UI.DesignationZones[_ID] = { id = _ID, name = _Name}
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.AddDesignator(_name, _displayName, _zones, _allowLaser, _allowIR, _code)
local dn = _displayName
if _displayName == '' then
dn = _name
end
UI.Designators[_name] = {
name = _name
, displayName = dn
, zones = _zones
, allowLaser = _allowLaser
, allowIR = _allowIR
, code = _code
}
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.desSetZone(_args)
local unitName = _args[1]
local zoneID = _args[2]
UI.Designation.zone = zoneID
local z = UI.DesignationZones[zoneID]
UI.messageToUnit(unitName,string.format('Designation zone set to: %s',z.name))
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.desWP(_args)
local _unitName = _args[1]
local isNew = _args[2]
local ERROR = 0
local targetList = {}
local n = 0 -- Number of designatable targets in zone
local tgt -- selectec target
local cfg = UI.unitConfig[_unitName]
-- Get WP designatable targets in the selected zone
if isNew then
for _,row in pairs(TITS.TargetList) do
if row.des_zone == UI.Designation.zone and row.des_wp and TITS.getTargetLife(row.name) > 0 then
n = n + 1
targetList[n] = row.name
end
end -- for t,row in pairs(TITS.TargetList) do
if n == 0 then
ERROR = 1 -- No designatable targets in zone
else
local r = mist.random(1,n)
tgt = targetList[r]
end
else -- if isNew or UI.Designation.Designated == '' then
tgt = UI.Designation.Designated
if TITS.getTargetLife(tgt) <= 0 then
ERROR = 2 -- prior designated target is dead
end
end -- if isNew then
if ERROR == 0 then -- Designate the target
--select a random target
local tgtPos = TITS.getTargetPos(tgt)
local markPos = mist.utils.makeVec3GL(mist.getRandPointInCircle(tgtPos,UI.maxWPmarkDist,UI.minWPmarkDist,0,360))
local _dir = mist.utils.toDegree(mist.utils.getDir(mist.vec.sub(tgtPos, markPos)))
local _dist = mist.utils.get2DDist(markPos,tgtPos)
local _direction = ''
local oTarget = TITS.TargetList[tgt]
if _dir >= 0 and _dir < 22 then
_direction = 'North'
elseif _dir >= 22 and _dir < 68 then
_direction = 'North East'
elseif _dir >= 68 and _dir < 113 then
_direction = 'East'
elseif _dir >= 113 and _dir < 158 then
_direction = 'South East'
elseif _dir >= 158 and _dir < 203 then
_direction = 'South'
elseif _dir >= 203 and _dir < 248 then
_direction = 'South West'
elseif _dir >= 248 and _dir < 293 then
_direction = 'West'
elseif _dir >= 293 and _dir < 338 then
_direction = 'North West'
elseif _dir >= 338 and _dir <= 360 then
_direction = 'North'
end
local ar_brief = ''
if cfg.desUseAttackRadial then
UI.Designation.AttackRadial = mist.random(1,359)
ar_brief = string.format('\nAttack radial %s° (magnetic)',UI.Designation.AttackRadial)
end
-- prepare the briefing
local dist,distUOM = UI.convertDistance(_unitName,_dist)
dist = mist.utils.round(dist,0)
if distUOM == 'ft' then
dist = math.floor(dist / 25) * 25
else
dist = math.floor(dist / 10) * 10
end
local briefing = string.format("%s marked with WP\n%i %s, %s from the mark%s",oTarget.displayName, dist,distUOM, _direction,ar_brief)
UI.Designation.Designator = '' -- unit name
UI.Designation.Designated = tgt
UI.Designation.type = 1 -- 0 = none, 1 = WP , 2 = laser, 3 = IR
UI.Designation.briefing = briefing
trigger.action.smoke(markPos, trigger.smokeColor.White )
UI.messageToUnit(_unitName,briefing,{},45)
else
local errorMSG =''
if ERROR == 1 then
errorMSG = string.format('There are no designatable targets in zone %s',UI.DesignationZones[UI.Designation.zone].name )
elseif ERROR == 2 then
errorMSG ='Target is dead. Select a new one'
end
UI.messageToUnit(_unitName,errorMSG,{},45)
UI.Designation.Designator = '' -- unit name
UI.Designation.Designated = ''
UI.Designation.type = 0 -- 0 = none, 1 = WP , 2 = laser, 3 = IR
UI.Designation.briefing = ''
end --if not ERROR then
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.desNM(_unitName)
local config = UI.unitConfig[_unitName]
local ERROR = 0
local targetList = {}
local n = 0 -- Number of designatable targets in zone
local tgt -- selectec target
-- Get WP designatable targets in the selected zone
for _,row in pairs(TITS.TargetList) do
if row.des_zone == UI.Designation.zone and row.des_nomark and TITS.getTargetLife(row.name) > 0 then
n = n + 1
targetList[n] = row.name
end
end -- for t,row in pairs(TITS.TargetList) do
if n == 0 then
ERROR = 1 -- No designatable targets in zone
else
local r = mist.random(1,n)
tgt = targetList[r]
end
if ERROR == 0 then -- Designate the target
-- prepare the briefing
local oTarget = TITS.TargetList[tgt]
local cfg = UI.unitConfig[_unitName]
local ar_brief = ''
if cfg.desUseAttackRadial then
UI.Designation.AttackRadial = mist.random(1,359)
ar_brief = string.format('\nAttack radial %s° (magnetic)',UI.Designation.AttackRadial)
end
local briefing = ''
if config.desDisplayCoords then
local pos = TITS.getTargetPos(tgt)
--local _coord, alt, auom = UI.getCoords(_unitName,pos)
local _coord_mgrs,_coord_dms,_coord_dm, alt, auom = UI.getCoords(_unitName,pos)
--Elev: %i%s MSL\n %s\n %s\n %s\n'
briefing = string.format("\nYour target is %s at Elev: %i%s MSL\n %s\n %s\n %s\n",oTarget.displayName,alt,auom,_coord_mgrs,_coord_dms,_coord_dm)
else
briefing = string.format("\nYour target is %s",oTarget.displayName)
end
briefing = string.format("%s%s",briefing,ar_brief)
if oTarget.uiText1 ~= '' then
briefing = string.format('%s\n%s',briefing,oTarget.uiText1)
end
if oTarget.uiText2 ~= '' then
briefing = string.format('%s\n%s',briefing,oTarget.uiText2)
end
if oTarget.uiText3 ~= '' then
briefing = string.format('%s\n%s',briefing,oTarget.uiText3)
end
UI.Designation.Designator = '' -- unit name
UI.Designation.Designated = tgt
UI.Designation.type = 4 -- 0 = none, 1 = WP , 2 = laser, 3 = IR, 4 = No mark
UI.Designation.briefing = briefing
UI.messageToUnit(_unitName,briefing,{},45)
else
local errorMSG =''
if ERROR == 1 then
errorMSG = string.format('There are no designatable targets in zone %s',UI.DesignationZones[UI.Designation.zone].name )
end
UI.messageToUnit(_unitName,errorMSG,{},45)
UI.Designation.Designator = '' -- unit name
UI.Designation.Designated = ''
UI.Designation.type = 0 -- 0 = none, 1 = WP , 2 = laser, 3 = IR
UI.Designation.briefing = ''
end --if not ERROR then
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.desRPTBrief(_unitName)
if UI.Designation.type ~= 0 then
UI.messageToUnit(_unitName,UI.Designation.briefing,{},45)
else
UI.messageToUnit(_unitName,'No targets designated',{},45)
end
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.desCancel(_unitName)
UI.Designation.Designator = '' -- unit name
UI.Designation.Designated = ''
UI.Designation.type = 0 -- 0 = none, 1 = WP , 2 = laser, 3 = IR, 4 = No mark, 23 = laser/IR
UI.Designation.briefing = ''
UI.Designation.spotON = false
UI.messageToUnit(_unitName,'Designation cancelled',{},5)
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.desLaserIR(_args)
local _unitName = _args[1]
local desType = _args[2] -- laser | IR
local designators = {}
local targets = {}
local Nt = 0
local Nd = 0
local cfg = UI.unitConfig[_unitName]
if UI.Designation.type == 2 or UI.Designation.type == 3 or UI.Designation.type == 23 then
UI.messageToUnit(_unitName,'There is a prior laser/IR designation. Cancel it first',{},15)
return false
end
-- get designatable targets is zone
for _,row in pairs(TITS.TargetList) do
if row.des_zone == UI.Designation.zone and TITS.getTargetLife(row.name) > 0 and
((row.des_laser and desType == 'laser') or (row.des_ir and desType == 'IR') or (desType == 'laser/IR' and row.des_laser and row.des_ir ) ) then
Nt = Nt + 1
targets[Nt] = row.name
end
end -- for t,row in pairs(TITS.TargetList) do
-- get designators in zone
for _,row in pairs(UI.Designators) do
if UI.inTable(UI.Designation.zone, row.zones ) and ((row.allowLaser and desType == 'laser') or (row.allowIR and desType == 'IR') or (desType == 'laser/IR' and row.allowLaser and row.allowIR ) ) then
-- check designator life
local u = Unit.getByName(row.name)
if u then
Nd = Nd + 1
designators[Nd] = row.name
end
end
end -- for t,row in pairs(TITS.TargetList) do
if Nt > 0 and Nd > 0 then
local Rt = mist.random(1,Nt)
local Rd = mist.random(1,Nd)
UI.Designation.Designator = designators[Rd]
UI.Designation.Designated = targets[Rt]
local designator = Unit.getByName(UI.Designation.Designator )
local desPos = TITS.getTargetPos(UI.Designation.Designated)
desPos = { x = desPos.x, y = desPos.y + 2.0, z = desPos.z }
if desType == 'laser' then
UI.Designation.type = 2
UI.Designation.ray1 = Spot.createLaser(designator, {x = 0, y = 1, z = 0}, desPos , UI.Designators[UI.Designation.Designator].code)
elseif desType == 'IR' then
UI.Designation.type = 3
UI.Designation.ray1 = Spot.createInfraRed(designator, {x = 0, y = 1, z = 0}, desPos)
else
UI.Designation.type = 23
UI.Designation.ray1 = Spot.createLaser(designator, {x = 0, y = 1, z = 0}, desPos, UI.Designators[UI.Designation.Designator].code)
UI.Designation.ray2 = Spot.createInfraRed(designator, {x = 0, y = 1, z = 0}, desPos)
end
local function updateRay()
if UI.Designation.type == 2 or UI.Designation.type == 3 then
local pos = TITS.getTargetPos(UI.Designation.Designated)
pos = { x = pos.x, y = pos.y + 2.0, z = pos.z }
UI.Designation.ray1:setPoint(pos)
timer.scheduleFunction(updateRay, {}, timer.getTime() + 0.5)
elseif UI.Designation.type == 23 then
local pos = TITS.getTargetPos(UI.Designation.Designated)
pos = { x = pos.x, y = pos.y + 2.0, z = pos.z }
UI.Designation.ray1:setPoint(pos)
UI.Designation.ray2:setPoint(pos)
timer.scheduleFunction(updateRay, {}, timer.getTime() + 0.5)
else
UI.Designation.ray1:destroy()
if UI.Designation.ray2 then
UI.Designation.ray2:destroy()
end
end
end
timer.scheduleFunction(updateRay, {}, timer.getTime() + 0.5)
-- prepare briefing
local ar_brief = ''
if cfg.desUseAttackRadial then
UI.Designation.AttackRadial = mist.random(1,359)
ar_brief = string.format('\nAttack radial %s° (magnetic)',UI.Designation.AttackRadial)
end
local dDN = UI.Designators[UI.Designation.Designator].displayName
local t = TITS.TargetList[UI.Designation.Designated]
local brief = string.format('%s designated by %s from %s', t.displayName, desType, dDN)
if UI.Designation.type == 2 or UI.Designation.type == 23 then
brief = string.format('%s with code: %s',brief,UI.Designators[UI.Designation.Designator].code)
end
if cfg.desDisplayCoords then
local pos = TITS.getTargetPos(UI.Designation.Designated)
--local _coord, alt, auom = UI.getCoords(_unitName,pos)
local _coord_mgrs,_coord_dms,_coord_dm, alt, auom = UI.getCoords(_unitName,pos)
brief = string.format('%s\ntarget coords: Elev: %i%s MSL\n %s\n %s\n %s',brief,alt,auom, _coord_mgrs,_coord_dms,_coord_dm)
end
brief = string.format('%s%s',brief,ar_brief)
local oTarget = TITS.TargetList[UI.Designation.Designated]
if oTarget.uiText1 ~= '' then
brief = string.format('%s\n%s',brief,oTarget.uiText1)
end
if oTarget.uiText2 ~= '' then
brief = string.format('%s\n%s',brief,oTarget.uiText2)
end
if oTarget.uiText3 ~= '' then
brief = string.format('%s\n%s',brief,oTarget.uiText3)
end
UI.messageToUnit(_unitName,brief,{},45)
else -- if Nt > 0 and Nd > 0 then
local errorMSG = string.format('There are no designatable targets or designators in zone %s',UI.DesignationZones[UI.Designation.zone].name )
UI.messageToUnit(_unitName,errorMSG,{},45)
end
return true
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
--
--#################################################################################################################################################
-- ############# Utility ###############
--#################################################################################################################################################
--
function UI.getCoords(_unitName,pos)
local lat, lon, alt = coord.LOtoLL(pos)
--MGRS
local _coord_mgrs = mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)), 5):gsub(string.char(9),' ')
-- DMS
local _coord_dms = mist.tostringLL(lat, lon, 3, true):gsub(string.char(9),' ')
-- DM.mm
local _coord_dm = mist.tostringLL(lat, lon, 4):gsub(string.char(9),' ')
local a,auom = UI.convertAltitude(_unitName,alt)
return _coord_mgrs,_coord_dms,_coord_dm, a, auom
--[[
local cfg = UI.unitConfig[_unitName]
local _coord = ''
local lat, lon, alt = coord.LOtoLL(pos)
if cfg.CoordFormat == 1 then --MGRS
_coord = mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)), 5)
elseif cfg.CoordFormat == 2 then -- DMS
_coord = mist.tostringLL(lat, lon, 2, true)
else -- DM.mm
_coord = mist.tostringLL(lat, lon, 2)
end
_coord = _coord:gsub(string.char(9),' ')
local a,auom = UI.convertAltitude(_unitName,alt)
return _coord, a, auom
--]]
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.convertSpeed(_unitName,_speed)
local s
local u
local cfg = UI.unitConfig[_unitName]
-- 1 = Knots, 2 = Mph, 3 = Kph
-- _speed in m/s
if cfg.SpeedUOM == 1 then
s = _speed * 1.943844
u = 'kt'
elseif cfg.SpeedUOM == 2 then
s = _speed * 2.23694
u = 'mph'
else
s = _speed * 36
u = 'kph'
end
return s,u
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.convertDistance(_unitName,_distance)
local s
local u
local cfg = UI.unitConfig[_unitName]
-- 1 = feet, 2 = yards, 3 = meters
-- _distance in meters
if cfg.DistanceUOM == 1 then
s = _distance * 3.28084
u = 'ft'
elseif cfg.DistanceUOM == 2 then
s = _distance * 1.093613
u = 'yd'
else
s = _distance
u = 'm'
end
return s,u
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.convertAngDistance(_unitName,_distance)
local s
local u
local cfg = UI.unitConfig[_unitName]
-- 1 = feet, 2 = yards, 3 = meters
-- _distance in meters
if cfg.DistanceUOM == 1 or cfg.DistanceUOM == 2 then
s = _distance * 3.4377492368197
u = 'MOA'
else
s = _distance
u = 'mils'
end
return s,u
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.convertAltitude(_unitName,_altitude)
local s
local u
local cfg = UI.unitConfig[_unitName]
-- 1 = feet, 2 = yards, 3 = meters
-- _distance in meters
if cfg.AltitudeUOM == 1 then
s = _altitude * 3.28084
u = 'ft'
else
s = _altitude
u = 'm'
end
return s,u
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.getRelativeDirection(_refHeading,_point1, _point2)
local tgtBearing
local xUnit = _point1.x
local yUnit = _point1.z
local xZone = _point2.x
local yZone = _point2.z
local xDiff = xUnit - xZone
local yDiff = yUnit - yZone
local tgtAngle = math.deg(math.atan(yDiff/xDiff))
if xDiff > 0 then
tgtBearing = 180 + tgtAngle
end
if xDiff < 0 and tgtAngle > 0 then
tgtBearing = tgtAngle
end
if xDiff < 0 and tgtAngle < 0 then
tgtBearing = 360 + tgtAngle
end
tgtBearing = tgtBearing - _refHeading
if tgtBearing > 360 then
tgtBearing = tgtBearing - 360
end
if tgtBearing < 0 then
tgtBearing = 360 + tgtBearing
end
return tgtBearing
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.getClockDirection(_direction)
-- by cfrag
if not _direction then return 0 end
while _direction < 0 do
_direction = _direction + 360
end
if _direction < 15 then -- special case 12 o'clock past 12 o'clock
return 12
end
_direction = _direction + 15 -- add offset so we get all other times correct
return math.floor(_direction/30)
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.getDirection(_point1, _point2)
local tgtBearing
local xUnit = _point1.x
local yUnit = _point1.z
local xZone = _point2.x
local yZone = _point2.z
local xDiff = xUnit - xZone
local yDiff = yUnit - yZone
local tgtAngle = math.deg(math.atan(yDiff/xDiff))
if xDiff > 0 then
tgtBearing = 180 + tgtAngle
end
if xDiff < 0 and tgtAngle > 0 then
tgtBearing = tgtAngle
end
if xDiff < 0 and tgtAngle < 0 then
tgtBearing = 360 + tgtAngle
end
return tgtBearing
end
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.rowCount(t)
local r = 0
if t then
for _,_ in pairs(t) do
r = r + 1
end
end
return r
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
function UI.inTable(v,t)
local r = true
for _,j in pairs(t) do
if j == v then
return true
end
end
return false
end -- function
-----------------------------------------------------------------------------------------------------------------------------------------------------
--
--#################################################################################################################################################
-- ############# Main body ###############
--#################################################################################################################################################
--
do
-- load event handler
world.addEventHandler(UI.EventHandler)
trigger.action.outText( 'Range UI - v'..UI.Version, 1 )
end