TommyC81 0441acf101
Documentation fixes. (#1803)
Improve the consistency of the module intros to the most commonly used version (single dash).

Add missing module information (abbreviated where none existed previously).

Fix broken documentation links

Make module names correspond to filenames (and fix links).

Fix typos.
2022-10-19 12:20:39 +02:00

1830 lines
58 KiB
Lua

--- **Functional** - (R2.5) - Yet Another Missile Trainer.
--
--
-- Practice to evade missiles without being destroyed.
--
--
-- ## Main Features:
--
-- * Handles air-to-air and surface-to-air missiles.
-- * Define your own training zones on the map. Players in this zone will be protected.
-- * Define launch zones. Only missiles launched in these zones are tracked.
-- * Define protected AI groups.
-- * F10 radio menu to adjust settings for each player.
-- * Alert on missile launch (optional).
-- * Marker of missile launch position (optional).
-- * Adaptive update of missile-to-player distance.
-- * Finite State Machine (FSM) implementation.
-- * Easy to use. See examples below.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Functional.Fox
-- @image Functional_FOX.png
--- FOX class.
-- @type FOX
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table menuadded Table of groups the menu was added for.
-- @field #boolean menudisabled If true, F10 menu for players is disabled.
-- @field #boolean destroy Default player setting for destroying missiles.
-- @field #boolean launchalert Default player setting for launch alerts.
-- @field #boolean marklaunch Default player setting for mark launch coordinates.
-- @field #table players Table of players.
-- @field #table missiles Table of tracked missiles.
-- @field #table safezones Table of practice zones.
-- @field #table launchzones Table of launch zones.
-- @field Core.Set#SET_GROUP protectedset Set of protected groups.
-- @field #number explosionpower Power of explostion when destroying the missile in kg TNT. Default 5 kg TNT.
-- @field #number explosiondist Missile player distance in meters for destroying smaller missiles. Default 200 m.
-- @field #number explosiondist2 Missile player distance in meters for destroying big missiles. Default 500 m.
-- @field #number bigmissilemass Explosion power of big missiles. Default 50 kg TNT. Big missiles will be destroyed earlier.
-- @field #number dt50 Time step [sec] for missile position updates if distance to target > 50 km. Default 5 sec.
-- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec.
-- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec.
-- @field #number dt01 Time step [sec] for missile position updates if distance to target > 1 km and < 5 km. Default 0.1 sec.
-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
-- @extends Core.Fsm#FSM
--- Fox 3!
--
-- ===
--
-- ![Banner Image](..\Presentations\FOX\FOX_Main.png)
--
-- # The FOX Concept
--
-- As you probably know [Fox](https://en.wikipedia.org/wiki/Fox_(code_word)) is a NATO brevity code for launching air-to-air munition. Therefore, the class name is not 100% accurate as this
-- script handles air-to-air but also surface-to-air missiles.
--
-- # Basic Script
--
-- -- Create a new missile trainer object.
-- fox=FOX:New()
--
-- -- Start missile trainer.
-- fox:Start()
--
-- # Training Zones
--
-- Players are only protected if they are inside one of the training zones.
--
-- -- Create a new missile trainer object.
-- fox=FOX:New()
--
-- -- Add training zones.
-- fox:AddSafeZone(ZONE:New("Training Zone Alpha"))
-- fox:AddSafeZone(ZONE:New("Training Zone Bravo"))
--
-- -- Start missile trainer.
-- fox:Start()
--
-- # Launch Zones
--
-- Missile launches are only monitored if the shooter is inside the defined launch zone.
--
-- -- Create a new missile trainer object.
-- fox=FOX:New()
--
-- -- Add training zones.
-- fox:AddLaunchZone(ZONE:New("Launch Zone SA-10 Krim"))
-- fox:AddLaunchZone(ZONE:New("Training Zone Bravo"))
--
-- -- Start missile trainer.
-- fox:Start()
--
-- # Protected AI Groups
--
-- Define AI protected groups. These groups cannot be harmed by missiles.
--
-- ## Add Individual Groups
--
-- -- Create a new missile trainer object.
-- fox=FOX:New()
--
-- -- Add single protected group(s).
-- fox:AddProtectedGroup(GROUP:FindByName("A-10 Protected"))
-- fox:AddProtectedGroup(GROUP:FindByName("Yak-40"))
--
-- -- Start missile trainer.
-- fox:Start()
--
-- # Fine Tuning
--
-- Todo!
--
-- # Special Events
--
-- Todo!
--
--
-- @field #FOX
FOX = {
ClassName = "FOX",
Debug = false,
lid = nil,
menuadded = {},
menudisabled = nil,
destroy = nil,
launchalert = nil,
marklaunch = nil,
missiles = {},
players = {},
safezones = {},
launchzones = {},
protectedset = nil,
explosionpower = 0.1,
explosiondist = 200,
explosiondist2 = 500,
bigmissilemass = 50,
destroy = nil,
dt50 = 5,
dt10 = 1,
dt05 = 0.5,
dt01 = 0.1,
dt00 = 0.01,
}
--- Player data table holding all important parameters of each player.
-- @type FOX.PlayerData
-- @field Wrapper.Unit#UNIT unit Aircraft of the player.
-- @field #string unitname Name of the unit.
-- @field Wrapper.Client#CLIENT client Client object of player.
-- @field #string callsign Callsign of player.
-- @field Wrapper.Group#GROUP group Aircraft group of player.
-- @field #string groupname Name of the the player aircraft group.
-- @field #string name Player name.
-- @field #number coalition Coalition number of player.
-- @field #boolean destroy Destroy missile.
-- @field #boolean launchalert Alert player on detected missile launch.
-- @field #boolean marklaunch Mark position of launched missile on F10 map.
-- @field #number defeated Number of missiles defeated.
-- @field #number dead Number of missiles not defeated.
-- @field #boolean inzone Player is inside a protected zone.
--- Missile data table.
-- @type FOX.MissileData
-- @field Wrapper.Unit#UNIT weapon Missile weapon unit.
-- @field #boolean active If true the missile is active.
-- @field #string missileType Type of missile.
-- @field #string missileName Name of missile.
-- @field #number missileRange Range of missile in meters.
-- @field #number fuseDist Fuse distance in meters.
-- @field #number explosive Explosive mass in kg TNT.
-- @field Wrapper.Unit#UNIT shooterUnit Unit that shot the missile.
-- @field Wrapper.Group#GROUP shooterGroup Group that shot the missile.
-- @field #number shooterCoalition Coalition side of the shooter.
-- @field #string shooterName Name of the shooter unit.
-- @field #number shotTime Abs. mission time in seconds the missile was fired.
-- @field Core.Point#COORDINATE shotCoord Coordinate where the missile was fired.
-- @field Wrapper.Unit#UNIT targetUnit Unit that was targeted.
-- @field #string targetName Name of the target unit or "unknown".
-- @field #string targetOrig Name of the "original" target, i.e. the one right after launched.
-- @field #FOX.PlayerData targetPlayer Player that was targeted or nil.
--- Main radio menu on group level.
-- @field #table MenuF10 Root menu table on group level.
FOX.MenuF10={}
--- Main radio menu on mission level.
-- @field #table MenuF10Root Root menu on mission level.
FOX.MenuF10Root=nil
--- FOX class version.
-- @field #string version
FOX.version="0.6.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list:
-- DONE: safe zones
-- DONE: mark shooter on F10
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new FOX class object.
-- @param #FOX self
-- @return #FOX self.
function FOX:New()
self.lid="FOX | "
-- Inherit everthing from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #FOX
-- Defaults:
self:SetDefaultMissileDestruction(true)
self:SetDefaultLaunchAlerts(true)
self:SetDefaultLaunchMarks(true)
-- Explosion/destruction defaults.
self:SetExplosionDistance()
self:SetExplosionDistanceBigMissiles()
self:SetExplosionPower()
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start FOX script.
self:AddTransition("*", "Status", "*") -- Status update.
self:AddTransition("*", "MissileLaunch", "*") -- Missile was launched.
self:AddTransition("*", "MissileDestroyed", "*") -- Missile was destroyed before impact.
self:AddTransition("*", "EnterSafeZone", "*") -- Player enters a safe zone.
self:AddTransition("*", "ExitSafeZone", "*") -- Player exists a safe zone.
self:AddTransition("Running", "Stop", "Stopped") -- Stop FOX script.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the FOX. Initializes parameters and starts event handlers.
-- @function [parent=#FOX] Start
-- @param #FOX self
--- Triggers the FSM event "Start" after a delay. Starts the FOX. Initializes parameters and starts event handlers.
-- @function [parent=#FOX] __Start
-- @param #FOX self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the FOX and all its event handlers.
-- @param #FOX self
--- Triggers the FSM event "Stop" after a delay. Stops the FOX and all its event handlers.
-- @function [parent=#FOX] __Stop
-- @param #FOX self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Status".
-- @function [parent=#FOX] Status
-- @param #FOX self
--- Triggers the FSM event "Status" after a delay.
-- @function [parent=#FOX] __Status
-- @param #FOX self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "MissileLaunch".
-- @function [parent=#FOX] MissileLaunch
-- @param #FOX self
-- @param #FOX.MissileData missile Data of the fired missile.
--- Triggers the FSM delayed event "MissileLaunch".
-- @function [parent=#FOX] __MissileLaunch
-- @param #FOX self
-- @param #number delay Delay in seconds before the function is called.
-- @param #FOX.MissileData missile Data of the fired missile.
--- On after "MissileLaunch" event user function. Called when a missile was launched.
-- @function [parent=#FOX] OnAfterMissileLaunch
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FOX.MissileData missile Data of the fired missile.
--- Triggers the FSM event "MissileDestroyed".
-- @function [parent=#FOX] MissileDestroyed
-- @param #FOX self
-- @param #FOX.MissileData missile Data of the destroyed missile.
--- Triggers the FSM delayed event "MissileDestroyed".
-- @function [parent=#FOX] __MissileDestroyed
-- @param #FOX self
-- @param #number delay Delay in seconds before the function is called.
-- @param #FOX.MissileData missile Data of the destroyed missile.
--- On after "MissileDestroyed" event user function. Called when a missile was destroyed.
-- @function [parent=#FOX] OnAfterMissileDestroyed
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FOX.MissileData missile Data of the destroyed missile.
--- Triggers the FSM event "EnterSafeZone".
-- @function [parent=#FOX] EnterSafeZone
-- @param #FOX self
-- @param #FOX.PlayerData player Player data.
--- Triggers the FSM delayed event "EnterSafeZone".
-- @function [parent=#FOX] __EnterSafeZone
-- @param #FOX self
-- @param #number delay Delay in seconds before the function is called.
-- @param #FOX.PlayerData player Player data.
--- On after "EnterSafeZone" event user function. Called when a player enters a safe zone.
-- @function [parent=#FOX] OnAfterEnterSafeZone
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FOX.PlayerData player Player data.
--- Triggers the FSM event "ExitSafeZone".
-- @function [parent=#FOX] ExitSafeZone
-- @param #FOX self
-- @param #FOX.PlayerData player Player data.
--- Triggers the FSM delayed event "ExitSafeZone".
-- @function [parent=#FOX] __ExitSafeZone
-- @param #FOX self
-- @param #number delay Delay in seconds before the function is called.
-- @param #FOX.PlayerData player Player data.
--- On after "ExitSafeZone" event user function. Called when a player exists a safe zone.
-- @function [parent=#FOX] OnAfterExitSafeZone
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FOX.PlayerData player Player data.
return self
end
--- On after Start event. Starts the missile trainer and adds event handlers.
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FOX:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting FOX Missile Trainer %s", FOX.version)
env.info(text)
-- Handle events:
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Shot)
if self.Debug then
self:HandleEvent(EVENTS.Hit)
end
if self.Debug then
self:TraceClass(self.ClassName)
self:TraceLevel(2)
end
self:__Status(-20)
end
--- On after Stop event. Stops the missile trainer and unhandles events.
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FOX:onafterStop(From, Event, To)
-- Short info.
local text=string.format("Stopping FOX Missile Trainer %s", FOX.version)
env.info(text)
-- Handle events:
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Shot)
if self.Debug then
self:UnhandleEvent(EVENTS.Hit)
end
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add a training zone. Players in the zone are safe.
-- @param #FOX self
-- @param Core.Zone#ZONE zone Training zone.
-- @return #FOX self
function FOX:AddSafeZone(zone)
table.insert(self.safezones, zone)
return self
end
--- Add a launch zone. Only missiles launched within these zones will be tracked.
-- @param #FOX self
-- @param Core.Zone#ZONE zone Training zone.
-- @return #FOX self
function FOX:AddLaunchZone(zone)
table.insert(self.launchzones, zone)
return self
end
--- Add a protected set of groups.
-- @param #FOX self
-- @param Core.Set#SET_GROUP groupset The set of groups.
-- @return #FOX self
function FOX:SetProtectedGroupSet(groupset)
self.protectedset=groupset
return self
end
--- Add a group to the protected set.
-- @param #FOX self
-- @param Wrapper.Group#GROUP group Protected group.
-- @return #FOX self
function FOX:AddProtectedGroup(group)
if not self.protectedset then
self.protectedset=SET_GROUP:New()
end
self.protectedset:AddGroup(group)
return self
end
--- Set explosion power. This is an "artificial" explosion generated when the missile is destroyed. Just for the visual effect.
-- Don't set the explosion power too big or it will harm the aircraft in the vicinity.
-- @param #FOX self
-- @param #number power Explosion power in kg TNT. Default 0.1 kg.
-- @return #FOX self
function FOX:SetExplosionPower(power)
self.explosionpower=power or 0.1
return self
end
--- Set missile-player distance when missile is destroyed.
-- @param #FOX self
-- @param #number distance Distance in meters. Default 200 m.
-- @return #FOX self
function FOX:SetExplosionDistance(distance)
self.explosiondist=distance or 200
return self
end
--- Set missile-player distance when BIG missiles are destroyed.
-- @param #FOX self
-- @param #number distance Distance in meters. Default 500 m.
-- @param #number explosivemass Explosive mass of missile threshold in kg TNT. Default 50 kg.
-- @return #FOX self
function FOX:SetExplosionDistanceBigMissiles(distance, explosivemass)
self.explosiondist2=distance or 500
self.bigmissilemass=explosivemass or 50
return self
end
--- Disable F10 menu for all players.
-- @param #FOX self
-- @return #FOX self
function FOX:SetDisableF10Menu()
self.menudisabled=true
return self
end
--- Enable F10 menu for all players.
-- @param #FOX self
-- @return #FOX self
function FOX:SetEnableF10Menu()
self.menudisabled=false
return self
end
--- Set default player setting for missile destruction.
-- @param #FOX self
-- @param #boolean switch If true missiles are destroyed. If false/nil missiles are not destroyed.
-- @return #FOX self
function FOX:SetDefaultMissileDestruction(switch)
if switch==nil then
self.destroy=false
else
self.destroy=switch
end
return self
end
--- Set default player setting for launch alerts.
-- @param #FOX self
-- @param #boolean switch If true launch alerts to players are active. If false/nil no launch alerts are given.
-- @return #FOX self
function FOX:SetDefaultLaunchAlerts(switch)
if switch==nil then
self.launchalert=false
else
self.launchalert=switch
end
return self
end
--- Set default player setting for marking missile launch coordinates
-- @param #FOX self
-- @param #boolean switch If true missile launches are marked. If false/nil marks are disabled.
-- @return #FOX self
function FOX:SetDefaultLaunchMarks(switch)
if switch==nil then
self.marklaunch=false
else
self.marklaunch=switch
end
return self
end
--- Set debug mode on/off.
-- @param #FOX self
-- @param #boolean switch If true debug mode on. If false/nil debug mode off.
-- @return #FOX self
function FOX:SetDebugOnOff(switch)
if switch==nil then
self.Debug=false
else
self.Debug=switch
end
return self
end
--- Set debug mode on.
-- @param #FOX self
-- @return #FOX self
function FOX:SetDebugOn()
self:SetDebugOnOff(true)
return self
end
--- Set debug mode off.
-- @param #FOX self
-- @return #FOX self
function FOX:SetDebugOff()
self:SetDebugOff(false)
return self
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Status Functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check spawn queue and spawn aircraft if necessary.
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FOX:onafterStatus(From, Event, To)
-- Get FSM state.
local fsmstate=self:GetState()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time)
-- Status.
self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate))
-- Check missile status.
self:_CheckMissileStatus()
-- Check player status.
self:_CheckPlayers()
if fsmstate=="Running" then
self:__Status(-10)
end
end
--- Check status of players.
-- @param #FOX self
function FOX:_CheckPlayers()
for playername,_playersettings in pairs(self.players) do
local playersettings=_playersettings --#FOX.PlayerData
local unitname=playersettings.unitname
local unit=UNIT:FindByName(unitname)
if unit and unit:IsAlive() then
local coord=unit:GetCoordinate()
local issafe=self:_CheckCoordSafe(coord)
if issafe then
-----------------------------
-- Player INSIDE Safe Zone --
-----------------------------
if not playersettings.inzone then
self:EnterSafeZone(playersettings)
playersettings.inzone=true
end
else
------------------------------
-- Player OUTSIDE Safe Zone --
------------------------------
if playersettings.inzone==true then
self:ExitSafeZone(playersettings)
playersettings.inzone=false
end
end
end
end
end
--- Remove missile.
-- @param #FOX self
-- @param #FOX.MissileData missile Missile data.
function FOX:_RemoveMissile(missile)
if missile then
for i,_missile in pairs(self.missiles) do
local m=_missile --#FOX.MissileData
if missile.missileName==m.missileName then
table.remove(self.missiles, i)
return
end
end
end
end
--- Missile status.
-- @param #FOX self
function FOX:_CheckMissileStatus()
local text="Missiles:"
local inactive={}
for i,_missile in pairs(self.missiles) do
local missile=_missile --#FOX.MissileData
local targetname="unkown"
if missile.targetUnit then
targetname=missile.targetUnit:GetName()
end
local playername="none"
if missile.targetPlayer then
playername=missile.targetPlayer.name
end
local active=tostring(missile.active)
local mtype=missile.missileType
local dtype=missile.missileType
local range=UTILS.MetersToNM(missile.missileRange)
if not active then
table.insert(inactive,i)
end
local heading=self:_GetWeapongHeading(missile.weapon)
text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s", i, mtype, active, range, heading, targetname, playername, missile.missileName)
end
if #self.missiles==0 then
text=text.." none"
end
self:I(self.lid..text)
-- Remove inactive missiles.
for i=#self.missiles,1,-1 do
local missile=self.missiles[i] --#FOX.MissileData
if missile and not missile.active then
table.remove(self.missiles, i)
end
end
end
--- Check if missile target is protected.
-- @param #FOX self
-- @param Wrapper.Unit#UNIT targetunit Target unit.
-- @return #boolean If true, unit is protected.
function FOX:_IsProtected(targetunit)
if not self.protectedset then
return false
end
if targetunit and targetunit:IsAlive() then
-- Get Group.
local targetgroup=targetunit:GetGroup()
if targetgroup then
local targetname=targetgroup:GetName()
for _,_group in pairs(self.protectedset:GetSetObjects()) do
local group=_group --Wrapper.Group#GROUP
if group then
local groupname=group:GetName()
-- Target belongs to a protected set.
if targetname==groupname then
return true
end
end
end
end
end
return false
end
--- Missle launch event.
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FOX.MissileData missile Fired missile
function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Tracking info and init of last bomb position.
local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName)
self:I(FOX.lid..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Loop over players.
for _,_player in pairs(self.players) do
local player=_player --#FOX.PlayerData
-- Player position.
local playerUnit=player.unit
-- Check that player is alive and of the opposite coalition.
if playerUnit and playerUnit:IsAlive() and player.coalition~=missile.shooterCoalition then
-- Player missile distance.
local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord)
-- Player bearing to missile.
local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord)
-- Alert that missile has been launched.
if player.launchalert then
-- Alert directly targeted players or players that are within missile max range.
if (missile.targetPlayer and player.unitname==missile.targetPlayer.unitname) or (distance<missile.missileRange) then
-- Inform player.
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing)
-- Say notching headings.
self:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
--TODO: ALERT or INFO depending on whether this is a direct target.
--TODO: lauchalertall option.
MESSAGE:New(text, 5, "ALERT"):ToClient(player.client)
end
end
-- Mark coordinate.
if player.marklaunch then
local text=string.format("Missile launch coordinates:\n%s\n%s", missile.shotCoord:ToStringLLDMS(), missile.shotCoord:ToStringBULLS(player.coalition))
missile.shotCoord:MarkToGroup(text, player.group)
end
end
end
-- Init missile position.
local _lastBombPos = {x=0,y=0,z=0}
-- Missile coordinate.
local missileCoord = nil --Core.Point#COORDINATE
-- Target unit of the missile.
local target=nil --Wrapper.Unit#UNIT
--- Function monitoring the position of a bomb until impact.
local function trackMissile(_ordnance)
-- When the pcall returns a failure the weapon has hit.
local _status,_bombPos = pcall(
function()
return _ordnance:getPoint()
end)
-- Check if status is not nil. If so, we have a valid point.
if _status then
----------------------------------------------
-- Still in the air. Remember this position --
----------------------------------------------
-- Missile position.
_lastBombPos = {x=_bombPos.x, y=_bombPos.y, z=_bombPos.z}
-- Missile coordinate.
missileCoord=COORDINATE:NewFromVec3(_lastBombPos)
-- Missile velocity in m/s.
local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity())
-- Update missile target if necessary.
self:GetMissileTarget(missile)
if missile.targetUnit then
-----------------------------------
-- Missile has a specific target --
-----------------------------------
if missile.targetPlayer then
-- Target is a player.
if missile.targetPlayer.destroy==true then
target=missile.targetUnit
end
else
-- Check if unit is protected.
if self:_IsProtected(missile.targetUnit) then
target=missile.targetUnit
end
end
else
------------------------------------
-- Missile has NO specific target --
------------------------------------
-- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not.
-- That would trigger the distance check right after missile launch if things to wrong.
--
-- Possible solutions:
-- * Time check: enable this check after X seconds after missile was fired. What is X?
-- * Coalition check. But would not work in training situations where blue on blue is valid!
-- * At least enable it for surface-to-air missiles.
local function _GetTarget(_unit)
local unit=_unit --Wrapper.Unit#UNIT
-- Player position.
local playerCoord=unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if dist<=self.explosiondist then
return unit
end
end
-- Distance to closest player.
local mindist=nil
-- Loop over players.
for _,_player in pairs(self.players) do
local player=_player --#FOX.PlayerData
-- Check that player was not the one who launched the missile.
if player.unitname~=missile.shooterName then
-- Player position.
local playerCoord=player.unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Distance from shooter to player.
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
mindist=dist
target=player.unit
end
end
end
if self.protectedset then
-- Distance to closest protected unit.
mindist=nil
for _,_group in pairs(self.protectedset:GetSet()) do
local group=_group --Wrapper.Group#GROUP
for _,_unit in pairs(group:GetUnits()) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
-- Check that player was not the one who launched the missile.
if unit:GetName()~=missile.shooterName then
-- Player position.
local playerCoord=unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Distance from shooter to player.
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
mindist=dist
target=unit
end
end
end
end
end
end
if target then
self:T(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m", missile.missileType, target:GetName(), tostring(mindist)))
end
end
-- Check if missile has a valid target.
if target then
-- Target coordinate.
local targetCoord=target:GetCoordinate()
-- Distance from missile to target.
local distance=missileCoord:Get3DDistance(targetCoord)
-- Distance missile to shooter.
local distShooter=nil
if missile.shooterUnit and missile.shooterUnit:IsAlive() then
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetCoordinate())
end
-- Debug output.
if self.Debug then
local bearing=targetCoord:HeadingTo(missileCoord)
local eta=distance/missileVelocity
-- Debug distance check.
self:I(self.lid..string.format("Missile %s Target %s: Distance = %.1f m, v=%.1f m/s, bearing=%03d°, ETA=%.1f sec", missile.missileType, target:GetName(), distance, missileVelocity, bearing, eta))
end
-- Distroy missile if it's getting too close.
local destroymissile=distance<=self.explosiondist
-- Check BIG missiles.
if self.explosiondist2 and distance<=self.explosiondist2 and not destroymissile then
destroymissile=missile.explosive>=self.bigmissilemass
end
-- If missile is 150 m from target ==> destroy missile if in safe zone.
if destroymissile and self:_CheckCoordSafe(targetCoord) then
-- Destroy missile.
self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m",
missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance))
_ordnance:destroy()
-- Missile is not active any more.
missile.active=false
-- Debug smoke.
if self.Debug then
missileCoord:SmokeRed()
targetCoord:SmokeGreen()
end
-- Create event.
self:MissileDestroyed(missile)
-- Little explosion for the visual effect.
if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then
missileCoord:Explosion(self.explosionpower)
end
-- Target was a player.
if missile.targetPlayer then
-- Message to target.
local text=string.format("Destroying missile. %s", self:_DeadText())
MESSAGE:New(text, 10):ToGroup(target:GetGroup())
-- Increase dead counter.
missile.targetPlayer.dead=missile.targetPlayer.dead+1
end
-- Terminate timer.
return nil
else
-- Time step.
local dt=1.0
if distance>50000 then
-- > 50 km
dt=self.dt50 --=5.0
elseif distance>10000 then
-- 10-50 km
dt=self.dt10 --=1.0
elseif distance>5000 then
-- 5-10 km
dt=self.dt05 --0.5
elseif distance>1000 then
-- 1-5 km
dt=self.dt01 --0.1
else
-- < 1 km
dt=self.dt00 --0.01
end
-- Check again in dt seconds.
return timer.getTime()+dt
end
else
-- Destroy missile.
self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName))
return timer.getTime()+0.1
-- No target ==> terminate timer.
--return nil
end
else
-------------------------------------
-- Missile does not exist any more --
-------------------------------------
if target then
-- Get human player.
local player=self:_GetPlayerFromUnit(target)
-- Check for player and distance < 10 km.
if player and player.unit:IsAlive() then -- and missileCoord and player.unit:GetCoordinate():Get3DDistance(missileCoord)<10*1000 then
local text=string.format("Missile defeated. Well done, %s!", player.name)
MESSAGE:New(text, 10):ToClient(player.client)
-- Increase defeated counter.
player.defeated=player.defeated+1
end
end
-- Missile is not active any more.
missile.active=false
--Terminate the timer.
self:T(FOX.lid..string.format("Terminating missile track timer."))
return nil
end -- _status check
end -- end function trackBomb
-- Weapon is not yet "alife" just yet. Start timer with a little delay.
self:T(FOX.lid..string.format("Tracking of missile starts in 0.0001 seconds."))
timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- FOX event handler for event birth.
-- @param #FOX self
-- @param Core.Event#EVENTDATA EventData
function FOX:OnEventPlayerEnterAircraft(EventData)
end
--- FOX event handler for event birth.
-- @param #FOX self
-- @param Core.Event#EVENTDATA EventData
function FOX:OnEventBirth(EventData)
self:F3({eventbirth = EventData})
-- Nil checks.
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
self:E(EventData)
return
end
-- Player unit and name.
local _unitName=EventData.IniUnitName
local playerunit, playername=self:_GetPlayerUnitAndName(_unitName)
-- Debug info.
self:T(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
self:T(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
self:T(self.lid.."BIRTH: player = "..tostring(playername))
-- Check if player entered.
if playerunit and playername then
local _uid=playerunit:GetID()
local _group=playerunit:GetGroup()
local _callsign=playerunit:GetCallsign()
-- Debug output.
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", playername, _callsign, _unitName, _group:GetName())
self:T(self.lid..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug)
-- Add F10 radio menu for player.
if not self.menudisabled then
self:ScheduleOnce(0.1, self._AddF10Commands, self, _unitName)
end
-- Player data.
local playerData={} --#FOX.PlayerData
-- Player unit, client and callsign.
playerData.unit = playerunit
playerData.unitname = _unitName
playerData.group = _group
playerData.groupname = _group:GetName()
playerData.name = playername
playerData.callsign = playerData.unit:GetCallsign()
playerData.client = CLIENT:FindByName(_unitName, nil, true)
playerData.coalition = _group:GetCoalition()
playerData.destroy=playerData.destroy or self.destroy
playerData.launchalert=playerData.launchalert or self.launchalert
playerData.marklaunch=playerData.marklaunch or self.marklaunch
playerData.defeated=playerData.defeated or 0
playerData.dead=playerData.dead or 0
-- Init player data.
self.players[playername]=playerData
end
end
--- Get missile target.
-- @param #FOX self
-- @param #FOX.MissileData missile The missile data table.
function FOX:GetMissileTarget(missile)
local target=nil
local targetName="unknown"
local targetUnit=nil --Wrapper.Unit#UNIT
if missile.weapon and missile.weapon:isExist() then
-- Get target of missile.
target=missile.weapon:getTarget()
-- Get the target unit. Note if if _target is not nil, the unit can sometimes not be found!
if target then
self:T2({missiletarget=target})
-- Get target unit.
targetUnit=UNIT:Find(target)
if targetUnit then
targetName=targetUnit:GetName()
missile.targetUnit=targetUnit
missile.targetPlayer=self:_GetPlayerFromUnit(missile.targetUnit)
end
end
end
-- Missile got new target.
if missile.targetName and missile.targetName~=targetName then
self:I(self.lid..string.format("Missile %s(%s) changed target to %s. Previous target was %s.", missile.missileType, missile.missileName, targetName, missile.targetName))
end
-- Set target name.
missile.targetName=targetName
end
--- FOX event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
-- @param #FOX self
-- @param Core.Event#EVENTDATA EventData
function FOX:OnEventShot(EventData)
self:T2({eventshot=EventData})
if EventData.Weapon==nil then
return
end
if EventData.IniDCSUnit==nil then
return
end
-- Weapon data.
local _weapon = EventData.WeaponName
local _target = EventData.Weapon:getTarget()
local _targetName = "unknown"
local _targetUnit = nil --Wrapper.Unit#UNIT
-- Weapon descriptor.
local desc=EventData.Weapon:getDesc()
self:T2({desc=desc})
-- Weapon category: 0=Shell, 1=Missile, 2=Rocket, 3=BOMB
local weaponcategory=desc.category
-- Missile category: 1=AAM, 2=SAM, 6=OTHER
local missilecategory=desc.missileCategory
local missilerange=nil
if missilecategory then
missilerange=desc.rangeMaxAltMax
end
-- Debug info.
self:T2(FOX.lid.."EVENT SHOT: FOX")
self:T2(FOX.lid..string.format("EVENT SHOT: Ini unit = %s", tostring(EventData.IniUnitName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Ini group = %s", tostring(EventData.IniGroupName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type = %s", tostring(_weapon)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s", tostring(weaponcategory)))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil categ = %s", tostring(missilecategory)))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil range = %s", tostring(missilerange)))
-- Check if fired in launch zone.
if not self:_CheckCoordLaunch(EventData.IniUnit:GetCoordinate()) then
self:T(self.lid.."Missile was not fired in launch zone. No tracking!")
return
end
-- Track missiles of type AAM=1, SAM=2 or OTHER=6
local _track = weaponcategory==1 and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6)
-- Only track missiles
if _track then
local missile={} --#FOX.MissileData
missile.active=true
missile.weapon=EventData.weapon
missile.missileType=_weapon
missile.missileRange=missilerange
missile.missileName=EventData.weapon:getName()
missile.shooterUnit=EventData.IniUnit
missile.shooterGroup=EventData.IniGroup
missile.shooterCoalition=EventData.IniUnit:GetCoalition()
missile.shooterName=EventData.IniUnitName
missile.shotTime=timer.getAbsTime()
missile.shotCoord=EventData.IniUnit:GetCoordinate()
missile.fuseDist=desc.fuseDist
missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass
missile.targetOrig=missile.targetName
-- Set missile target name, unit and player.
self:GetMissileTarget(missile)
self:I(FOX.lid..string.format("EVENT SHOT: Shooter=%s %s(%s) ==> Target=%s, fuse dist=%s, explosive=%s",
tostring(missile.shooterName), tostring(missile.missileType), tostring(missile.missileName), tostring(missile.targetName), tostring(missile.fuseDist), tostring(missile.explosive)))
-- Only track if target was a player or target is protected. Saw the 9M311 missiles have no target!
if missile.targetPlayer or self:_IsProtected(missile.targetUnit) or missile.targetName=="unknown" then
-- Add missile table.
table.insert(self.missiles, missile)
-- Trigger MissileLaunch event.
self:__MissileLaunch(0.1, missile)
end
end --if _track
end
--- FOX event handler for event hit.
-- @param #FOX self
-- @param Core.Event#EVENTDATA EventData
function FOX:OnEventHit(EventData)
self:T({eventhit = EventData})
-- Nil checks.
if EventData.Weapon==nil then
return
end
if EventData.IniUnit==nil then
return
end
if EventData.TgtUnit==nil then
return
end
local weapon=EventData.Weapon
local weaponname=weapon:getName()
for i,_missile in pairs(self.missiles) do
local missile=_missile --#FOX.MissileData
if missile.missileName==weaponname then
self:I(self.lid..string.format("WARNING: Missile %s (%s) hit target %s. Missile trainer target was %s.", missile.missileType, missile.missileName, EventData.TgtUnitName, missile.targetName))
self:I({missile=missile})
return
end
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- RADIO MENU Functions
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add menu commands for player.
-- @param #FOX self
-- @param #string _unitName Name of player unit.
function FOX:_AddF10Commands(_unitName)
self:F(_unitName)
-- Get player unit and name.
local _unit, playername = self:_GetPlayerUnitAndName(_unitName)
-- Check for player unit.
if _unit and playername then
-- Get group and ID.
local group=_unit:GetGroup()
local gid=group:GetID()
if group and gid then
if not self.menuadded[gid] then
-- Enable switch so we don't do this twice.
self.menuadded[gid]=true
-- Set menu root path.
local _rootPath=nil
if FOX.MenuF10Root then
------------------------
-- MISSON LEVEL MENUE --
------------------------
-- F10/FOX/...
_rootPath=FOX.MenuF10Root
else
------------------------
-- GROUP LEVEL MENUES --
------------------------
-- Main F10 menu: F10/FOX/
if FOX.MenuF10[gid]==nil then
FOX.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FOX")
end
-- F10/FOX/...
_rootPath=FOX.MenuF10[gid]
end
--------------------------------
-- F10/F<X> FOX/F1 Help
--------------------------------
--local _helpPath=missionCommands.addSubMenuForGroup(gid, "Help", _rootPath)
-- F10/FOX/F1 Help/
--missionCommands.addCommandForGroup(gid, "Subtitles On/Off", _helpPath, self._SubtitlesOnOff, self, _unitName) -- F7
--missionCommands.addCommandForGroup(gid, "Trapsheet On/Off", _helpPath, self._TrapsheetOnOff, self, _unitName) -- F8
-------------------------
-- F10/F<X> FOX/
-------------------------
missionCommands.addCommandForGroup(gid, "Destroy Missiles On/Off", _rootPath, self._ToggleDestroyMissiles, self, _unitName) -- F1
missionCommands.addCommandForGroup(gid, "Launch Alerts On/Off", _rootPath, self._ToggleLaunchAlert, self, _unitName) -- F2
missionCommands.addCommandForGroup(gid, "Mark Launch On/Off", _rootPath, self._ToggleLaunchMark, self, _unitName) -- F3
missionCommands.addCommandForGroup(gid, "My Status", _rootPath, self._MyStatus, self, _unitName) -- F4
end
else
self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.", _unitName or "unknown"))
end
else
self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.", _unitName or "unknown"))
end
end
--- Turn player's launch alert on/off.
-- @param #FOX self
-- @param #string _unitname Name of the player unit.
function FOX:_MyStatus(_unitname)
self:F2(_unitname)
-- Get player unit and player name.
local unit, playername = self:_GetPlayerUnitAndName(_unitname)
-- Check if we have a player.
if unit and playername then
-- Player data.
local playerData=self.players[playername] --#FOX.PlayerData
if playerData then
local m,mtext=self:_GetTargetMissiles(playerData.name)
local text=string.format("Status of player %s:\n", playerData.name)
local safe=self:_CheckCoordSafe(playerData.unit:GetCoordinate())
text=text..string.format("Destroy missiles? %s\n", tostring(playerData.destroy))
text=text..string.format("Launch alert? %s\n", tostring(playerData.launchalert))
text=text..string.format("Launch marks? %s\n", tostring(playerData.marklaunch))
text=text..string.format("Am I safe? %s\n", tostring(safe))
text=text..string.format("Missiles defeated: %d\n", playerData.defeated)
text=text..string.format("Missiles destroyed: %d\n", playerData.dead)
text=text..string.format("Me target: %d\n%s", m, mtext)
MESSAGE:New(text, 10, nil, true):ToClient(playerData.client)
end
end
end
--- Turn player's launch alert on/off.
-- @param #FOX self
-- @param #string playername Name of the player.
-- @return #number Number of missiles targeting the player.
-- @return #string Missile info.
function FOX:_GetTargetMissiles(playername)
local text=""
local n=0
for _,_missile in pairs(self.missiles) do
local missile=_missile --#FOX.MissileData
if missile.targetPlayer and missile.targetPlayer.name==playername then
n=n+1
text=text..string.format("Type %s: active %s\n", missile.missileType, tostring(missile.active))
end
end
return n,text
end
--- Turn player's launch alert on/off.
-- @param #FOX self
-- @param #string _unitname Name of the player unit.
function FOX:_ToggleLaunchAlert(_unitname)
self:F2(_unitname)
-- Get player unit and player name.
local unit, playername = self:_GetPlayerUnitAndName(_unitname)
-- Check if we have a player.
if unit and playername then
-- Player data.
local playerData=self.players[playername] --#FOX.PlayerData
if playerData then
-- Invert state.
playerData.launchalert=not playerData.launchalert
-- Inform player.
local text=""
if playerData.launchalert==true then
text=string.format("%s, missile launch alerts are now ENABLED.", playerData.name)
else
text=string.format("%s, missile launch alerts are now DISABLED.", playerData.name)
end
MESSAGE:New(text, 5):ToClient(playerData.client)
end
end
end
--- Turn player's launch marks on/off.
-- @param #FOX self
-- @param #string _unitname Name of the player unit.
function FOX:_ToggleLaunchMark(_unitname)
self:F2(_unitname)
-- Get player unit and player name.
local unit, playername = self:_GetPlayerUnitAndName(_unitname)
-- Check if we have a player.
if unit and playername then
-- Player data.
local playerData=self.players[playername] --#FOX.PlayerData
if playerData then
-- Invert state.
playerData.marklaunch=not playerData.marklaunch
-- Inform player.
local text=""
if playerData.marklaunch==true then
text=string.format("%s, missile launch marks are now ENABLED.", playerData.name)
else
text=string.format("%s, missile launch marks are now DISABLED.", playerData.name)
end
MESSAGE:New(text, 5):ToClient(playerData.client)
end
end
end
--- Turn destruction of missiles on/off for player.
-- @param #FOX self
-- @param #string _unitname Name of the player unit.
function FOX:_ToggleDestroyMissiles(_unitname)
self:F2(_unitname)
-- Get player unit and player name.
local unit, playername = self:_GetPlayerUnitAndName(_unitname)
-- Check if we have a player.
if unit and playername then
-- Player data.
local playerData=self.players[playername] --#FOX.PlayerData
if playerData then
-- Invert state.
playerData.destroy=not playerData.destroy
-- Inform player.
local text=""
if playerData.destroy==true then
text=string.format("%s, incoming missiles will be DESTROYED.", playerData.name)
else
text=string.format("%s, incoming missiles will NOT be DESTROYED.", playerData.name)
end
MESSAGE:New(text, 5):ToClient(playerData.client)
end
end
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get a random text message in case you die.
-- @param #FOX self
-- @return #string Text in case you die.
function FOX:_DeadText()
local texts={}
texts[1]="You're dead!"
texts[2]="Meet your maker!"
texts[3]="Time to meet your maker!"
texts[4]="Well, I guess that was it!"
texts[5]="Bye, bye!"
texts[6]="Cheers buddy, was nice knowing you!"
local r=math.random(#texts)
return texts[r]
end
--- Check if a coordinate lies within a safe training zone.
-- @param #FOX self
-- @param Core.Point#COORDINATE coord Coordinate to check.
-- @return #boolean True if safe.
function FOX:_CheckCoordSafe(coord)
-- No safe zones defined ==> Everything is safe.
if #self.safezones==0 then
return true
end
-- Loop over all zones.
for _,_zone in pairs(self.safezones) do
local zone=_zone --Core.Zone#ZONE
local inzone=zone:IsCoordinateInZone(coord)
if inzone then
return true
end
end
return false
end
--- Check if a coordinate lies within a launch zone.
-- @param #FOX self
-- @param Core.Point#COORDINATE coord Coordinate to check.
-- @return #boolean True if in launch zone.
function FOX:_CheckCoordLaunch(coord)
-- No safe zones defined ==> Everything is safe.
if #self.launchzones==0 then
return true
end
-- Loop over all zones.
for _,_zone in pairs(self.launchzones) do
local zone=_zone --Core.Zone#ZONE
local inzone=zone:IsCoordinateInZone(coord)
if inzone then
return true
end
end
return false
end
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
-- @param #FOX self
-- @param DCS#Weapon weapon The weapon.
-- @return #number Heading of weapon in degrees or -1.
function FOX:_GetWeapongHeading(weapon)
if weapon and weapon:isExist() then
local wp=weapon:getPosition()
local wph = math.atan2(wp.x.z, wp.x.x)
if wph < 0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
return wph
end
return -1
end
--- Tell player notching headings.
-- @param #FOX self
-- @param #FOX.PlayerData playerData Player data.
-- @param DCS#Weapon weapon The weapon.
function FOX:_SayNotchingHeadings(playerData, weapon)
if playerData and playerData.unit and playerData.unit:IsAlive() then
local nr, nl=self:_GetNotchingHeadings(weapon)
if nr and nl then
local text=string.format("Notching heading %03d° or %03d°", nr, nl)
MESSAGE:New(text, 5, "FOX"):ToClient(playerData.client)
end
end
end
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
-- @param #FOX self
-- @param DCS#Weapon weapon The weapon.
-- @return #number Notching heading right, i.e. missile heading +90°.
-- @return #number Notching heading left, i.e. missile heading -90°.
function FOX:_GetNotchingHeadings(weapon)
if weapon then
local hdg=self:_GetWeapongHeading(weapon)
local hdg1=hdg+90
if hdg1>360 then
hdg1=hdg1-360
end
local hdg2=hdg-90
if hdg2<0 then
hdg2=hdg2+360
end
return hdg1, hdg2
end
return nil, nil
end
--- Returns the player data from a unit name.
-- @param #FOX self
-- @param #string unitName Name of the unit.
-- @return #FOX.PlayerData Player data.
function FOX:_GetPlayerFromUnitname(unitName)
for _,_player in pairs(self.players) do
local player=_player --#FOX.PlayerData
if player.unitname==unitName then
return player
end
end
return nil
end
--- Retruns the player data from a unit.
-- @param #FOX self
-- @param Wrapper.Unit#UNIT unit
-- @return #FOX.PlayerData Player data.
function FOX:_GetPlayerFromUnit(unit)
if unit and unit:IsAlive() then
-- Name of the unit
local unitname=unit:GetName()
for _,_player in pairs(self.players) do
local player=_player --#FOX.PlayerData
if player.unitname==unitname then
return player
end
end
end
return nil
end
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
-- @param #FOX self
-- @param #string _unitName Name of the player unit.
-- @return Wrapper.Unit#UNIT Unit of player or nil.
-- @return #string Name of the player or nil.
function FOX:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName ~= nil then
-- Get DCS unit from its name.
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
-- Get player name if any.
local playername=DCSunit:getPlayerName()
-- Unit object.
local unit=UNIT:Find(DCSunit)
-- Debug.
self:T2({DCSunit=DCSunit, unit=unit, playername=playername})
-- Check if enverything is there.
if DCSunit and unit and playername then
self:T(self.lid..string.format("Found DCS unit %s with player %s.", tostring(_unitName), tostring(playername)))
return unit, playername
end
end
end
-- Return nil if we could not find a player.
return nil,nil
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------