#Fixes from dev

This commit is contained in:
Applevangelist 2023-02-23 10:30:51 +01:00
parent 9cfed56847
commit e95c1ad3aa
11 changed files with 844 additions and 846 deletions

View File

@ -305,8 +305,8 @@ EVENTS = {
-- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object.
-- @field #string PlaceName The name of the airbase.
--
-- @field #table weapon The weapon used during the event.
-- @field #table Weapon
-- @field DCS#Weapon weapon The weapon used during the event.
-- @field DCS#Weapon Weapon The weapon used during the event.
-- @field #string WeaponName Name of the weapon.
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon.
--

View File

@ -1160,12 +1160,15 @@ do -- COORDINATE
--- Return the 3D distance in meters between the target COORDINATE and the COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE TargetCoordinate The target COORDINATE.
-- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3.
-- @return DCS#Distance Distance The distance in meters.
function COORDINATE:Get3DDistance( TargetCoordinate )
local TargetVec3 = TargetCoordinate:GetVec3()
--local TargetVec3 = TargetCoordinate:GetVec3()
local TargetVec3 = {x=TargetCoordinate.x, y=TargetCoordinate.y, z=TargetCoordinate.z}
local SourceVec3 = self:GetVec3()
return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5
--local dist=( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5
local dist=UTILS.VecDist3D(TargetVec3, SourceVec3)
return dist
end

View File

@ -555,7 +555,7 @@ function ZONE_BASE:GetZoneMaybe()
end
end
-- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists.
--- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists.
-- @param #ZONE_BASE self
-- @param #string PropertyName The name of a the TriggerZone Property to be retrieved.
-- @return #string The Value of the TriggerZone Property with the given PropertyName, or nil if absent.
@ -569,7 +569,7 @@ function ZONE_BASE:GetProperty(PropertyName)
return self.Properties[PropertyName]
end
-- Returns the zone Properties table.
--- Returns the zone Properties table.
-- @param #ZONE_BASE self
-- @return #table The Key:Value table of TriggerZone properties of the zone.
function ZONE_BASE:GetAllProperties()
@ -927,7 +927,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
local ZoneCoord = self:GetCoordinate()
local ZoneRadius = self:GetRadius()
self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
--self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
local SphereSearch = {
id = world.VolumeType.SPHERE,
@ -2084,13 +2084,10 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
return self
end
--- Get the smallest circular zone encompassing all points points of the polygon zone.
--- Get the smallest radius encompassing all points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone.
-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered.
-- @return #ZONE_RADIUS The circular zone.
function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone)
-- @return #number Radius of the zone in meters.
function ZONE_POLYGON_BASE:GetRadius()
local center=self:GetVec2()
@ -2107,6 +2104,20 @@ function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone)
end
return radius
end
--- Get the smallest circular zone encompassing all points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone.
-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered.
-- @return #ZONE_RADIUS The circular zone.
function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone)
local center=self:GetVec2()
local radius=self:GetRadius()
local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName, center, radius, DoNotRegisterZone)
return zone
@ -2913,7 +2924,7 @@ do -- ZONE_ELASTIC
--- Add a set of groups. Positions of the group will be considered as polygon vertices when contructing the convex hull.
-- @param #ZONE_ELASTIC self
-- @param Core.Set#SET_GROUP SetGroup Set of groups.
-- @param Core.Set#SET_GROUP GroupSet Set of groups.
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:AddSetGroup(GroupSet)

View File

@ -103,6 +103,7 @@
-- @field #number coalition The coalition of the arty group.
-- @field #boolean respawnafterdeath Respawn arty group after all units are dead.
-- @field #number respawndelay Respawn delay in seconds.
-- @field #number dtTrack Time interval in seconds for weapon tracking.
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can
@ -693,7 +694,7 @@ ARTY.db={
--- Arty script version.
-- @field #string version
ARTY.version="1.2.0"
ARTY.version="1.3.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -802,6 +803,9 @@ function ARTY:New(group, alias)
self.ismobile=false
end
-- Set track time interval.
self.dtTrack=0.2
-- Set speed to 0.7 of maximum.
self.Speed=self.SpeedMax * 0.7
@ -1497,6 +1501,15 @@ function ARTY:SetStatusInterval(interval)
return self
end
--- Set time interval for weapon tracking.
-- @param #ARTY self
-- @param #number interval Time interval in seconds. Default 0.2 seconds.
-- @return self
function ARTY:SetTrackInterval(interval)
self.dtTrack=interval or 0.2
return self
end
--- Set time how it is waited a unit the first shot event happens. If no shot is fired after this time, the task to fire is aborted and the target removed.
-- @param #ARTY self
-- @param #number waittime Time in seconds. Default 300 seconds.
@ -2129,6 +2142,95 @@ end
-- Event Handling
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function called during tracking of weapon.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #ARTY self ARTY object.
-- @param #ARTY.Target target Target of the weapon.
function ARTY._FuncTrack(weapon, self, target)
-- Coordinate and distance to target.
local _coord=weapon.coordinate
local _dist=_coord:Get2DDistance(target.coord)
local _destroyweapon=false
-- Debug
self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m", self.groupname,_dist))
if target.weapontype==ARTY.WeaponType.IlluminationShells then
-- Check if within distace.
if _dist<target.radius then
-- Get random coordinate within certain radius of the target.
local _cr=target.coord:GetRandomCoordinateInRadius(target.radius)
-- Get random altitude over target.
local _alt=_cr:GetLandHeight()+math.random(self.illuMinalt, self.illuMaxalt)
-- Adjust explosion height of coordinate.
local _ci=COORDINATE:New(_cr.x,_alt,_cr.z)
-- Create illumination flare.
_ci:IlluminationBomb(self.illuPower)
-- Destroy actual shell.
_destroyweapon=true
end
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
if _dist<target.radius then
-- Get random coordinate within a certain radius.
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Fire smoke at this coordinate.
_cr:Smoke(self.smokeColor)
-- Destroy actual shell.
_destroyweapon=true
end
end
if _destroyweapon then
self:T2(self.lid..string.format("ARTY %s destroying shell, stopping timer.", self.groupname))
-- Destroy weapon and stop timer.
weapon:Destroy()
-- No more tracking.
weapon.tracking=false
end
end
--- Function called after impact of weapon.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #ARTY self ARTY object.
-- @param #ARTY.Target target Target of the weapon.
function ARTY._FuncImpact(weapon, self, target)
-- Debug info.
self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.", self.groupname))
-- Get impact coordinate.
local _impactcoord=weapon:GetImpactCoordinate()
-- Create a "nuclear" explosion and blast at the impact point.
if target.weapontype==ARTY.WeaponType.TacticalNukes then
self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.", self.groupname))
--SCHEDULER:New(nil, ARTY._NuclearBlast, {self,_impactcoord}, 1.0)
self:ScheduleOnce(1.0, ARTY._NuclearBlast, self, _impactcoord)
end
end
--- Eventhandler for shot event.
-- @param #ARTY self
-- @param Core.Event#EVENTDATA EventData
@ -2162,128 +2264,32 @@ function ARTY:OnEventShot(EventData)
self:T(self.lid..text)
MESSAGE:New(text, 5):Clear():ToAllIf(self.report or self.Debug)
-- Last known position of the weapon fired.
local _lastpos={x=0, y=0, z=0}
--- Track the position of the weapon if it is supposed to model a tac nuke, illumination or smoke shell.
-- @param #table _weapon
local function _TrackWeapon(_data)
-- When the pcall status returns false the weapon has hit.
local _weaponalive,_currpos = pcall(
function()
return _data.weapon:getPoint()
end)
-- Debug
self:T3(self.lid..string.format("ARTY %s: Weapon still in air: %s", self.groupname, tostring(_weaponalive)))
-- Destroy weapon before impact.
local _destroyweapon=false
if _weaponalive then
-- Update last position.
_lastpos={x=_currpos.x, y=_currpos.y, z=_currpos.z}
-- Coordinate and distance to target.
local _coord=COORDINATE:NewFromVec3(_lastpos)
local _dist=_coord:Get2DDistance(_data.target.coord)
-- Debug
self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m", self.groupname,_dist))
if _data.target.weapontype==ARTY.WeaponType.IlluminationShells then
-- Check if within distace.
if _dist<_data.target.radius then
-- Get random coordinate within certain radius of the target.
local _cr=_data.target.coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Get random altitude over target.
local _alt=_cr:GetLandHeight()+math.random(self.illuMinalt, self.illuMaxalt)
-- Adjust explosion height of coordinate.
local _ci=COORDINATE:New(_cr.x,_alt,_cr.z)
-- Create illumination flare.
_ci:IlluminationBomb(self.illuPower)
-- Destroy actual shell.
_destroyweapon=true
end
elseif _data.target.weapontype==ARTY.WeaponType.SmokeShells then
if _dist<_data.target.radius then
-- Get random coordinate within a certain radius.
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Fire smoke at this coordinate.
_cr:Smoke(self.smokeColor)
-- Destroy actual shell.
_destroyweapon=true
end
end
if _destroyweapon then
self:T2(self.lid..string.format("ARTY %s destroying shell, stopping timer.", self.groupname))
-- Destroy weapon and stop timer.
_data.weapon:destroy()
return nil
else
-- TODO: Make dt input parameter.
local dt=0.02
self:T3(self.lid..string.format("ARTY %s tracking weapon again in %.3f seconds", self.groupname, dt))
-- Check again in 0.05 seconds.
return timer.getTime() + dt
end
else
-- Get impact coordinate.
local _impactcoord=COORDINATE:NewFromVec3(_lastpos)
self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.", self.groupname))
-- Create a "nuclear" explosion and blast at the impact point.
if _data.target.weapontype==ARTY.WeaponType.TacticalNukes then
self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.", self.groupname))
SCHEDULER:New(nil, ARTY._NuclearBlast, {self,_impactcoord}, 1.0)
end
-- Stop timer.
return nil
end
end
-- Start track the shell if we want to model a tactical nuke.
local _tracknuke = self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes>0
local _trackillu = self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0
local _tracksmoke = self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0
if _tracknuke or _trackillu or _tracksmoke then
-- Debug info.
self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname))
local _peter={}
_peter.weapon=EventData.weapon
_peter.target=UTILS.DeepCopy(self.currentTarget)
-- Create a weapon object.
local weapon=WEAPON:New(EventData.weapon)
timer.scheduleFunction(_TrackWeapon, _peter, timer.getTime() + 2.0)
-- Set time step for tracking.
weapon:SetTimeStepTrack(self.dtTrack)
-- Copy target. We need a copy because it might already be overwritten with the next target during flight of weapon.
local target=UTILS.DeepCopy(self.currentTarget)
-- Set callback functions.
weapon:SetFuncTrack(ARTY._FuncTrack, self, target)
weapon:SetFuncImpact(ARTY._FuncImpact, self, target)
-- Start tracking in 2 sec (arty ammo should fly a bit).
weapon:StartTrack(2)
end
-- Get current ammo.
@ -3931,9 +3937,10 @@ function ARTY:GetAmmo(display)
return nammo, nshells, nrockets, nmissiles
end
for _,unit in pairs(units) do
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
if unit then
-- Output.
local text=string.format("ARTY group %s - unit %s:\n", self.groupname, unit:GetName())

View File

@ -26,6 +26,7 @@
--- FOX class.
-- @type FOX
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @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.
@ -124,6 +125,7 @@
-- @field #FOX
FOX = {
ClassName = "FOX",
verbose = 0,
Debug = false,
lid = nil,
menuadded = {},
@ -168,7 +170,7 @@ FOX = {
--- Missile data table.
-- @type FOX.MissileData
-- @field Wrapper.Unit#UNIT weapon Missile weapon unit.
-- @field DCS#Weapon weapon Missile weapon object.
-- @field #boolean active If true the missile is active.
-- @field #string missileType Type of missile.
-- @field #string missileName Name of missile.
@ -185,6 +187,8 @@ FOX = {
-- @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.
-- @field Core.Point#COORDINATE missileCoord Missile coordinate during tracking.
-- @field Wrapper.Weapon#WEAPON Weapon Weapon object.
--- Main radio menu on group level.
-- @field #table MenuF10 Root menu table on group level.
@ -196,7 +200,7 @@ FOX.MenuF10Root=nil
--- FOX class version.
-- @field #string version
FOX.version="0.6.1"
FOX.version="0.8.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@ -500,6 +504,7 @@ function FOX:SetDisableF10Menu()
return self
end
--- Enable F10 menu for all players.
-- @param #FOX self
-- @return #FOX self
@ -510,6 +515,15 @@ function FOX:SetEnableF10Menu()
return self
end
--- Set verbosity level.
-- @param #FOX self
-- @param #number VerbosityLevel Level of output (higher=more). Default 0.
-- @return #FOX self
function FOX:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
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.
@ -605,7 +619,9 @@ function FOX:onafterStatus(From, Event, To)
local clock=UTILS.SecondsToClock(time)
-- Status.
if self.verbose>=1 then
self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate))
end
-- Check missile status.
self:_CheckMissileStatus()
@ -713,7 +729,9 @@ function FOX:_CheckMissileStatus()
if #self.missiles==0 then
text=text.." none"
end
if self.verbose>=2 then
self:I(self.lid..text)
end
-- Remove inactive missiles.
for i=#self.missiles,1,-1 do
@ -743,7 +761,7 @@ function FOX:_IsProtected(targetunit)
if targetgroup then
local targetname=targetgroup:GetName()
for _,_group in pairs(self.protectedset:GetSetObjects()) do
for _,_group in pairs(self.protectedset:GetSet()) do
local group=_group --Wrapper.Group#GROUP
if group then
@ -762,101 +780,25 @@ function FOX:_IsProtected(targetunit)
return false
end
--- Missle launch event.
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- Function called from weapon tracking.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #FOX self FOX object.
-- @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}
function FOX._FuncTrack(weapon, self, missile)
-- 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)
local missileCoord= missile.missileCoord:UpdateFromVec3(weapon.vec3) --COORDINATE:NewFromVec3(_lastBombPos)
-- Missile velocity in m/s.
local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity())
local missileVelocity=weapon:GetSpeed() --UTILS.VecNorm(_ordnance:getVelocity())
-- Update missile target if necessary.
self:GetMissileTarget(missile)
-- Target unit of the missile.
local target=nil --Wrapper.Unit#UNIT
if missile.targetUnit then
-----------------------------------
@ -947,13 +889,13 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
if unit:GetName()~=missile.shooterName then
-- Player position.
local playerCoord=unit:GetCoordinate()
local playerVec3=unit:GetVec3()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
local dist=missileCoord:Get3DDistance(playerVec3)
-- Distance from shooter to player.
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
local Dshooter2player=missile.shotCoord:Get3DDistance(playerVec3)
-- 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
@ -977,23 +919,22 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
if target then
-- Target coordinate.
local targetCoord=target:GetCoordinate()
local targetVec3=target:GetVec3() --target:GetCoordinate()
-- Distance from missile to target.
local distance=missileCoord:Get3DDistance(targetCoord)
local distance=missileCoord:Get3DDistance(targetVec3)
-- Distance missile to shooter.
local distShooter=nil
if missile.shooterUnit and missile.shooterUnit:IsAlive() then
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetCoordinate())
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetVec3())
end
-- Debug output.
if self.Debug then
local bearing=targetCoord:HeadingTo(missileCoord)
local bearing=missileCoord:HeadingTo(targetVec3)
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
@ -1007,12 +948,12 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
end
-- If missile is 150 m from target ==> destroy missile if in safe zone.
if destroymissile and self:_CheckCoordSafe(targetCoord) then
if destroymissile and self:_CheckCoordSafe(targetVec3) 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()
weapon:Destroy()
-- Missile is not active any more.
missile.active=false
@ -1020,7 +961,6 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Debug smoke.
if self.Debug then
missileCoord:SmokeRed()
targetCoord:SmokeGreen()
end
-- Create event.
@ -1042,8 +982,8 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
missile.targetPlayer.dead=missile.targetPlayer.dead+1
end
-- Terminate timer.
return nil
-- We could disable the tracking here but then the impact function would not be called.
--weapon.tracking=false
else
@ -1066,30 +1006,30 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
dt=self.dt00 --0.01
end
-- Check again in dt seconds.
return timer.getTime()+dt
-- Set time step.
weapon:SetTimeStepTrack(dt)
end
else
-- Destroy missile.
-- No current target.
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
weapon:SetTimeStepTrack(0.1)
-- No target ==> terminate timer.
--return nil
end
else
end
-------------------------------------
-- Missile does not exist any more --
-------------------------------------
--- Callback function on impact or destroy otherwise.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #FOX self FOX object.
-- @param #FOX.MissileData missile Fired missile.
function FOX._FuncImpact(weapon, self, missile)
if target then
if missile.targetPlayer then
-- Get human player.
local player=self:_GetPlayerFromUnit(target)
local player=missile.targetPlayer
-- 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
@ -1107,15 +1047,79 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
--Terminate the timer.
self:T(FOX.lid..string.format("Terminating missile track timer."))
return nil
weapon.tracking=false
end -- _status check
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
-- Set callback function for tracking.
missile.Weapon:SetFuncTrack(FOX._FuncTrack, self, missile)
-- Set callback function for impact.
missile.Weapon:SetFuncImpact(FOX._FuncImpact, self, missile)
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)
--timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001)
missile.Weapon:StartTrack(0.0001)
end
@ -1247,29 +1251,28 @@ end
function FOX:OnEventShot(EventData)
self:T2({eventshot=EventData})
if EventData.Weapon==nil then
return
end
if EventData.IniDCSUnit==nil then
-- Nil checks.
if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.weapon==nil then
return
end
-- Create a weapon object.
local weapon=WEAPON:New(EventData.weapon)
-- Weapon data.
local _weapon = EventData.WeaponName
local _weapon = weapon:GetTypeName()
local _target = EventData.Weapon:getTarget()
local _targetName = "unknown"
local _targetUnit = nil --Wrapper.Unit#UNIT
-- Weapon descriptor.
local desc=EventData.Weapon:getDesc()
local desc=weapon.desc
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
-- Missile range.
local missilerange=nil
if missilecategory then
missilerange=desc.rangeMaxAltMax
@ -1279,8 +1282,8 @@ function FOX:OnEventShot(EventData)
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: Weapon type = %s", tostring(weapon:GetTypeName())))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s", tostring(weapon:GetCategory())))
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)))
@ -1292,7 +1295,7 @@ function FOX:OnEventShot(EventData)
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)
local _track = weapon:IsMissile() and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6)
-- Only track missiles
if _track then
@ -1301,6 +1304,7 @@ function FOX:OnEventShot(EventData)
missile.active=true
missile.weapon=EventData.weapon
missile.Weapon=weapon
missile.missileType=_weapon
missile.missileRange=missilerange
missile.missileName=EventData.weapon:getName()
@ -1313,6 +1317,7 @@ function FOX:OnEventShot(EventData)
missile.fuseDist=desc.fuseDist
missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass
missile.targetOrig=missile.targetName
missile.missileCoord=COORDINATE:New(0,0,0)
-- Set missile target name, unit and player.
self:GetMissileTarget(missile)
@ -1631,7 +1636,7 @@ end
--- Check if a coordinate lies within a safe training zone.
-- @param #FOX self
-- @param Core.Point#COORDINATE coord Coordinate to check.
-- @param Core.Point#COORDINATE coord Coordinate to check. Can also be a DCS#Vec3.
-- @return #boolean True if safe.
function FOX:_CheckCoordSafe(coord)
@ -1643,7 +1648,9 @@ function FOX:_CheckCoordSafe(coord)
-- Loop over all zones.
for _,_zone in pairs(self.safezones) do
local zone=_zone --Core.Zone#ZONE
local inzone=zone:IsCoordinateInZone(coord)
local Vec2={x=coord.x, y=coord.z}
local inzone=zone:IsVec2InZone(Vec2)
--local inzone=zone:IsCoordinateInZone(coord)
if inzone then
return true
end
@ -1654,7 +1661,7 @@ end
--- Check if a coordinate lies within a launch zone.
-- @param #FOX self
-- @param Core.Point#COORDINATE coord Coordinate to check.
-- @param Core.Point#COORDINATE coord Coordinate to check. Can also be a DCS#Vec2.
-- @return #boolean True if in launch zone.
function FOX:_CheckCoordLaunch(coord)
@ -1666,7 +1673,9 @@ function FOX:_CheckCoordLaunch(coord)
-- Loop over all zones.
for _,_zone in pairs(self.launchzones) do
local zone=_zone --Core.Zone#ZONE
local inzone=zone:IsCoordinateInZone(coord)
local Vec2={x=coord.x, y=coord.z}
local inzone=zone:IsVec2InZone(Vec2)
--local inzone=zone:IsCoordinateInZone(coord)
if inzone then
return true
end

View File

@ -56,7 +56,7 @@
-- @field #string ClassName Name of the Class.
-- @field #boolean Debug If true, debug info is sent as messages on the screen.
-- @field #boolean verbose Verbosity level. Higher means more output to DCS log file.
-- @field #string id String id of range for output in DCS log.
-- @field #string lid String id of range for output in DCS log.
-- @field #string rangename Name of the range.
-- @field Core.Point#COORDINATE location Coordinate of the range location.
-- @field #number rangeradius Radius of range defining its total size for e.g. smoking bomb impact points and sending radio messages. Default 5 km.
@ -86,7 +86,6 @@
-- @field #number illuminationmaxalt Maximum altitude in meters AGL at which illumination bombs are fired. Default is 1000 m.
-- @field #number scorebombdistance Distance from closest target up to which bomb hits are counted. Default 1000 m.
-- @field #number TdelaySmoke Time delay in seconds between impact of bomb and starting the smoke. Default 3 seconds.
-- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true.
-- @field #boolean trackbombs If true (default), all bomb types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean trackrockets If true (default), all rocket types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated.
@ -102,10 +101,10 @@
-- @field #boolean targetsheet If true, players can save their target sheets. Rangeboss will not work if targetsheets do not save.
-- @field #string targetpath Path where to save the target sheets.
-- @field #string targetprefix File prefix for target sheet files.
-- @field Sound.SRS#MSRS controlmsrs
-- @field Sound.SRS#MSRSQUEUE controlsrsQ
-- @field Sound.SRS#MSRS instructmsrs
-- @field Sound.SRS#MSRSQUEUE instructsrsQ
-- @field Sound.SRS#MSRS controlmsrs SRS wrapper for range controller.
-- @field Sound.SRS#MSRSQUEUE controlsrsQ SRS queue for range controller.
-- @field Sound.SRS#MSRS instructmsrs SRS wrapper for range instructor.
-- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor.
-- @extends Core.Fsm#FSM
--- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven
@ -232,8 +231,8 @@
--
-- ## Voice output via SRS
--
-- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}(). Range control and instructor frequencies and voices can then be
-- set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}()
-- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}().
-- Range control and instructor frequencies and voices can then be set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}().
--
-- # Persistence
--
@ -343,7 +342,6 @@ RANGE = {
illuminationmaxalt = 1000,
scorebombdistance = 1000,
TdelaySmoke = 3.0,
eventmoose = true,
trackbombs = true,
trackrockets = true,
trackmissiles = true,
@ -360,7 +358,18 @@ RANGE = {
}
--- Default range parameters.
-- @list Defaults
-- @type RANGE.Defaults
-- @param #number goodhitrange Radius for good hits in meters.
-- @param #number strafemaxalt Max altitude in meters for players to enter a strafing pit.
-- @param #number dtBombtrack Timer interval in seconds.
-- @param #number Tmsg Message display time in seconds.
-- @param #number ndisplayresults Number of results to display.
-- @param #number rangeradius Radius of range in meters.
-- @param #number TdelaySmoke Time delay in seconds before smoke is triggered.
-- @param #number boxlength Length of strafe pit box in meters.
-- @param #number boxwidth Width of strafe pit box in meters.
-- @param #number goodpass Number of hits for a good strafing pit pass.
-- @param #number foulline Distance of foul line in meters.
RANGE.Defaults = {
goodhitrange = 25,
strafemaxalt = 914,
@ -377,13 +386,15 @@ RANGE.Defaults = {
--- Target type, i.e. unit, static, or coordinate.
-- @type RANGE.TargetType
-- @field #string UNIT Target is a unit.
-- @field #string STATIC Target is a static.
-- @field #string UNIT Target is a unitobject.
-- @field #string STATIC Target is a static object.
-- @field #string COORD Target is a coordinate.
-- @field #string SCENERY Target is a scenery object.
RANGE.TargetType = {
UNIT = "Unit",
STATIC = "Static",
COORD = "Coordinate"
COORD = "Coordinate",
SCENERY = "Scenery"
}
--- Player settings.
@ -395,9 +406,11 @@ RANGE.TargetType = {
-- @field #boolean messages Display info messages.
-- @field Wrapper.Client#CLIENT client Client object of player.
-- @field #string unitname Name of player aircraft unit.
-- @field Wrapper.Unit#UNIT unit Player unit.
-- @field #string playername Name of player.
-- @field #string airframe Aircraft type name.
-- @field #boolean inzone If true, player is inside the range zone.
-- @field #boolean targeton Target on.
--- Bomb target data.
-- @type RANGE.BombTarget
@ -578,13 +591,14 @@ RANGE.MenuF10Root = nil
--- Range script version.
-- @field #string version
RANGE.version = "2.5.1"
RANGE.version = "2.7.0"
-- TODO list:
-- TODO: Verbosity level for messages.
-- TODO: Add option for default settings such as smoke off.
-- TODO: Add custom weapons, which can be specified by the user.
-- TODO: Check if units are still alive.
-- DONE: Scenery as targets.
-- DONE: Add statics for strafe pits.
-- DONE: Add missiles.
-- DONE: Convert env.info() to self:T()
@ -610,11 +624,11 @@ function RANGE:New( RangeName )
self.rangename = RangeName or "Practice Range"
-- Log id.
self.id = string.format( "RANGE %s | ", self.rangename )
self.lid = string.format( "RANGE %s | ", self.rangename )
-- Debug info.
local text = string.format( "Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename )
self:I( self.id .. text )
self:I( self.lid .. text )
-- Defaults
self:SetDefaultPlayerSmokeBomb()
@ -802,7 +816,7 @@ function RANGE:onafterStart()
if self.location == nil then
local text = string.format( "ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.", self.nstrafetargets, self.nbombtargets )
self:E( self.id .. text )
self:E( self.lid .. text )
return
end
@ -813,31 +827,20 @@ function RANGE:onafterStart()
-- Starting range.
local text = string.format( "Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets )
self:I( self.id .. text )
self:I( self.lid .. text )
-- Event handling.
if self.eventmoose then
-- Events are handled my MOOSE.
self:T( self.id .. "Events are handled by MOOSE." )
self:HandleEvent( EVENTS.Birth )
self:HandleEvent( EVENTS.Hit )
self:HandleEvent( EVENTS.Shot )
else
-- Events are handled directly by DCS.
self:T( self.id .. "Events are handled directly by DCS." )
world.addEventHandler( self )
end
-- Make bomb target move randomly within the range zone.
for _, _target in pairs( self.bombingTargets ) do
local target=_target --#RANGE.BombTarget
-- Check if it is a static object.
-- local _static=self:_CheckStatic(_target.target:GetName())
local _static = _target.type == RANGE.TargetType.STATIC
if _target.move and _static == false and _target.speed > 1 then
local unit = _target.target -- Wrapper.Unit#UNIT
_target.target:PatrolZones( { self.rangezone }, _target.speed * 0.75, "Off road" )
-- Check if unit and can move.
if target.move and target.type==RANGE.TargetType.UNIT and target.speed > 1 then
target.target:PatrolZones( { self.rangezone }, target.speed * 0.75, ENUMS.Formation.Vehicle.OffRoad )
end
end
@ -1064,7 +1067,7 @@ end
--- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke.
-- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red.
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Red`.
-- @return #RANGE self
function RANGE:SetBombTargetSmokeColor( colorid )
self.BombSmokeColor = colorid or SMOKECOLOR.Red
@ -1082,7 +1085,7 @@ end
--- Set smoke color for marking strafe targets. By default strafe targets are marked by green smoke.
-- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green.
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Green`.
-- @return #RANGE self
function RANGE:SetStrafeTargetSmokeColor( colorid )
self.StrafeSmokeColor = colorid or SMOKECOLOR.Green
@ -1091,7 +1094,7 @@ end
--- Set smoke color for marking strafe pit approach boxes. By default strafe pit boxes are marked by white smoke.
-- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White.
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.White`.
-- @return #RANGE self
function RANGE:SetStrafePitSmokeColor( colorid )
self.StrafePitSmokeColor = colorid or SMOKECOLOR.White
@ -1191,13 +1194,14 @@ end
-- @param #RANGE self
-- @param #string PathToSRS Path to SRS directory.
-- @param #number Port SRS port. Default 5002.
-- @param #number Coalition Coalition side, e.g. coalition.side.BLUE or coalition.side.RED
-- @param #number Frequency Frequency to use, defaults to 256 (same as rangecontrol)
-- @param #number Coalition Coalition side, e.g. `coalition.side.BLUE` or `coalition.side.RED`. Default `coalition.side.BLUE`.
-- @param #number Frequency Frequency to use. Default is 256 MHz for range control and 305 MHz for instructor. If given, both control and instructor get this frequency.
-- @param #number Modulation Modulation to use, defaults to radio.modulation.AM
-- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0
-- @param #string PathToGoogleKey Path to Google TTS credentials.
-- @return #RANGE self
function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey)
if PathToSRS then
self.useSRS=true
@ -1215,7 +1219,7 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then
self.instructmsrs:SetGoogle(PathToGoogleKey)
self.controlmsrs:SetGoogle(PathToGoogleKey)
self.instructmsrs:SetGoogle(PathToGoogleKey)
end
@ -1304,7 +1308,7 @@ end
-- @return #RANGE self
function RANGE:SetSoundfilesPath( path )
self.soundpath = tostring( path or "Range Soundfiles/" )
self:I( self.id .. string.format( "Setting sound files path to %s", self.soundpath ) )
self:I( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) )
return self
end
@ -1342,20 +1346,20 @@ function RANGE:AddStrafePit( targetnames, boxlength, boxwidth, heading, inverseh
if _isstatic == true then
-- Add static object.
self:T( self.id .. string.format( "Adding STATIC object %s as strafe target #%d.", _name, _i ) )
self:T( self.lid .. string.format( "Adding STATIC object %s as strafe target #%d.", _name, _i ) )
unit = STATIC:FindByName( _name, false )
elseif _isstatic == false then
-- Add unit object.
self:T( self.id .. string.format( "Adding UNIT object %s as strafe target #%d.", _name, _i ) )
self:T( self.lid .. string.format( "Adding UNIT object %s as strafe target #%d.", _name, _i ) )
unit = UNIT:FindByName( _name )
else
-- Neither unit nor static object with this name could be found.
local text = string.format( "ERROR! Could not find ANY strafe target object with name %s.", _name )
self:E( self.id .. text )
self:E( self.lid .. text )
end
@ -1374,7 +1378,7 @@ function RANGE:AddStrafePit( targetnames, boxlength, boxwidth, heading, inverseh
-- Check if at least one target could be found.
if ntargets == 0 then
local text = string.format( "ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s", self.rangename )
self:E( self.id .. text )
self:E( self.lid .. text )
return
end
@ -1443,7 +1447,7 @@ function RANGE:AddStrafePit( targetnames, boxlength, boxwidth, heading, inverseh
-- Debug info
local text = string.format( "Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline )
self:T( self.id .. text )
self:T( self.lid .. text )
return self
end
@ -1513,14 +1517,14 @@ function RANGE:AddBombingTargets( targetnames, goodhitrange, randommove )
if _isstatic == true then
local _static = STATIC:FindByName( name )
self:T2( self.id .. string.format( "Adding static bombing target %s with hit range %d.", name, goodhitrange, false ) )
self:T2( self.lid .. string.format( "Adding static bombing target %s with hit range %d.", name, goodhitrange, false ) )
self:AddBombingTargetUnit( _static, goodhitrange )
elseif _isstatic == false then
local _unit = UNIT:FindByName( name )
self:T2( self.id .. string.format( "Adding unit bombing target %s with hit range %d.", name, goodhitrange, randommove ) )
self:T2( self.lid .. string.format( "Adding unit bombing target %s with hit range %d.", name, goodhitrange, randommove ) )
self:AddBombingTargetUnit( _unit, goodhitrange, randommove )
else
self:E( self.id .. string.format( "ERROR! Could not find bombing target %s.", name ) )
self:E( self.lid .. string.format( "ERROR! Could not find bombing target %s.", name ) )
end
end
@ -1530,7 +1534,7 @@ end
--- Add a unit or static object as bombing target.
-- @param #RANGE self
-- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the strafe target.
-- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the bombing target.
-- @param #number goodhitrange Max distance from unit which is considered as a good hit.
-- @param #boolean randommove If true, unit will move randomly within the range. Default is false.
-- @return #RANGE self
@ -1553,11 +1557,11 @@ function RANGE:AddBombingTargetUnit( unit, goodhitrange, randommove )
-- Debug or error output.
if _isstatic == true then
self:I( self.id .. string.format( "Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
self:I( self.lid .. string.format( "Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
elseif _isstatic == false then
self:I( self.id .. string.format( "Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
self:I( self.lid .. string.format( "Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
else
self:E( self.id .. string.format( "ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!", name ) )
self:E( self.lid .. string.format( "ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!", name ) )
end
-- Get max speed of unit in km/h.
@ -1608,6 +1612,42 @@ function RANGE:AddBombingTargetCoordinate( coord, name, goodhitrange )
return self
end
--- Add a scenery object as bombing target.
-- @param #RANGE self
-- @param Wrapper.Scenery#SCENERY scenery Scenary object.
-- @param #number goodhitrange Max distance from unit which is considered as a good hit.
-- @return #RANGE self
function RANGE:AddBombingTargetScenery( scenery, goodhitrange)
-- Get name of positionable.
local name = scenery:GetName()
-- Default range is 25 m.
goodhitrange = goodhitrange or RANGE.Defaults.goodhitrange
-- Debug or error output.
if name then
self:I( self.lid .. string.format( "Adding SCENERY bombing target %s with good hit range %d", name, goodhitrange) )
else
self:E( self.lid .. string.format( "ERROR! No bombing target with name %s could be found!", name ) )
end
local target = {} -- #RANGE.BombTarget
target.name = name
target.target = scenery
target.goodhitrange = goodhitrange
target.move = false
target.speed = 0
target.coordinate = scenery:GetCoordinate()
target.type = RANGE.TargetType.SCENERY
-- Insert target to table.
table.insert( self.bombingTargets, target )
return self
end
--- Add all units of a group as bombing targets.
-- @param #RANGE self
-- @param Wrapper.Group#GROUP group Group of bombing targets.
@ -1650,7 +1690,7 @@ function RANGE:GetFoullineDistance( namepit, namefoulline )
elseif _staticpit == false then
pit = UNIT:FindByName( namepit )
else
self:E( self.id .. string.format( "ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namepit ) )
self:E( self.lid .. string.format( "ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namepit ) )
end
-- Get the unit or static foul line object.
@ -1660,7 +1700,7 @@ function RANGE:GetFoullineDistance( namepit, namefoulline )
elseif _staticfoul == false then
foul = UNIT:FindByName( namefoulline )
else
self:E( self.id .. string.format( "ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namefoulline ) )
self:E( self.lid .. string.format( "ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namefoulline ) )
end
-- Get the distance between the two objects.
@ -1668,10 +1708,10 @@ function RANGE:GetFoullineDistance( namepit, namefoulline )
if pit ~= nil and foul ~= nil then
fouldist = pit:GetCoordinate():Get2DDistance( foul:GetCoordinate() )
else
self:E( self.id .. string.format( "ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.", namepit, namefoulline ) )
self:E( self.lid .. string.format( "ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.", namepit, namefoulline ) )
end
self:T( self.id .. string.format( "Foul line distance = %.1f m.", fouldist ) )
self:T( self.lid .. string.format( "Foul line distance = %.1f m.", fouldist ) )
return fouldist
end
@ -1679,73 +1719,6 @@ end
-- Event Handling
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- General event handler.
-- @param #RANGE self
-- @param #table Event DCS event table.
function RANGE:onEvent( Event )
self:F3( Event )
if Event == nil or Event.initiator == nil then
self:T3( "Skipping onEvent. Event or Event.initiator unknown." )
return true
end
if Unit.getByName( Event.initiator:getName() ) == nil then
self:T3( "Skipping onEvent. Initiator unit name unknown." )
return true
end
local DCSiniunit = Event.initiator
local DCStgtunit = Event.target
local DCSweapon = Event.weapon
local EventData = {}
local _playerunit = nil
local _playername = nil
if Event.initiator then
EventData.IniUnitName = Event.initiator:getName()
EventData.IniDCSGroup = Event.initiator:getGroup()
EventData.IniGroupName = Event.initiator:getGroup():getName()
-- Get player unit and name. This returns nil,nil if the event was not fired by a player unit. And these are the only events we are interested in.
_playerunit, _playername = self:_GetPlayerUnitAndName( EventData.IniUnitName )
end
if Event.target then
EventData.TgtUnitName = Event.target:getName()
EventData.TgtUnit = UNIT:FindByName( EventData.TgtUnitName )
end
if Event.weapon then
EventData.Weapon = Event.weapon
EventData.weapon = Event.weapon
EventData.WeaponTypeName = Event.weapon:getTypeName()
end
-- Event info.
self:T3( self.id .. string.format( "EVENT: Event in onEvent with ID = %s", tostring( Event.id ) ) )
self:T3( self.id .. string.format( "EVENT: Ini unit = %s", tostring( EventData.IniUnitName ) ) )
self:T3( self.id .. string.format( "EVENT: Ini group = %s", tostring( EventData.IniGroupName ) ) )
self:T3( self.id .. string.format( "EVENT: Ini player = %s", tostring( _playername ) ) )
self:T3( self.id .. string.format( "EVENT: Tgt unit = %s", tostring( EventData.TgtUnitName ) ) )
self:T3( self.id .. string.format( "EVENT: Wpn type = %s", tostring( EventData.WeaponTypeName ) ) )
-- Call event Birth function.
if Event.id == world.event.S_EVENT_BIRTH and _playername then
self:OnEventBirth( EventData )
end
-- Call event Shot function.
if Event.id == world.event.S_EVENT_SHOT and _playername and Event.weapon then
self:OnEventShot( EventData )
end
-- Call event Hit function.
if Event.id == world.event.S_EVENT_HIT and _playername and DCStgtunit then
self:OnEventHit( EventData )
end
end
--- Range event handler for event birth.
-- @param #RANGE self
-- @param Core.Event#EVENTDATA EventData
@ -1755,9 +1728,9 @@ function RANGE:OnEventBirth( EventData )
local _unitName = EventData.IniUnitName
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
self:T3( self.id .. "BIRTH: unit = " .. tostring( EventData.IniUnitName ) )
self:T3( self.id .. "BIRTH: group = " .. tostring( EventData.IniGroupName ) )
self:T3( self.id .. "BIRTH: player = " .. tostring( _playername ) )
self:T3( self.lid .. "BIRTH: unit = " .. tostring( EventData.IniUnitName ) )
self:T3( self.lid .. "BIRTH: group = " .. tostring( EventData.IniGroupName ) )
self:T3( self.lid .. "BIRTH: player = " .. tostring( _playername ) )
if _unit and _playername then
@ -1768,7 +1741,7 @@ function RANGE:OnEventBirth( EventData )
-- Debug output.
local text = string.format( "Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)", _playername, _callsign, _unitName, _uid, _group:GetName(), _gid )
self:T( self.id .. text )
self:T( self.lid .. text )
-- Reset current strafe status.
self.strafeStatus[_uid] = nil
@ -1786,6 +1759,7 @@ function RANGE:OnEventBirth( EventData )
self.PlayerSettings[_playername].messages = true
self.PlayerSettings[_playername].client = CLIENT:FindByName( _unitName, nil, true )
self.PlayerSettings[_playername].unitname = _unitName
self.PlayerSettings[_playername].unit = _unit
self.PlayerSettings[_playername].playername = _playername
self.PlayerSettings[_playername].airframe = EventData.IniUnit:GetTypeName()
self.PlayerSettings[_playername].inzone = false
@ -1806,9 +1780,9 @@ function RANGE:OnEventHit( EventData )
self:F( { eventhit = EventData } )
-- Debug info.
self:T3( self.id .. "HIT: Ini unit = " .. tostring( EventData.IniUnitName ) )
self:T3( self.id .. "HIT: Ini group = " .. tostring( EventData.IniGroupName ) )
self:T3( self.id .. "HIT: Tgt target = " .. tostring( EventData.TgtUnitName ) )
self:T3( self.lid .. "HIT: Ini unit = " .. tostring( EventData.IniUnitName ) )
self:T3( self.lid .. "HIT: Ini group = " .. tostring( EventData.IniGroupName ) )
self:T3( self.lid .. "HIT: Tgt target = " .. tostring( EventData.TgtUnitName ) )
-- Player info
local _unitName = EventData.IniUnitName
@ -1861,7 +1835,7 @@ function RANGE:OnEventHit( EventData )
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2)
end
self:_DisplayMessageToGroup( _unit, text )
self:T2( self.id .. text )
self:T2( self.lid .. text )
_currentTarget.pastfoulline = true
end
end
@ -1894,115 +1868,14 @@ function RANGE:OnEventHit( EventData )
end
end
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
-- @param #RANGE self
-- @param #table weapon Weapon
function RANGE:_TrackWeapon(weapon)
end
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
-- @param #RANGE self
-- @param Core.Event#EVENTDATA EventData
function RANGE:OnEventShot( EventData )
self:F( { eventshot = EventData } )
-- Nil checks.
if EventData.Weapon == nil then
return
end
if EventData.IniDCSUnit == nil then
return
end
if EventData.IniPlayerName == nil then
return
end
-- Weapon data.
local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName
local _weaponStrArray = UTILS.Split( _weapon, "%." )
local _weaponName = _weaponStrArray[#_weaponStrArray]
-- Weapon descriptor.
local desc = EventData.Weapon:getDesc()
-- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X)
local weaponcategory = desc.category
-- Debug info.
self:T( self.id .. "EVENT SHOT: Range " .. self.rangename )
self:T( self.id .. "EVENT SHOT: Ini unit = " .. EventData.IniUnitName )
self:T( self.id .. "EVENT SHOT: Ini group = " .. EventData.IniGroupName )
self:T( self.id .. "EVENT SHOT: Weapon type = " .. _weapon )
self:T( self.id .. "EVENT SHOT: Weapon name = " .. _weaponName )
self:T( self.id .. "EVENT SHOT: Weapon cate = " .. weaponcategory )
-- Tracking conditions for bombs, rockets and missiles.
local _bombs = weaponcategory == Weapon.Category.BOMB -- string.match(_weapon, "weapons.bombs")
local _rockets = weaponcategory == Weapon.Category.ROCKET -- string.match(_weapon, "weapons.nurs")
local _missiles = weaponcategory == Weapon.Category.MISSILE -- string.match(_weapon, "weapons.missiles") or _viggen
-- Check if any condition applies here.
local _track = (_bombs and self.trackbombs) or (_rockets and self.trackrockets) or (_missiles and self.trackmissiles)
-- Get unit name.
local _unitName = EventData.IniUnitName
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
-- Attack parameters.
local attackHdg=_unit:GetHeading()
local attackAlt=_unit:GetHeight()
local attackVel=_unit:GetVelocityKNOTS()
-- Set this to larger value than the threshold.
local dPR = self.BombtrackThreshold * 2
-- Distance player to range.
if _unit and _playername then
dPR = _unit:GetCoordinate():Get2DDistance( self.location )
self:T( self.id .. string.format( "Range %s, player %s, player-range distance = %d km.", self.rangename, _playername, dPR / 1000 ) )
end
-- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons.
if _track and dPR <= self.BombtrackThreshold and _unit and _playername then
-- Player data.
local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData
-- Tracking info and init of last bomb position.
self:T( self.id .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName() ) )
-- Init bomb position.
local _lastBombPos = { x = 0, y = 0, z = 0 } -- DCS#Vec3
-- Function monitoring the position of a bomb until impact.
local function trackBomb( _ordnance )
-- When the pcall returns a failure the weapon has hit.
local _status, _bombPos = pcall( function()
return _ordnance:getPoint()
end )
self:T2( self.id .. string.format( "Range %s: Bomb still in air: %s", self.rangename, tostring( _status ) ) )
if _status then
----------------------------
-- Weapon is still in air --
----------------------------
-- Remember this position.
_lastBombPos = { x = _bombPos.x, y = _bombPos.y, z = _bombPos.z }
-- Check again in ~0.005 seconds ==> 200 checks per second.
return timer.getTime() + self.dtBombtrack
else
-----------------------------
-- Bomb did hit the ground --
-----------------------------
--- Function called on impact of a tracked weapon.
-- @param Wrapper.Weapon#WEAPON weapon The weapon object.
-- @param #RANGE self RANGE object.
-- @param #RANGE.PlayerData playerData Player data table.
-- @param #number attackHdg Attack heading.
-- @param #number attackAlt Attack altitude.
-- @param #number attackVel Attack velocity.
function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackVel)
-- Get closet target to last position.
local _closetTarget = nil -- #RANGE.BombTarget
@ -2011,18 +1884,18 @@ function RANGE:OnEventShot( EventData )
local _hitquality = "POOR"
-- Get callsign.
local _callsign = self:_myname( _unitName )
local _callsign = self:_myname( playerData.unitname )
local _playername=playerData.playername
local _unit=playerData.unit
-- Coordinate of impact point.
local impactcoord = COORDINATE:NewFromVec3( _lastBombPos )
local impactcoord = weapon:GetImpactCoordinate()
-- Check if impact happened in range zone.
local insidezone = self.rangezone:IsCoordinateInZone( impactcoord )
-- Impact point of bomb.
if self.Debug then
impactcoord:MarkToAll( "Bomb impact point" )
end
-- Smoke impact point of bomb.
if playerData.smokebombimpact and insidezone then
@ -2081,7 +1954,7 @@ function RANGE:OnEventShot( EventData )
result.name = _closetTarget.name or "unknown"
result.distance = _distance
result.radial = _closeCoord:HeadingTo( impactcoord )
result.weapon = _weaponName or "unknown"
result.weapon = weapon:GetTypeName() or "unknown"
result.quality = _hitquality
result.player = playerData.playername
result.time = timer.getAbsTime()
@ -2096,6 +1969,7 @@ function RANGE:OnEventShot( EventData )
result.attackHdg = attackHdg
result.attackVel = attackVel
result.attackAlt = attackAlt
result.date=os and os.date() or "n/a"
-- Add to table.
table.insert( _results, result )
@ -2124,22 +1998,65 @@ function RANGE:OnEventShot( EventData )
end
else
self:T( self.id .. "Weapon impacted outside range zone." )
self:T( self.lid .. "Weapon impacted outside range zone." )
end
-- Terminate the timer
self:T( self.id .. string.format( "Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername ) )
return nil
end
end -- _status check
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
-- @param #RANGE self
-- @param Core.Event#EVENTDATA EventData
function RANGE:OnEventShot( EventData )
self:F( { eventshot = EventData } )
end -- end function trackBomb
-- Nil checks.
if EventData.Weapon == nil or EventData.IniDCSUnit == nil or EventData.IniPlayerName == nil then
return
end
-- Weapon is not yet "alife" just yet. Start timer in one second.
self:T( self.id .. string.format( "Range %s, player %s: Tracking of weapon starts in 0.1 seconds.", self.rangename, _playername ) )
timer.scheduleFunction( trackBomb, EventData.weapon, timer.getTime() + 0.1 )
-- Create weapon object.
local weapon=WEAPON:New(EventData.weapon)
end -- if _track (string.match) and player-range distance < threshold.
-- Check if any condition applies here.
local _track = (weapon:IsBomb() and self.trackbombs) or (weapon:IsRocket() and self.trackrockets) or (weapon:IsMissile() and self.trackmissiles)
-- Get unit name.
local _unitName = EventData.IniUnitName
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
-- Distance Player-to-Range. Set this to larger value than the threshold.
local dPR = self.BombtrackThreshold * 2
-- Distance player to range.
if _unit and _playername then
dPR = _unit:GetCoordinate():Get2DDistance( self.location )
self:T( self.lid .. string.format( "Range %s, player %s, player-range distance = %d km.", self.rangename, _playername, dPR / 1000 ) )
end
-- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons.
if _track and dPR <= self.BombtrackThreshold and _unit and _playername then
-- Player data.
local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData
-- Attack parameters.
local attackHdg=_unit:GetHeading()
local attackAlt=_unit:GetHeight()
local attackVel=_unit:GetVelocityKNOTS()
-- Tracking info and init of last bomb position.
self:T( self.lid .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, weapon:GetTypeName(), weapon:GetName()))
-- Set callback function on impact.
weapon:SetFuncImpact(RANGE._OnImpact, self, playerData, attackHdg, attackAlt, attackVel)
-- Weapon is not yet "alife" just yet. Start timer in 0.1 seconds.
self:T( self.lid .. string.format( "Range %s, player %s: Tracking of weapon starts in 0.1 seconds.", self.rangename, _playername ) )
weapon:StartTrack(0.1)
end
end
@ -2183,7 +2100,7 @@ function RANGE:onafterStatus( From, Event, To )
end
-- Check range status.
self:I( self.id .. text )
self:I( self.lid .. text )
end
@ -2205,22 +2122,31 @@ function RANGE:onafterEnterRange( From, Event, To, player )
if self.instructor and self.rangecontrol then
if self.useSRS then
local text = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz", self.rangecontrolfreq)
local ttstext = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.", self.rangecontrolfreq)
local group = player.client:GetGroup()
self.instructsrsQ:NewTransmission(ttstext,nil,self.instructmsrs,nil,1,{group},text,10)
self.instructsrsQ:NewTransmission(ttstext, nil, self.instructmsrs, nil, 1, {group}, text, 10)
else
-- Range control radio frequency split.
local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." )
-- Radio message that player entered the range
-- You entered the bombing range. For hit assessment, contact the range controller at xy MHz
self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath )
self.instructor:Number2Transmission( RF[1] )
if tonumber( RF[2] ) > 0 then
self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath )
self.instructor:Number2Transmission( RF[2] )
end
self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath )
end
end
@ -2238,9 +2164,24 @@ function RANGE:onafterExitRange( From, Event, To, player )
if self.instructor then
-- You left the bombing range zone. Have a nice day!
if self.useSRS then
local text = "You left the bombing range zone. Have a nice day!"
local group = player.client:GetGroup()
self.instructsrsQ:NewTransmission(text,nil,self.instructmsrs,nil,1,{group},text,10)
local text = "You left the bombing range zone. "
local r=math.random(2)
if r==1 then
text=text.."Have a nice day!"
elseif r==2 then
text=text.."Take care and bye bye!"
elseif r==3 then
text=text.."Talk to you soon!"
elseif r==4 then
text=text.."See you in two weeks!"
elseif r==5 then
text=text.."!"
end
self.instructsrsQ:NewTransmission(text, nil, self.instructmsrs, nil, 1, {player.client:GetGroup()}, text, 10)
else
self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath )
end
@ -2304,7 +2245,7 @@ function RANGE:onafterImpact( From, Event, To, result, player )
-- Send message.
self:_DisplayMessageToGroup( unit, text, nil, true )
self:T( self.id .. text )
self:T( self.lid .. text )
end
-- Save results.
@ -2343,7 +2284,7 @@ function RANGE:onbeforeSave( From, Event, To )
if io and lfs then
return true
else
self:E( self.id .. string.format( "WARNING: io and/or lfs not desanitized. Cannot save player results." ) )
self:E( self.lid .. string.format( "WARNING: io and/or lfs not desanitized. Cannot save player results." ) )
return false
end
end
@ -2360,9 +2301,9 @@ function RANGE:onafterSave( From, Event, To )
if f then
f:write( data )
f:close()
self:I( self.id .. string.format( "Saving player results to file %s", tostring( filename ) ) )
self:I( self.lid .. string.format( "Saving player results to file %s", tostring( filename ) ) )
else
self:E( self.id .. string.format( "ERROR: Could not save results to file %s", tostring( filename ) ) )
self:E( self.lid .. string.format( "ERROR: Could not save results to file %s", tostring( filename ) ) )
end
end
@ -2388,10 +2329,7 @@ function RANGE:onafterSave( From, Event, To )
local quality = result.quality
local time = UTILS.SecondsToClock(result.time, true)
local airframe = result.airframe
local date = "n/a"
if os then
date = os.date()
end
local date = result.date or "n/a"
scores = scores .. string.format( "\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s,%s", playername, i, target, distance, radial, quality, weapon, airframe, time, date )
end
end
@ -2408,7 +2346,7 @@ function RANGE:onbeforeLoad( From, Event, To )
if io and lfs then
return true
else
self:E( self.id .. string.format( "WARNING: io and/or lfs not desanitized. Cannot load player results." ) )
self:E( self.lid .. string.format( "WARNING: io and/or lfs not desanitized. Cannot load player results." ) )
return false
end
end
@ -2424,12 +2362,12 @@ function RANGE:onafterLoad( From, Event, To )
local function _loadfile( filename )
local f = io.open( filename, "rb" )
if f then
-- self:I(self.id..string.format("Loading player results from file %s", tostring(filename)))
-- self:I(self.lid..string.format("Loading player results from file %s", tostring(filename)))
local data = f:read( "*all" )
f:close()
return data
else
self:E( self.id .. string.format( "WARNING: Could not load player results from file %s. File might not exist just yet.", tostring( filename ) ) )
self:E( self.lid .. string.format( "WARNING: Could not load player results from file %s. File might not exist just yet.", tostring( filename ) ) )
return nil
end
end
@ -2442,7 +2380,7 @@ function RANGE:onafterLoad( From, Event, To )
-- Info message.
local text = string.format( "Loading player bomb results from file %s", filename )
self:I( self.id .. text )
self:I( self.lid .. text )
-- Load asset data from file.
local data = _loadfile( filename )
@ -2909,7 +2847,7 @@ function RANGE:_DisplayRangeInfo( _unitname )
self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer )
-- Debug output.
self:T2( self.id .. text )
self:T2( self.lid .. text )
end
end
end
@ -3056,9 +2994,9 @@ function RANGE:_DisplayRangeWeather( _unitname )
self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer )
-- Debug output.
self:T2( self.id .. text )
self:T2( self.lid .. text )
else
self:T( self.id .. string.format( "ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname ) )
self:T( self.lid .. string.format( "ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname ) )
end
end
@ -3453,10 +3391,10 @@ function RANGE:_AddF10Commands( _unitName )
local _StrPits = MENU_GROUP_COMMAND:New( group, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName ):Refresh()
end
else
self:E( self.id .. "Could not find group or group ID in AddF10Menu() function. Unit name: " .. _unitName or "N/A")
self:E( self.lid .. "Could not find group or group ID in AddF10Menu() function. Unit name: " .. _unitName or "N/A")
end
else
self:E( self.id .. "Player unit does not exist in AddF10Menu() function. Unit name: " .. _unitName or "N/A")
self:E( self.lid .. "Player unit does not exist in AddF10Menu() function. Unit name: " .. _unitName or "N/A")
end
end
@ -3496,8 +3434,13 @@ function RANGE:_GetBombTargetCoordinate( target )
-- Coordinates dont move.
coord = target.coordinate
elseif target.type == RANGE.TargetType.SCENERY then
-- Coordinates dont move.
coord = target.coordinate
else
self:E( self.id .. "ERROR: Unknown target type." )
self:E( self.lid .. "ERROR: Unknown target type." )
end
return coord
@ -3525,7 +3468,7 @@ function RANGE:_GetAmmo( unitname )
if ammotable ~= nil then
local weapons = #ammotable
self:T2( self.id .. string.format( "Number of weapons %d.", weapons ) )
self:T2( self.lid .. string.format( "Number of weapons %d.", weapons ) )
for w = 1, weapons do
@ -3539,10 +3482,10 @@ function RANGE:_GetAmmo( unitname )
ammo = ammo + Nammo
local text = string.format( "Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo )
self:T( self.id .. text )
self:T( self.lid .. text )
else
local text = string.format( "Player %s has %d ammo of type %s", playername, Nammo, Tammo )
self:T( self.id .. text )
self:T( self.lid .. text )
end
end
end
@ -4006,20 +3949,20 @@ function RANGE:_CheckStatic( name )
-- If static is not yet in MOOSE DB, we add it. Can happen for cargo statics!
if not _MOOSEstatic then
self:T( self.id .. string.format( "Adding DCS static to MOOSE database. Name = %s.", name ) )
self:T( self.lid .. string.format( "Adding DCS static to MOOSE database. Name = %s.", name ) )
_DATABASE:AddStatic( name )
end
return true
else
self:T3( self.id .. string.format( "No static object with name %s exists.", name ) )
self:T3( self.lid .. string.format( "No static object with name %s exists.", name ) )
end
-- Check if a unit has this name.
if UNIT:FindByName( name ) then
return false
else
self:T3( self.id .. string.format( "No unit object with name %s exists.", name ) )
self:T3( self.lid .. string.format( "No unit object with name %s exists.", name ) )
end
-- If not unit or static exist, we return nil.

View File

@ -44,6 +44,7 @@ SOCKET = {
}
--- Data type. This is the keyword the socket listener uses.
-- @type SOCKET.DataType
-- @field #string TEXT Plain text.
-- @field #string BOMBRESULT Range bombing.
-- @field #string STRAFERESULT Range strafeing result.

View File

@ -1222,6 +1222,20 @@ function UTILS.HdgDiff(h1, h2)
return math.abs(delta)
end
--- Returns the heading from one vec3 to another vec3.
-- @param DCS#Vec3 a From vec3.
-- @param DCS#Vec3 b To vec3.
-- @return #number Heading in degrees.
function UTILS.HdgTo(a, b)
local dz=b.z-a.z
local dx=b.x-a.x
local heading=math.deg(math.atan2(dz, dx))
if heading < 0 then
heading = 360 + heading
end
return heading
end
--- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.

View File

@ -1768,6 +1768,7 @@ do -- Cargo
["tt_DSHK"] = 6,
["HL_KORD"] = 6,
["HL_DSHK"] = 6,
["CCKW_353"] = 16, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers
}
-- Assuming that each passenger weighs 95 kg on average.

View File

@ -4,7 +4,7 @@
--
-- ### Author: **FlightControl**
--
-- ### Contributions: **Applevangelist**
-- ### Contributions: **Applevangelist**, **funkyfranky**
--
-- ===
--
@ -12,12 +12,12 @@
-- @image Wrapper_Scenery.JPG
--- @type SCENERY
-- @field #string ClassName
-- @field #string SceneryName
-- @field #DCS.Object SceneryObject
-- @field #number Life0
--- SCENERY Class
-- @type SCENERY
-- @field #string ClassName Name of the class.
-- @field #string SceneryName Name of the scenery object.
-- @field DCS#Object SceneryObject DCS scenery object.
-- @field #number Life0 Initial life points.
-- @extends Wrapper.Positionable#POSITIONABLE
@ -37,12 +37,16 @@ SCENERY = {
--- Register scenery object as POSITIONABLE.
--@param #SCENERY self
--@param #string SceneryName Scenery name.
--@param #DCS.Object SceneryObject DCS scenery object.
--@param DCS#Object SceneryObject DCS scenery object.
--@return #SCENERY Scenery object.
function SCENERY:Register( SceneryName, SceneryObject )
local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) )
self.SceneryName = SceneryName
self.SceneryObject = SceneryObject
if self.SceneryObject then
self.Life0 = self.SceneryObject:getLife()
else
@ -53,7 +57,7 @@ end
--- Obtain DCS Object from the SCENERY Object.
--@param #SCENERY self
--@return #DCS.Object DCS scenery object.
--@return DCS#Object DCS scenery object.
function SCENERY:GetDCSObject()
return self.SceneryObject
end
@ -69,7 +73,7 @@ function SCENERY:GetLife()
return life
end
--- Get current initial life points from the SCENERY Object.
--- Get initial life points of the SCENERY Object.
--@param #SCENERY self
--@return #number life
function SCENERY:GetLife0()
@ -90,7 +94,7 @@ function SCENERY:IsDead()
return self:GetLife() < 1 and true or false
end
--- Get the threat level of a SCENERY object. Always 0.
--- Get the threat level of a SCENERY object. Always 0 as scenery does not pose a threat to anyone.
--@param #SCENERY self
--@return #number Threat level 0.
--@return #string "Scenery".

View File

@ -167,7 +167,7 @@ end
--- Get the DCS unit object.
-- @param #UNIT self
-- @return DCS#Unit
-- @return DCS#Unit The DCS unit object.
function UNIT:GetDCSObject()
local DCSUnit = Unit.getByName( self.UnitName )
@ -325,14 +325,19 @@ function UNIT:IsAlive()
local DCSUnit = self:GetDCSObject() -- DCS#Unit
if DCSUnit then
local UnitIsAlive = DCSUnit:isExist() and DCSUnit:isActive()
local UnitIsAlive = DCSUnit:isExist() and DCSUnit:isActive() -- and DCSUnit:getLife() > 1
return UnitIsAlive
end
return nil
end
--- Returns if the Unit is dead.
-- @param #UNIT self
-- @return #boolean `true` if Unit is dead, else false or nil if the unit does not exist
function UNIT:IsDead()
return not self:IsAlive()
end
--- Returns the Unit's callsign - the localized string.
-- @param #UNIT self
@ -626,7 +631,7 @@ function UNIT:IsFuelSupply()
return false
end
--- Returns the unit's group if it exist and nil otherwise.
--- Returns the unit's group if it exists and nil otherwise.
-- @param Wrapper.Unit#UNIT self
-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist.
function UNIT:GetGroup()