Merge pull request #1077 from FlightControl-Master/FF/Develop

New classes, fixes and improvements.
This commit is contained in:
Frank 2018-12-26 04:50:56 +01:00 committed by GitHub
commit ab38efd69e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 14490 additions and 1894 deletions

View File

@ -36,6 +36,7 @@
-- @field #boolean ReportTargets If true, nearby targets are reported. -- @field #boolean ReportTargets If true, nearby targets are reported.
-- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup. -- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup.
-- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup. -- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup.
-- @field #number dtFollow Time step between position updates.
--- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader. --- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader.
@ -106,6 +107,7 @@ AI_FORMATION = {
FollowScheduler = nil, FollowScheduler = nil,
OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE,
OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
dtFollow = 0.5,
} }
--- AI_FORMATION.Mode class --- AI_FORMATION.Mode class
@ -125,6 +127,7 @@ AI_FORMATION = {
-- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet. -- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet.
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
-- @param #string FollowName Name of the escort. -- @param #string FollowName Name of the escort.
-- @param #string FollowBriefing Briefing.
-- @return #AI_FORMATION self -- @return #AI_FORMATION self
function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1 function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1
local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) ) local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) )
@ -139,7 +142,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
self:AddTransition( "*", "Stop", "Stopped" ) self:AddTransition( "*", "Stop", "Stopped" )
self:AddTransition( "None", "Start", "Following" ) self:AddTransition( {"None", "Stopped"}, "Start", "Following" )
self:AddTransition( "*", "FormationLine", "*" ) self:AddTransition( "*", "FormationLine", "*" )
--- FormationLine Handler OnBefore for AI_FORMATION --- FormationLine Handler OnBefore for AI_FORMATION
@ -620,6 +623,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
return self return self
end end
--- Set time interval between updates of the formation.
-- @param #AI_FORMATION self
-- @param #number dt Time step in seconds between formation updates. Default is every 0.5 seconds.
-- @return #AI_FORMATION
function AI_FORMATION:SetFollowTimeInterval(dt) --R2.1
self.dtFollow=dt or 0.5
return self
end
--- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to.
-- This allows to visualize where the escort is flying to. -- This allows to visualize where the escort is flying to.
-- @param #AI_FORMATION self -- @param #AI_FORMATION self
@ -893,7 +906,30 @@ function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1
end end
--- @param Follow#AI_FORMATION self --- Stop function. Formation will not be updated any more.
-- @param #AI_FORMATION self
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
-- @param #string From From state.
-- @param #string Event Event.
-- @pram #string To The to state.
function AI_FORMATION:onafterStop(FollowGroupSet, From, Event, To) --R2.1
self:E("Stopping formation.")
end
--- Follow event fuction. Check if coming from state "stopped". If so the transition is rejected.
-- @param #AI_FORMATION self
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
-- @param #string From From state.
-- @param #string Event Event.
-- @pram #string To The to state.
function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1
if From=="Stopped" then
return false -- Deny transition.
end
return true
end
--- @param #AI_FORMATION self
function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
self:F( ) self:F( )
@ -1033,7 +1069,7 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
self, ClientUnit, CT1, CV1, CT2, CV2 self, ClientUnit, CT1, CV1, CT2, CV2
) )
self:__Follow( -0.5 ) self:__Follow( -self.dtFollow )
end end
end end

View File

@ -342,18 +342,18 @@ do -- COORDINATE
return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z
end end
--- Returns if the 2 coordinates are at the same 2D position. --- Scan/find objects (units, statics, scenery) within a certain radius around the coordinate using the world.searchObjects() DCS API function.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param #number radius (Optional) Scan radius in meters. Default 100 m. -- @param #number radius (Optional) Scan radius in meters. Default 100 m.
-- @param #boolean scanunits (Optional) If true scan for units. Default true. -- @param #boolean scanunits (Optional) If true scan for units. Default true.
-- @param #boolean scanstatics (Optional) If true scan for static objects. Default true. -- @param #boolean scanstatics (Optional) If true scan for static objects. Default true.
-- @param #boolean scanscenery (Optional) If true scan for scenery objects. Default false. -- @param #boolean scanscenery (Optional) If true scan for scenery objects. Default false.
-- @return True if units were found. -- @return #boolean True if units were found.
-- @return True if statics were found. -- @return #boolean True if statics were found.
-- @return True if scenery objects were found. -- @return #boolean True if scenery objects were found.
-- @return Unit objects found. -- @return #table Table of MOOSE @[#Wrapper.Unit#UNIT} objects found.
-- @return Static objects found. -- @return #table Table of DCS static objects found.
-- @return Scenery objects found. -- @return #table Table of DCS scenery objects found.
function COORDINATE:ScanObjects(radius, scanunits, scanstatics, scanscenery) function COORDINATE:ScanObjects(radius, scanunits, scanstatics, scanscenery)
self:F(string.format("Scanning in radius %.1f m.", radius)) self:F(string.format("Scanning in radius %.1f m.", radius))
@ -405,18 +405,17 @@ do -- COORDINATE
local ObjectCategory = ZoneObject:getCategory() local ObjectCategory = ZoneObject:getCategory()
-- Check for unit or static objects -- Check for unit or static objects
--if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive()) then if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist() then
if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist()) then
table.insert(Units, UNIT:Find(ZoneObject)) table.insert(Units, UNIT:Find(ZoneObject))
gotunits=true gotunits=true
elseif (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then
table.insert(Statics, ZoneObject) table.insert(Statics, ZoneObject)
gotstatics=true gotstatics=true
elseif ObjectCategory == Object.Category.SCENERY then elseif ObjectCategory==Object.Category.SCENERY then
table.insert(Scenery, ZoneObject) table.insert(Scenery, ZoneObject)
gotscenery=true gotscenery=true
@ -460,16 +459,45 @@ do -- COORDINATE
--- Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE. --- Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param DCS#Distance Distance The Distance to be added in meters. -- @param DCS#Distance Distance The Distance to be added in meters.
-- @param DCS#Angle Angle The Angle in degrees. -- @param DCS#Angle Angle The Angle in degrees. Defaults to 0 if not specified (nil).
-- @return #COORDINATE The new calculated COORDINATE. -- @param #boolean Keepalt If true, keep altitude of original coordinate. Default is that the new coordinate is created at the translated land height.
function COORDINATE:Translate( Distance, Angle ) -- @return Core.Point#COORDINATE The new calculated COORDINATE.
function COORDINATE:Translate( Distance, Angle, Keepalt )
local SX = self.x local SX = self.x
local SY = self.z local SY = self.z
local Radians = Angle / 180 * math.pi local Radians = (Angle or 0) / 180 * math.pi
local TX = Distance * math.cos( Radians ) + SX local TX = Distance * math.cos( Radians ) + SX
local TY = Distance * math.sin( Radians ) + SY local TY = Distance * math.sin( Radians ) + SY
return COORDINATE:NewFromVec2( { x = TX, y = TY } ) if Keepalt then
return COORDINATE:NewFromVec3( { x = TX, y=self.y, z = TY } )
else
return COORDINATE:NewFromVec2( { x = TX, y = TY } )
end
end
--- Rotate coordinate in 2D (x,z) space.
-- @param #COORDINATE self
-- @param DCS#Angle Angle Angle of rotation in degrees.
-- @return Core.Point#COORDINATE The rotated coordinate.
function COORDINATE:Rotate2D(Angle)
if not Angle then
return self
end
local phi=math.rad(Angle)
local X=self.z
local Y=self.x
--slocal R=math.sqrt(X*X+Y*Y)
local x=X*math.cos(phi)-Y*math.sin(phi)
local y=X*math.sin(phi)+Y*math.cos(phi)
-- Coordinate assignment looks bit strange but is correct.
return COORDINATE:NewFromVec3({x=y, y=self.y, z=x})
end end
--- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. --- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE.
@ -1003,11 +1031,15 @@ do -- COORDINATE
function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description ) function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description )
self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) self:F2( { AltType, Type, Action, Speed, SpeedLocked } )
-- Defaults -- Set alttype or "RADIO" which is AGL.
AltType=AltType or "RADIO" AltType=AltType or "RADIO"
-- Speedlocked by default
if SpeedLocked==nil then if SpeedLocked==nil then
SpeedLocked=true SpeedLocked=true
end end
-- Speed or default 500 km/h.
Speed=Speed or 500 Speed=Speed or 500
-- Waypoint array. -- Waypoint array.
@ -1016,19 +1048,26 @@ do -- COORDINATE
-- Coordinates. -- Coordinates.
RoutePoint.x = self.x RoutePoint.x = self.x
RoutePoint.y = self.z RoutePoint.y = self.z
-- Altitude. -- Altitude.
RoutePoint.alt = self.y RoutePoint.alt = self.y
RoutePoint.alt_type = AltType RoutePoint.alt_type = AltType
-- Waypoint type. -- Waypoint type.
RoutePoint.type = Type or nil RoutePoint.type = Type or nil
RoutePoint.action = Action or nil RoutePoint.action = Action or nil
-- Set speed/ETA.
-- Speed.
RoutePoint.speed = Speed/3.6 RoutePoint.speed = Speed/3.6
RoutePoint.speed_locked = SpeedLocked RoutePoint.speed_locked = SpeedLocked
-- ETA.
RoutePoint.ETA=nil RoutePoint.ETA=nil
RoutePoint.ETA_locked = false RoutePoint.ETA_locked = false
-- Waypoint description. -- Waypoint description.
RoutePoint.name=description RoutePoint.name=description
-- Airbase parameters for takeoff and landing points. -- Airbase parameters for takeoff and landing points.
if airbase then if airbase then
local AirbaseID = airbase:GetID() local AirbaseID = airbase:GetID()
@ -1041,27 +1080,20 @@ do -- COORDINATE
else else
self:T("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!") self:T("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!")
end end
--self:MarkToAll(string.format("Landing waypoint at airbase %s", airbase:GetName()))
end end
-- ["task"] =
-- {
-- ["id"] = "ComboTask",
-- ["params"] =
-- {
-- ["tasks"] =
-- {
-- }, -- end of ["tasks"]
-- }, -- end of ["params"]
-- }, -- end of ["task"]
-- Waypoint tasks. -- Waypoint tasks.
RoutePoint.task = {} RoutePoint.task = {}
RoutePoint.task.id = "ComboTask" RoutePoint.task.id = "ComboTask"
RoutePoint.task.params = {} RoutePoint.task.params = {}
RoutePoint.task.params.tasks = DCSTasks or {} RoutePoint.task.params.tasks = DCSTasks or {}
-- Debug.
self:T({RoutePoint=RoutePoint}) self:T({RoutePoint=RoutePoint})
-- Return waypoint.
return RoutePoint return RoutePoint
end end
@ -1121,6 +1153,9 @@ do -- COORDINATE
--- Build a Waypoint Air "Landing". --- Build a Waypoint Air "Landing".
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param DCS#Speed Speed Airspeed in km/h. -- @param DCS#Speed Speed Airspeed in km/h.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase for takeoff and landing points.
-- @param #table DCSTasks A table of @{DCS#Task} items which are executed at the waypoint.
-- @param #string description A text description of the waypoint, which will be shown on the F10 map.
-- @return #table The route point. -- @return #table The route point.
-- @usage -- @usage
-- --
@ -1129,8 +1164,8 @@ do -- COORDINATE
-- LandingWaypoint = LandingCoord:WaypointAirLanding( 60 ) -- LandingWaypoint = LandingCoord:WaypointAirLanding( 60 )
-- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second. -- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second.
-- --
function COORDINATE:WaypointAirLanding( Speed ) function COORDINATE:WaypointAirLanding( Speed, airbase, DCSTasks, description )
return self:WaypointAir( nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed ) return self:WaypointAir(nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, nil, airbase, DCSTasks, description)
end end

View File

@ -9,12 +9,12 @@
-- --
-- The Radio contains 2 classes : RADIO and BEACON -- The Radio contains 2 classes : RADIO and BEACON
-- --
-- What are radio communications in DCS ? -- What are radio communications in DCS?
-- --
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM), -- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**. -- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
-- --
-- How to supply DCS my own Sound Files ? -- How to supply DCS my own Sound Files?
-- --
-- * Your sound files need to be encoded in **.ogg** or .wav, -- * Your sound files need to be encoded in **.ogg** or .wav,
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings, -- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
@ -23,7 +23,7 @@
-- --
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE} -- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
-- --
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically, -- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped. -- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
-- --
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft, -- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
@ -33,7 +33,7 @@
-- --
-- === -- ===
-- --
-- ### Author: Hugues "Grey_Echo" Bousquet -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
-- --
-- @module Core.Radio -- @module Core.Radio
-- @image Core_Radio.JPG -- @image Core_Radio.JPG
@ -66,24 +66,25 @@
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts -- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call -- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
-- --
-- What is this power thing ? -- What is this power thing?
-- --
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna, -- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit, -- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
-- * If the player gets **too far** from the transmiter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**, -- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
-- * This an automated DCS calculation you have no say on, -- * This an automated DCS calculation you have no say on,
-- * For reference, a standard VOR station has a 100W antenna, a standard AA TACAN has a 120W antenna, and civilian ATC's antenna usually range between 300 and 500W, -- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission. -- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
-- --
-- @type RADIO -- @type RADIO
-- @field Positionable#POSITIONABLE Positionable The transmiter -- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
-- @field #string FileName Name of the sound file -- @field #string FileName Name of the sound file played.
-- @field #number Frequency Frequency of the transmission in Hz -- @field #number Frequency Frequency of the transmission in Hz.
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM) -- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM).
-- @field #string Subtitle Subtitle of the transmission -- @field #string Subtitle Subtitle of the transmission.
-- @field #number SubtitleDuration Duration of the Subtitle in seconds -- @field #number SubtitleDuration Duration of the Subtitle in seconds.
-- @field #number Power Power of the antenna is Watts -- @field #number Power Power of the antenna is Watts.
-- @field #boolean Loop (default true) -- @field #boolean Loop Transmission is repeated (default true).
-- @field #string alias Name of the radio transmitter.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
RADIO = { RADIO = {
ClassName = "RADIO", ClassName = "RADIO",
@ -93,19 +94,19 @@ RADIO = {
Subtitle = "", Subtitle = "",
SubtitleDuration = 0, SubtitleDuration = 0,
Power = 100, Power = 100,
Loop = true, Loop = false,
alias=nil,
} }
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast --- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead -- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead.
-- @param #RADIO self -- @param #RADIO self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. -- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #RADIO Radio -- @return #RADIO The RADIO object or #nil if Positionable is invalid.
-- @return #nil If Positionable is invalid
function RADIO:New(Positionable) function RADIO:New(Positionable)
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
self.Loop = true -- default Loop to true (not sure the above RADIO definition actually is working) -- Inherit base
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
self:F(Positionable) self:F(Positionable)
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
@ -113,11 +114,27 @@ function RADIO:New(Positionable)
return self return self
end end
self:E({"The passed positionable is invalid, no RADIO created", Positionable}) self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
return nil return nil
end end
--- Check validity of the filename passed and sets RADIO.FileName --- Set alias of the transmitter.
-- @param #RADIO self
-- @param #string alias Name of the radio transmitter.
-- @return #RADIO self
function RADIO:SetAlias(alias)
self.alias=tostring(alias)
return self
end
--- Get alias of the transmitter.
-- @param #RADIO self
-- @return #string Name of the transmitter.
function RADIO:GetAlias()
return tostring(self.alias)
end
--- Set the file name for the radio transmission.
-- @param #RADIO self -- @param #RADIO self
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg") -- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
-- @return #RADIO self -- @return #RADIO self
@ -125,49 +142,63 @@ function RADIO:SetFileName(FileName)
self:F2(FileName) self:F2(FileName)
if type(FileName) == "string" then if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName FileName = "l10n/DEFAULT/" .. FileName
end end
self.FileName = FileName self.FileName = FileName
return self return self
end end
end end
self:E({"File name invalid. Maybe something wrong with the extension ?", self.FileName}) self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
return self return self
end end
--- Check validity of the frequency passed and sets RADIO.Frequency --- Set the frequency for the radio transmission.
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
-- @param #RADIO self -- @param #RADIO self
-- @param #number Frequency in MHz (Ranges allowed for radio transmissions in DCS : 30-88 / 108-152 / 225-400MHz) -- @param #number Frequency Frequency in MHz. Ranges allowed for radio transmissions in DCS : 30-88 / 108-152 / 225-400MHz.
-- @return #RADIO self -- @return #RADIO self
function RADIO:SetFrequency(Frequency) function RADIO:SetFrequency(Frequency)
self:F2(Frequency) self:F2(Frequency)
if type(Frequency) == "number" then if type(Frequency) == "number" then
-- If frequency is in range -- If frequency is in range
if (Frequency >= 30 and Frequency < 88) or (Frequency >= 108 and Frequency < 152) or (Frequency >= 225 and Frequency < 400) then if (Frequency >= 30 and Frequency < 88) or (Frequency >= 108 and Frequency < 152) or (Frequency >= 225 and Frequency < 400) then
self.Frequency = Frequency * 1000000 -- Conversion in Hz
-- Convert frequency from MHz to Hz
self.Frequency = Frequency * 1000000
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency -- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
self.Positionable:SetCommand({
local commandSetFrequency={
id = "SetFrequency", id = "SetFrequency",
params = { params = {
frequency = self.Frequency, frequency = self.Frequency,
modulation = self.Modulation, modulation = self.Modulation,
} }
}) }
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end end
return self return self
end end
end end
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", self.Frequency})
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", Frequency})
return self return self
end end
--- Check validity of the frequency passed and sets RADIO.Modulation --- Set AM or FM modulation of the radio transmitter.
-- @param #RADIO self -- @param #RADIO self
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM -- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
-- @return #RADIO self -- @return #RADIO self
function RADIO:SetModulation(Modulation) function RADIO:SetModulation(Modulation)
self:F2(Modulation) self:F2(Modulation)
@ -183,23 +214,24 @@ end
--- Check validity of the power passed and sets RADIO.Power --- Check validity of the power passed and sets RADIO.Power
-- @param #RADIO self -- @param #RADIO self
-- @param #number Power in W -- @param #number Power Power in W.
-- @return #RADIO self -- @return #RADIO self
function RADIO:SetPower(Power) function RADIO:SetPower(Power)
self:F2(Power) self:F2(Power)
if type(Power) == "number" then if type(Power) == "number" then
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
return self else
self:E({"Power is invalid. Power unchanged.", self.Power})
end end
self:E({"Power is invalid. Power unchanged.", self.Power})
return self return self
end end
--- Check validity of the loop passed and sets RADIO.Loop --- Set message looping on or off.
-- @param #RADIO self -- @param #RADIO self
-- @param #boolean Loop -- @param #boolean Loop If true, message is repeated indefinitely.
-- @return #RADIO self -- @return #RADIO self
-- @usage
function RADIO:SetLoop(Loop) function RADIO:SetLoop(Loop)
self:F2(Loop) self:F2(Loop)
if type(Loop) == "boolean" then if type(Loop) == "boolean" then
@ -232,13 +264,12 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle}) self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
end end
if type(SubtitleDuration) == "number" then if type(SubtitleDuration) == "number" then
if math.floor(math.abs(SubtitleDuration)) == SubtitleDuration then self.SubtitleDuration = SubtitleDuration
self.SubtitleDuration = SubtitleDuration else
return self self.SubtitleDuration = 0
end self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end end
self.SubtitleDuration = 0 return self
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end end
--- Create a new transmission, that is to say, populate the RADIO with relevant data --- Create a new transmission, that is to say, populate the RADIO with relevant data
@ -246,10 +277,10 @@ end
-- but it will work with a UNIT or a GROUP anyway. -- but it will work with a UNIT or a GROUP anyway.
-- Only the #RADIO and the Filename are mandatory -- Only the #RADIO and the Filename are mandatory
-- @param #RADIO self -- @param #RADIO self
-- @param #string FileName -- @param #string FileName Name of the sound file that will be transmitted.
-- @param #number Frequency in MHz -- @param #number Frequency Frequency in MHz.
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM -- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
-- @param #number Power in W -- @param #number Power Power in W.
-- @return #RADIO self -- @return #RADIO self
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop) function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
self:F({FileName, Frequency, Modulation, Power}) self:F({FileName, Frequency, Modulation, Power})
@ -269,31 +300,43 @@ end
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}. -- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory. -- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self -- @param #RADIO self
-- @param #string FileName -- @param #string FileName Name of sound file.
-- @param #string Subtitle -- @param #string Subtitle Subtitle to be displayed with sound file.
-- @param #number SubtitleDuration in s -- @param #number SubtitleDuration Duration of subtitle display in seconds.
-- @param #number Frequency in MHz -- @param #number Frequency Frequency in MHz.
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM -- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
-- @param #boolean Loop -- @param #boolean Loop If true, loop message.
-- @return #RADIO self -- @return #RADIO self
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop) function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop}) self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
-- Set file name.
self:SetFileName(FileName) self:SetFileName(FileName)
local Duration = 5
if SubtitleDuration then Duration = SubtitleDuration end -- Set modulation AM/FM.
-- SubtitleDuration argument was missing, adding it if Modulation then
if Subtitle then self:SetSubtitle(Subtitle, Duration) end self:SetModulation(Modulation)
-- self:SetSubtitleDuration is non existent, removing faulty line end
-- if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration) end
if Frequency then self:SetFrequency(Frequency) end -- Set frequency.
if Modulation then self:SetModulation(Modulation) end if Frequency then
if Loop then self:SetLoop(Loop) end self:SetFrequency(Frequency)
end
-- Set subtitle.
if Subtitle then
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
end
-- Set Looping.
if Loop then
self:SetLoop(Loop)
end
return self return self
end end
--- Actually Broadcast the transmission --- Broadcast the transmission.
-- * The Radio has to be populated with the new transmission before broadcasting. -- * The Radio has to be populated with the new transmission before broadcasting.
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission} -- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE -- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
@ -302,31 +345,38 @@ end
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored. -- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored -- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
-- @param #RADIO self -- @param #RADIO self
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
-- @return #RADIO self -- @return #RADIO self
function RADIO:Broadcast() function RADIO:Broadcast(viatrigger)
self:F() self:F({viatrigger=viatrigger})
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system -- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
self:T2("Broadcasting from a UNIT or a GROUP") self:T("Broadcasting from a UNIT or a GROUP")
self.Positionable:SetCommand({
local commandTransmitMessage={
id = "TransmitMessage", id = "TransmitMessage",
params = { params = {
file = self.FileName, file = self.FileName,
duration = self.SubtitleDuration, duration = self.SubtitleDuration,
subtitle = self.Subtitle, subtitle = self.Subtitle,
loop = self.Loop, loop = self.Loop,
} }}
})
self:T3(commandTransmitMessage)
self.Positionable:SetCommand(commandTransmitMessage)
else else
-- If the POSITIONABLE is anything else, we revert to the general singleton function -- If the POSITIONABLE is anything else, we revert to the general singleton function
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID -- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
self:T2("Broadcasting from a POSITIONABLE") self:T("Broadcasting from a POSITIONABLE")
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID)) trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
end end
return self return self
end end
--- Stops a transmission --- Stops a transmission
-- This function is especially usefull to stop the broadcast of looped transmissions -- This function is especially usefull to stop the broadcast of looped transmissions
-- @param #RADIO self -- @param #RADIO self
@ -335,10 +385,10 @@ function RADIO:StopBroadcast()
self:F() self:F()
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command -- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
self.Positionable:SetCommand({
id = "StopTransmission", local commandStopTransmission={id="StopTransmission", params={}}
params = {}
}) self.Positionable:SetCommand(commandStopTransmission)
else else
-- Else, we use the appropriate singleton funciton -- Else, we use the appropriate singleton funciton
trigger.action.stopRadioTransmission(tostring(self.ID)) trigger.action.stopRadioTransmission(tostring(self.ID))
@ -364,22 +414,86 @@ end
-- Use @{#BEACON:StopRadioBeacon}() to stop it. -- Use @{#BEACON:StopRadioBeacon}() to stop it.
-- --
-- @type BEACON -- @type BEACON
-- @field #string ClassName Name of the class "BEACON".
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
BEACON = { BEACON = {
ClassName = "BEACON", ClassName = "BEACON",
Positionable = nil,
} }
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.AATACAN} or @{#BEACON.Generic} --- Beacon types supported by DCS.
-- @type BEACON.Type
-- @field #number NULL
-- @field #number VOR
-- @field #number DME
-- @field #number VOR_DME
-- @field #number TACAN
-- @field #number VORTAC
-- @field #number RSBN
-- @field #number BROADCAST_STATION
-- @field #number HOMER
-- @field #number AIRPORT_HOMER
-- @field #number AIRPORT_HOMER_WITH_MARKER
-- @field #number ILS_FAR_HOMER
-- @field #number ILS_NEAR_HOMER
-- @field #number ILS_LOCALIZER
-- @field #number ILS_GLIDESLOPE
-- @field #number NAUTICAL_HOMER
-- @field #number ICLS
BEACON.Type={
NULL = 0,
VOR = 1,
DME = 2,
VOR_DME = 3,
TACAN = 4,
VORTAC = 5,
RSBN = 32,
BROADCAST_STATION = 1024,
HOMER = 8,
AIRPORT_HOMER = 4104,
AIRPORT_HOMER_WITH_MARKER = 4136,
ILS_FAR_HOMER = 16408,
ILS_NEAR_HOMER = 16456,
ILS_LOCALIZER = 16640,
ILS_GLIDESLOPE = 16896,
NAUTICAL_HOMER = 32776,
ICLS = 131584,
}
--- Beacon systems supported by DCS.
-- @type BEACON.System
-- @field #number PAR_10
-- @field #number RSBN_5
-- @field #number TACAN
-- @field #number TACAN_TANKER
-- @field #number ILS_LOCALIZER
-- @field #number ILS_GLIDESLOPE
-- @field #number BROADCAST_STATION
BEACON.System={
PAR_10 = 1,
RSBN_5 = 2,
TACAN = 3,
TACAN_TANKER = 4,
ILS_LOCALIZER = 5,
ILS_GLIDESLOPE = 6,
BROADCAST_STATION = 7,
}
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. -- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
-- @param #BEACON self -- @param #BEACON self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. -- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #BEACON Beacon -- @return #BEACON Beacon object or #nil if the positionable is invalid.
-- @return #nil If Positionable is invalid
function BEACON:New(Positionable) function BEACON:New(Positionable)
local self = BASE:Inherit(self, BASE:New())
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#BEACON
-- Debug.
self:F(Positionable) self:F(Positionable)
-- Set positionable.
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable self.Positionable = Positionable
return self return self
@ -390,44 +504,95 @@ function BEACON:New(Positionable)
end end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz --- Activates a TACAN BEACON.
-- @param #BEACON self -- @param #BEACON self
-- @param #number TACANChannel -- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
-- @param #string TACANMode -- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
-- @return #number Frequecy -- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @return #nil if parameters are invalid -- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
function BEACON:_TACANToFrequency(TACANChannel, TACANMode) -- @param #number Duration How long will the beacon last in seconds. Omit for forever.
self:F3({TACANChannel, TACANMode}) -- @return #BEACON self
-- @usage
-- -- Let's create a TACAN Beacon for a tanker
-- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
--
-- myBeacon:TACAN(20, "Y", "TEXACO", true) -- Activate the beacon
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
if type(TACANChannel) ~= "number" then -- Get frequency.
if TACANMode ~= "X" and TACANMode ~= "Y" then local Frequency=UTILS.TACANToFrequency(Channel, Mode)
return nil -- error in arguments
end -- Check.
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. -- Beacon type.
-- I have no idea what it does but it seems to work local Type=BEACON.Type.TACAN
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then -- Beacon system.
B = 1 local System=BEACON.System.TACAN
end
if TACANMode == 'Y' then -- Check if unit is an aircraft and set system accordingly.
A = 1025 local AA=self.Positionable:IsAir()
if TACANChannel < 64 then if AA then
A = 1088 System=BEACON.System.TACAN_TANKER
end -- Check if "Y" mode is selected for aircraft.
else -- 'X' if Mode~="Y" then
if TACANChannel < 64 then self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
A = 962
end end
end end
return (A + TACANChannel - B) * 1000000 -- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug.
self:T({"TACAN BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
-- Stop sheduler.
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end end
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
-- @param #BEACON self
-- @param #number Channel ICLS channel.
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
function BEACON:ActivateICLS(Channel, Callsign, Duration)
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug
self:T2({"ICLS BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
-- Stop sheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates a TACAN BEACON on an Aircraft. --- Activates a TACAN BEACON on an Aircraft.
-- @param #BEACON self -- @param #BEACON self
@ -480,7 +645,7 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
}) })
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil, SCHEDULER:New(nil,
function() function()
self:StopAATACAN() self:StopAATACAN()
end, {}, BeaconDuration) end, {}, BeaconDuration)
@ -591,4 +756,44 @@ function BEACON:StopRadioBeacon()
self:F() self:F()
-- The unique name of the transmission is the class ID -- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID)) trigger.action.stopRadioTransmission(tostring(self.ID))
return self
end end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
-- @param #BEACON self
-- @param #number TACANChannel
-- @param #string TACANMode
-- @return #number Frequecy
-- @return #nil if parameters are invalid
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
self:F3({TACANChannel, TACANMode})
if type(TACANChannel) ~= "number" then
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end

View File

@ -409,9 +409,9 @@ do -- SET_BASE
for ObjectID, ObjectData in pairs( self.Set ) do for ObjectID, ObjectData in pairs( self.Set ) do
if NearestObject == nil then if NearestObject == nil then
NearestObject = ObjectData NearestObject = ObjectData
ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetVec2() ) ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() )
else else
local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetVec2() ) local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() )
if Distance < ClosestDistance then if Distance < ClosestDistance then
NearestObject = ObjectData NearestObject = ObjectData
ClosestDistance = Distance ClosestDistance = Distance

View File

@ -2637,7 +2637,9 @@ function SPAWN:_OnEngineShutDown( EventData )
if Landed and self.RepeatOnEngineShutDown then if Landed and self.RepeatOnEngineShutDown then
local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } )
self:ReSpawn( SpawnGroupIndex ) --self:ReSpawn( SpawnGroupIndex )
-- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076
SCHEDULER:New(self, self.ReSpawn, {SpawnGroupIndex}, 3)
end end
end end
end end

View File

@ -195,6 +195,49 @@ function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
end end
--- Creates a new @{Static} from a COORDINATE.
-- @param #SPAWNSTATIC self
-- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static.
-- @param #number Heading (Optional) Heading The heading of the static, which is a number in degrees from 0 to 360. Default is 0 degrees.
-- @param #string NewName (Optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName) --R2.4
self:F( { PointVec2, Heading, NewName } )
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
Heading=Heading or 0
local StaticUnitTemplate = StaticTemplate.units[1]
StaticUnitTemplate.x = Coordinate.x
StaticUnitTemplate.y = Coordinate.z
StaticUnitTemplate.alt = Coordinate.y
StaticTemplate.route = nil
StaticTemplate.groupId = nil
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticUnitTemplate.name = StaticTemplate.name
StaticUnitTemplate.heading = ( Heading / 180 ) * math.pi
_DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID)
self:F({StaticTemplate = StaticTemplate})
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
self.SpawnIndex = self.SpawnIndex + 1
return _DATABASE:FindStatic(Static:getName())
end
return nil
end
--- Respawns the original @{Static}. --- Respawns the original @{Static}.
-- @param #SPAWNSTATIC self -- @param #SPAWNSTATIC self
-- @return #SPAWNSTATIC -- @return #SPAWNSTATIC

View File

@ -70,7 +70,7 @@ do -- UserFlag
-- local BlueVictory = USERFLAG:New( "VictoryBlue" ) -- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value. -- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value.
-- --
function USERFLAG:Get( Number ) --R2.3 function USERFLAG:Get() --R2.3
return trigger.misc.getUserFlag( self.UserFlagName ) return trigger.misc.getUserFlag( self.UserFlagName )
end end

View File

@ -118,15 +118,21 @@ do -- UserSound
--- Play the usersound to the given @{Wrapper.Group}. --- Play the usersound to the given @{Wrapper.Group}.
-- @param #USERSOUND self -- @param #USERSOUND self
-- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} to play the usersound to. -- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} to play the usersound to.
-- @param #number Delay (Optional) Delay in seconds, before the sound is played. Default 0.
-- @return #USERSOUND The usersound instance. -- @return #USERSOUND The usersound instance.
-- @usage -- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player. -- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player.
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group. -- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group.
-- --
function USERSOUND:ToGroup( Group ) --R2.3 function USERSOUND:ToGroup( Group, Delay ) --R2.3
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName ) Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil, USERSOUND.ToGroup,{self, Group}, Delay)
else
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName )
end
return self return self
end end

View File

@ -1398,16 +1398,15 @@ end
--- Smokes the zone boundaries in a color. --- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
-- @return #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:SmokeZone( SmokeColor ) function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments )
self:F2( SmokeColor ) self:F2( SmokeColor )
local i Segments=Segments or 10
local j
local Segments = 10
i = 1 local i=1
j = #self._.Polygon local j=#self._.Polygon
while i <= #self._.Polygon do while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
@ -1428,6 +1427,42 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor )
end end
--- Flare the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color.
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
-- @param DCS#Azimuth Azimuth (optional) Azimuth The azimuth of the flare.
-- @param #number AddHeight (optional) The height to be added for the smoke.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight )
self:F2(FlareColor)
Segments=Segments or 10
AddHeight = AddHeight or 0
local i=1
local j=#self._.Polygon
while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line.
local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments )
POINT_VEC2:New( PointX, PointY, AddHeight ):Flare(FlareColor, Azimuth)
end
j = i
i = i + 1
end
return self
end
--- Returns if a location is within the zone. --- Returns if a location is within the zone.

View File

@ -216,7 +216,7 @@
-- One way to determin which types of ammo the unit carries, one can use the debug mode of the arty class via @{#ARTY.SetDebugON}(). -- One way to determin which types of ammo the unit carries, one can use the debug mode of the arty class via @{#ARTY.SetDebugON}().
-- In debug mode, the all ammo types of the group are printed to the monitor as message and can be found in the DCS.log file. -- In debug mode, the all ammo types of the group are printed to the monitor as message and can be found in the DCS.log file.
-- --
-- ## Empoying Selected Weapons -- ## Employing Selected Weapons
-- --
-- If an ARTY group carries multiple weapons, which can be used for artillery task, a certain weapon type can be selected to attack the target. -- If an ARTY group carries multiple weapons, which can be used for artillery task, a certain weapon type can be selected to attack the target.
-- This is done via the *weapontype* parameter of the @{#ARTY.AssignTargetCoord}(..., *weapontype*, ...) function. -- This is done via the *weapontype* parameter of the @{#ARTY.AssignTargetCoord}(..., *weapontype*, ...) function.
@ -674,11 +674,13 @@ ARTY.id="ARTY | "
--- Arty script version. --- Arty script version.
-- @field #string version -- @field #string version
ARTY.version="1.0.6" ARTY.version="1.0.7"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list: -- TODO list:
-- TODO: Add hit event and make the arty group relocate.
-- TODO: Handle rearming for ships. How?
-- DONE: Delete targets from queue user function. -- DONE: Delete targets from queue user function.
-- DONE: Delete entire target queue user function. -- DONE: Delete entire target queue user function.
-- DONE: Add weapon types. Done but needs improvements. -- DONE: Add weapon types. Done but needs improvements.
@ -697,11 +699,9 @@ ARTY.version="1.0.6"
-- DONE: Add command move to make arty group move. -- DONE: Add command move to make arty group move.
-- DONE: remove schedulers for status event. -- DONE: remove schedulers for status event.
-- DONE: Improve handling of special weapons. When winchester if using selected weapons? -- DONE: Improve handling of special weapons. When winchester if using selected weapons?
-- TODO: Handle rearming for ships. How?
-- DONE: Make coordinate after rearming general, i.e. also work after the group has moved to anonther location. -- DONE: Make coordinate after rearming general, i.e. also work after the group has moved to anonther location.
-- DONE: Add set commands via markers. E.g. set rearming place. -- DONE: Add set commands via markers. E.g. set rearming place.
-- DONE: Test stationary types like mortas ==> rearming etc. -- DONE: Test stationary types like mortas ==> rearming etc.
-- TODO: Add hit event and make the arty group relocate.
-- DONE: Add illumination and smoke. -- DONE: Add illumination and smoke.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -2878,7 +2878,7 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target)
self.Controllable:ClearTasks() self.Controllable:ClearTasks()
else else
self:E(ARTY.id.."ERROR: No target in cease fire for group %s.", self.groupname) self:E(ARTY.id..string.format("ERROR: No target in cease fire for group %s.", self.groupname))
end end
-- Set number of shots to zero. -- Set number of shots to zero.
@ -4253,101 +4253,116 @@ end
-- @param #ARTY self -- @param #ARTY self
function ARTY:_CheckTargetsInRange() function ARTY:_CheckTargetsInRange()
local targets2delete={}
for i=1,#self.targets do for i=1,#self.targets do
local _target=self.targets[i] local _target=self.targets[i]
self:T3(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange))) self:T3(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
-- Check if target is in range. -- Check if target is in range.
local _inrange,_toofar,_tooclose=self:_TargetInRange(_target) local _inrange,_toofar,_tooclose,_remove=self:_TargetInRange(_target)
self:T3(ARTY.id..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s", _target.name, tostring(_target.inrange), tostring(_toofar), tostring(_tooclose))) self:T3(ARTY.id..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s", _target.name, tostring(_target.inrange), tostring(_toofar), tostring(_tooclose)))
-- Init default for assigning moves into range. if _remove then
local _movetowards=false
local _moveaway=false
if _target.inrange==nil then -- The ARTY group is immobile and not cargo but the target is not in range!
table.insert(targets2delete, _target.name)
-- First time the check is performed. We call the function again and send a message. else
_target.inrange,_toofar,_tooclose=self:_TargetInRange(_target, self.report or self.Debug)
-- Init default for assigning moves into range.
local _movetowards=false
local _moveaway=false
if _target.inrange==nil then
-- First time the check is performed. We call the function again and send a message.
_target.inrange,_toofar,_tooclose=self:_TargetInRange(_target, self.report or self.Debug)
-- Send group towards/away from target.
if _toofar then
_movetowards=true
elseif _tooclose then
_moveaway=true
end
elseif _target.inrange==true then
-- Target was in range at previous check...
if _toofar then --...but is now too far away.
_movetowards=true
elseif _tooclose then --...but is now too close.
_moveaway=true
end
elseif _target.inrange==false then
-- Target was out of range at previous check.
if _inrange then
-- Inform coalition that target is now in range.
local text=string.format("%s, target %s is now in range.", self.alias, _target.name)
self:T(ARTY.id..text)
MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
end
-- Send group towards/away from target.
if _toofar then
_movetowards=true
elseif _tooclose then
_moveaway=true
end end
elseif _target.inrange==true then -- Assign a relocation command so that the unit will be in range of the requested target.
if self.autorelocate and (_movetowards or _moveaway) then
-- Target was in range at previous check... -- Get current position.
local _from=self.Controllable:GetCoordinate()
local _dist=_from:Get2DDistance(_target.coord)
if _toofar then --...but is now too far away. if _dist<=self.autorelocatemaxdist then
_movetowards=true
elseif _tooclose then --...but is now too close.
_moveaway=true
end
elseif _target.inrange==false then local _tocoord --Core.Point#COORDINATE
local _name=""
local _safetymargin=500
-- Target was out of range at previous check. if _movetowards then
if _inrange then -- Target was in range on previous check but now we are too far away.
-- Inform coalition that target is now in range. local _waytogo=_dist-self.maxrange+_safetymargin
local text=string.format("%s, target %s is now in range.", self.alias, _target.name) local _heading=self:_GetHeading(_from,_target.coord)
self:T(ARTY.id..text) _tocoord=_from:Translate(_waytogo, _heading)
MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) _name=string.format("%s, relocation to within max firing range of target %s", self.alias, _target.name)
end
end elseif _moveaway then
-- Assign a relocation command so that the unit will be in range of the requested target. -- Target was in range on previous check but now we are too far away.
if self.autorelocate and (_movetowards or _moveaway) then local _waytogo=_dist-self.minrange+_safetymargin
local _heading=self:_GetHeading(_target.coord,_from)
_tocoord=_from:Translate(_waytogo, _heading)
_name=string.format("%s, relocation to within min firing range of target %s", self.alias, _target.name)
-- Get current position. end
local _from=self.Controllable:GetCoordinate()
local _dist=_from:Get2DDistance(_target.coord)
if _dist<=self.autorelocatemaxdist then -- Send info message.
MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
local _tocoord --Core.Point#COORDINATE -- Assign relocation move.
local _name="" self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true)
local _safetymargin=500
if _movetowards then
-- Target was in range on previous check but now we are too far away.
local _waytogo=_dist-self.maxrange+_safetymargin
local _heading=self:_GetHeading(_from,_target.coord)
_tocoord=_from:Translate(_waytogo, _heading)
_name=string.format("%s, relocation to within max firing range of target %s", self.alias, _target.name)
elseif _moveaway then
-- Target was in range on previous check but now we are too far away.
local _waytogo=_dist-self.minrange+_safetymargin
local _heading=self:_GetHeading(_target.coord,_from)
_tocoord=_from:Translate(_waytogo, _heading)
_name=string.format("%s, relocation to within min firing range of target %s", self.alias, _target.name)
end end
-- Send info message.
MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug)
-- Assign relocation move.
self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true)
end end
-- Update value.
_target.inrange=_inrange
self:T3(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
end end
-- Update value.
_target.inrange=_inrange
self:T3(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
end end
-- Remove targets not in range.
for _,targetname in pairs(targets2delete) do
self:RemoveTarget(targetname)
end
end end
--- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times. --- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times.
@ -4728,6 +4743,7 @@ end
-- @return #boolean True if target is in range, false otherwise. -- @return #boolean True if target is in range, false otherwise.
-- @return #boolean True if ARTY group is too far away from the target, i.e. distance > max firing range. -- @return #boolean True if ARTY group is too far away from the target, i.e. distance > max firing range.
-- @return #boolean True if ARTY group is too close to the target, i.e. distance < min finring range. -- @return #boolean True if ARTY group is too close to the target, i.e. distance < min finring range.
-- @return #boolean True if target should be removed since ARTY group is immobile and not cargo.
function ARTY:_TargetInRange(target, message) function ARTY:_TargetInRange(target, message)
self:F3(target) self:F3(target)
@ -4763,11 +4779,13 @@ function ARTY:_TargetInRange(target, message)
end end
-- Remove target if ARTY group cannot move, e.g. Mortas. No chance to be ever in range - unless they are cargo. -- Remove target if ARTY group cannot move, e.g. Mortas. No chance to be ever in range - unless they are cargo.
local _remove=false
if not (self.ismobile or self.iscargo) and _inrange==false then if not (self.ismobile or self.iscargo) and _inrange==false then
self:RemoveTarget(target.name) --self:RemoveTarget(target.name)
_remove=true
end end
return _inrange,_toofar,_tooclose return _inrange,_toofar,_tooclose,_remove
end end
--- Get the weapon type name, which should be used to attack the target. --- Get the weapon type name, which should be used to attack the target.

View File

@ -1012,7 +1012,7 @@ do -- DETECTION_BASE
--- Set the parameters to calculate to optimal intercept point. --- Set the parameters to calculate to optimal intercept point.
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #boolean Intercept Intercept is true if an intercept point is calculated. Intercept is false if it is disabled. The default Intercept is false. -- @param #boolean Intercept Intercept is true if an intercept point is calculated. Intercept is false if it is disabled. The default Intercept is false.
-- @param #number IntereptDelay If Intercept is true, then InterceptDelay is the average time it takes to get airplanes airborne. -- @param #number InterceptDelay If Intercept is true, then InterceptDelay is the average time it takes to get airplanes airborne.
-- @return #DETECTION_BASE self -- @return #DETECTION_BASE self
function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay ) function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay )
self:F2() self:F2()

View File

@ -5435,7 +5435,7 @@ function RAT:_ATCInit(airports_map)
if not RAT.ATC.init then if not RAT.ATC.init then
local text local text
text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay
self:T(RAT.id..text) BASE:T(RAT.id..text)
RAT.ATC.init=true RAT.ATC.init=true
for _,ap in pairs(airports_map) do for _,ap in pairs(airports_map) do
local name=ap:GetName() local name=ap:GetName()
@ -5458,7 +5458,7 @@ end
-- @param #string name Group name of the flight. -- @param #string name Group name of the flight.
-- @param #string dest Name of the destination airport. -- @param #string dest Name of the destination airport.
function RAT:_ATCAddFlight(name, dest) function RAT:_ATCAddFlight(name, dest)
self:T(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest)) BASE:T(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest))
RAT.ATC.flight[name]={} RAT.ATC.flight[name]={}
RAT.ATC.flight[name].destination=dest RAT.ATC.flight[name].destination=dest
RAT.ATC.flight[name].Tarrive=-1 RAT.ATC.flight[name].Tarrive=-1
@ -5483,7 +5483,7 @@ end
-- @param #string name Group name of the flight. -- @param #string name Group name of the flight.
-- @param #number time Time the fight first registered. -- @param #number time Time the fight first registered.
function RAT:_ATCRegisterFlight(name, time) function RAT:_ATCRegisterFlight(name, time)
self:T(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.") BASE:T(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.")
RAT.ATC.flight[name].Tarrive=time RAT.ATC.flight[name].Tarrive=time
RAT.ATC.flight[name].holding=0 RAT.ATC.flight[name].holding=0
end end
@ -5514,7 +5514,7 @@ function RAT:_ATCStatus()
-- Aircraft is holding. -- Aircraft is holding.
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy) local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy)
self:T(RAT.id..text) BASE:T(RAT.id..text)
elseif hold==RAT.ATC.onfinal then elseif hold==RAT.ATC.onfinal then
@ -5522,7 +5522,7 @@ function RAT:_ATCStatus()
local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60) local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60)
self:T(RAT.id..text) BASE:T(RAT.id..text)
elseif hold==RAT.ATC.unregistered then elseif hold==RAT.ATC.unregistered then
@ -5530,7 +5530,7 @@ function RAT:_ATCStatus()
--self:T(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold)) --self:T(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold))
else else
self:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().")
end end
end end
@ -5572,12 +5572,12 @@ function RAT:_ATCCheck()
-- Debug message. -- Debug message.
local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", name, flight,qID, nqueue, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60) local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", name, flight,qID, nqueue, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60)
self:T(RAT.id..text) BASE:T(RAT.id..text)
else else
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", name, flight, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60) local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", name, flight, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60)
self:T(RAT.id..text) BASE:T(RAT.id..text)
-- Clear flight for landing. -- Clear flight for landing.
RAT:_ATCClearForLanding(name, flight) RAT:_ATCClearForLanding(name, flight)
@ -5706,11 +5706,6 @@ function RAT:_ATCQueue()
table.insert(RAT.ATC.airport[airport].queue, v[1]) table.insert(RAT.ATC.airport[airport].queue, v[1])
end end
--fvh
--for k,v in ipairs(RAT.ATC.airport[airport].queue) do
--print(string.format("queue #%02i flight \"%s\" holding %d seconds",k, v, RAT.ATC.flight[v].holding))
--end
end end
end end

View File

@ -276,7 +276,7 @@ RANGE.id="RANGE | "
--- Range script version. --- Range script version.
-- @field #string version -- @field #string version
RANGE.version="1.2.1" RANGE.version="1.2.3"
--TODO list: --TODO list:
--TODO: Add custom weapons, which can be specified by the user. --TODO: Add custom weapons, which can be specified by the user.
@ -460,9 +460,10 @@ function RANGE:SetBombtrackThreshold(distance)
self.BombtrackThreshold=distance*1000 or 25*1000 self.BombtrackThreshold=distance*1000 or 25*1000
end end
--- Set range location. If this is not done, one (random) unit position of the range is used to determine the center of the range. --- Set range location. If this is not done, one (random) unit position of the range is used to determine the location of the range.
-- The range location determines the position at which the weather data is evaluated.
-- @param #RANGE self -- @param #RANGE self
-- @param Core.Point#COORDINATE coordinate Coordinate of the center of the range. -- @param Core.Point#COORDINATE coordinate Coordinate of the range.
function RANGE:SetRangeLocation(coordinate) function RANGE:SetRangeLocation(coordinate)
self.location=coordinate self.location=coordinate
end end
@ -471,7 +472,7 @@ end
-- If a zone is not explicitly specified, the range zone is determined by its location and radius. -- If a zone is not explicitly specified, the range zone is determined by its location and radius.
-- @param #RANGE self -- @param #RANGE self
-- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters. -- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters.
function RANGE:SetRangeLocation(zone) function RANGE:SetRangeZone(zone)
self.rangezone=zone self.rangezone=zone
end end
@ -1163,11 +1164,19 @@ function RANGE:OnEventShot(EventData)
-- Coordinate of impact point. -- Coordinate of impact point.
local impactcoord=COORDINATE:NewFromVec3(_lastBombPos) local impactcoord=COORDINATE:NewFromVec3(_lastBombPos)
-- Check if impact happend in range zone.
local insidezone=self.rangezone:IsCoordinateInZone(impactcoord)
-- Distance from range. We dont want to smoke targets outside of the range. -- Distance from range. We dont want to smoke targets outside of the range.
local impactdist=impactcoord:Get2DDistance(self.location) local impactdist=impactcoord:Get2DDistance(self.location)
-- Impact point of bomb.
if self.Debug then
impactcoord:MarkToAll("Bomb impact point")
end
-- Smoke impact point of bomb. -- Smoke impact point of bomb.
if self.PlayerSettings[_playername].smokebombimpact and impactdist<self.rangeradius then if self.PlayerSettings[_playername].smokebombimpact and insidezone then
if self.PlayerSettings[_playername].delaysmoke then if self.PlayerSettings[_playername].delaysmoke then
timer.scheduleFunction(self._DelayedSmoke, {coord=impactcoord, color=self.PlayerSettings[_playername].smokecolor}, timer.getTime() + self.TdelaySmoke) timer.scheduleFunction(self._DelayedSmoke, {coord=impactcoord, color=self.PlayerSettings[_playername].smokecolor}, timer.getTime() + self.TdelaySmoke)
else else
@ -1185,6 +1194,8 @@ function RANGE:OnEventShot(EventData)
-- Distance between bomb and target. -- Distance between bomb and target.
local _temp = impactcoord:Get2DDistance(_target:GetCoordinate()) local _temp = impactcoord:Get2DDistance(_target:GetCoordinate())
--env.info(string.format("FF target = %s dist = %d m", _target:GetName(), _temp))
-- Find closest target to last known position of the bomb. -- Find closest target to last known position of the bomb.
if _distance == nil or _temp < _distance then if _distance == nil or _temp < _distance then
_distance = _temp _distance = _temp
@ -1203,7 +1214,7 @@ function RANGE:OnEventShot(EventData)
end end
end end
-- Count if bomb fell less than 1 km away from the target. -- Count if bomb fell less than ~1 km away from the target.
if _distance <= self.scorebombdistance then if _distance <= self.scorebombdistance then
-- Init bomb player results. -- Init bomb player results.
@ -1222,10 +1233,10 @@ function RANGE:OnEventShot(EventData)
-- Send message. -- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, true)
elseif _distance <= self.rangeradius then elseif insidezone then
-- Send message -- Send message
local _message=string.format("%s, weapon fell more than %.1f km away from nearest range target. No score!", _callsign, self.scorebombdistance/1000) local _message=string.format("%s, weapon fell more than %.1f km away from nearest range target. No score!", _callsign, self.scorebombdistance/1000)
self:_DisplayMessageToGroup(_unit, _message, nil, true) self:_DisplayMessageToGroup(_unit, _message, nil, false)
end end
--Terminate the timer --Terminate the timer

View File

@ -35,7 +35,7 @@
-- === -- ===
-- --
-- @module Functional.Warehouse -- @module Functional.Warehouse
-- @image MOOSE.JPG -- @image Warehouse.JPG
--- WAREHOUSE class. --- WAREHOUSE class.
-- @type WAREHOUSE -- @type WAREHOUSE
@ -69,6 +69,8 @@
-- @field #boolean autosave Automatically save assets to file when mission ends. -- @field #boolean autosave Automatically save assets to file when mission ends.
-- @field #string autosavepath Path where the asset file is saved on auto save. -- @field #string autosavepath Path where the asset file is saved on auto save.
-- @field #string autosavefilename File name of the auto asset save file. Default is auto generated from warehouse id and name. -- @field #string autosavefilename File name of the auto asset save file. Default is auto generated from warehouse id and name.
-- @field #boolean safeparking If true, parking spots for aircraft are considered as occupied if e.g. a client aircraft is parked there. Default false.
-- @field #boolean isunit If true, warehouse is represented by a unit instead of a static.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Have your assets at the right place at the right time - or not! --- Have your assets at the right place at the right time - or not!
@ -624,7 +626,8 @@
-- The @{#WAREHOUSE.OnAfterAttacked} function can be used by the mission designer to react to the enemy attack. For example by deploying some or all ground troops -- The @{#WAREHOUSE.OnAfterAttacked} function can be used by the mission designer to react to the enemy attack. For example by deploying some or all ground troops
-- currently in stock to defend the warehouse. Note that the warehouse also has a self defence option which can be enabled by the @{#WAREHOUSE.SetAutoDefenceOn}() -- currently in stock to defend the warehouse. Note that the warehouse also has a self defence option which can be enabled by the @{#WAREHOUSE.SetAutoDefenceOn}()
-- function. In this case, the warehouse will automatically spawn all ground troops. If the spawn zone is further away from the warehouse zone, all mobile troops -- function. In this case, the warehouse will automatically spawn all ground troops. If the spawn zone is further away from the warehouse zone, all mobile troops
-- are routed to the warehouse zone. -- are routed to the warehouse zone. The self request which is triggered on an automatic defence has the assignment "AutoDefence". So you can use this to
-- give orders to the groups that were spawned using the @{#WAREHOUSE.OnAfterSelfRequest} function.
-- --
-- If only ground troops of the enemy coalition are present in the warehouse zone, the warehouse and all its assets falls into the hands of the enemy. -- If only ground troops of the enemy coalition are present in the warehouse zone, the warehouse and all its assets falls into the hands of the enemy.
-- In this case the event **Captured** is triggered which can be captured by the @{#WAREHOUSE.OnAfterCaptured} function. -- In this case the event **Captured** is triggered which can be captured by the @{#WAREHOUSE.OnAfterCaptured} function.
@ -1555,6 +1558,8 @@ WAREHOUSE = {
autosave = false, autosave = false,
autosavepath = nil, autosavepath = nil,
autosavefile = nil, autosavefile = nil,
saveparking = false,
isunit = false,
} }
--- Item of the warehouse stock table. --- Item of the warehouse stock table.
@ -1716,17 +1721,19 @@ WAREHOUSE.Quantity = {
--- Warehouse database. Note that this is a global array to have easier exchange between warehouses. --- Warehouse database. Note that this is a global array to have easier exchange between warehouses.
-- @type WAREHOUSE.db -- @type WAREHOUSE.db
-- @field #number AssetID Unique ID of each asset. This is a running number, which is increased each time a new asset is added. -- @field #number AssetID Unique ID of each asset. This is a running number, which is increased each time a new asset is added.
-- @field #table Assets Table holding registered assets, which are of type @{Functional.Warehouse#WAREHOUSE.Assetitem}. -- @field #table Assets Table holding registered assets, which are of type @{Functional.Warehouse#WAREHOUSE.Assetitem}.#
-- @field #number WarehouseID Unique ID of the warehouse. Running number.
-- @field #table Warehouses Table holding all defined @{#WAREHOUSE} objects by their unique ids. -- @field #table Warehouses Table holding all defined @{#WAREHOUSE} objects by their unique ids.
WAREHOUSE.db = { WAREHOUSE.db = {
AssetID = 0, AssetID = 0,
Assets = {}, Assets = {},
Warehouses = {} WarehouseID = 0,
Warehouses = {}
} }
--- Warehouse class version. --- Warehouse class version.
-- @field #string version -- @field #string version
WAREHOUSE.version="0.6.4" WAREHOUSE.version="0.6.7"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Warehouse todo list. -- TODO: Warehouse todo list.
@ -1735,12 +1742,12 @@ WAREHOUSE.version="0.6.4"
-- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time? -- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time?
-- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths. -- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths.
-- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number?
-- TODO: Test capturing a neutral warehouse.
-- TODO: Make more examples: ARTY, CAP, ... -- TODO: Make more examples: ARTY, CAP, ...
-- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport. -- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport.
-- TODO: Handle the case when units of a group die during the transfer. -- TODO: Handle the case when units of a group die during the transfer.
-- TODO: Added habours as interface for transport to from warehouses? Could make a rudimentary shipping dispatcher. -- TODO: Added habours as interface for transport to from warehouses? Could make a rudimentary shipping dispatcher.
-- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua! -- DONE: Test capturing a neutral warehouse.
-- DONE: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua!
-- DONE: Get cargo bay and weight from CARGO_GROUP and GROUP. No necessary any more! -- DONE: Get cargo bay and weight from CARGO_GROUP and GROUP. No necessary any more!
-- DONE: Add possibility to set weight and cargo bay manually in AddAsset function as optional parameters. -- DONE: Add possibility to set weight and cargo bay manually in AddAsset function as optional parameters.
-- DONE: Check overlapping aircraft sometimes. -- DONE: Check overlapping aircraft sometimes.
@ -1787,7 +1794,7 @@ WAREHOUSE.version="0.6.4"
--- The WAREHOUSE constructor. Creates a new WAREHOUSE object from a static object. Parameters like the coalition and country are taken from the static object structure. --- The WAREHOUSE constructor. Creates a new WAREHOUSE object from a static object. Parameters like the coalition and country are taken from the static object structure.
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
-- @param Wrapper.Static#STATIC warehouse The physical structure of the warehouse. -- @param Wrapper.Static#STATIC warehouse The physical structure representing the warehouse.
-- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static -- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static
-- @return #WAREHOUSE self -- @return #WAREHOUSE self
function WAREHOUSE:New(warehouse, alias) function WAREHOUSE:New(warehouse, alias)
@ -1795,7 +1802,14 @@ function WAREHOUSE:New(warehouse, alias)
-- Check if just a string was given and convert to static. -- Check if just a string was given and convert to static.
if type(warehouse)=="string" then if type(warehouse)=="string" then
warehouse=STATIC:FindByName(warehouse, true) warehouse=UNIT:FindByName(warehouse)
if warehouse==nil then
env.info(string.format("No warehouse unit with name %s found trying static.", warehouse))
warehouse=STATIC:FindByName(warehouse, true)
self.isunit=false
else
self.isunit=true
end
end end
-- Nil check. -- Nil check.
@ -1818,7 +1832,15 @@ function WAREHOUSE:New(warehouse, alias)
-- Set some variables. -- Set some variables.
self.warehouse=warehouse self.warehouse=warehouse
self.uid=tonumber(warehouse:GetID())
-- Increase global warehouse counter.
WAREHOUSE.db.WarehouseID=WAREHOUSE.db.WarehouseID+1
-- Set unique ID for this warehouse.
self.uid=WAREHOUSE.db.WarehouseID
-- As Kalbuth found out, this would fail when using SPAWNSTATIC https://forums.eagle.ru/showthread.php?p=3703488#post3703488
--self.uid=tonumber(warehouse:GetID())
-- Closest of the same coalition but within a certain range. -- Closest of the same coalition but within a certain range.
local _airbase=self:GetCoordinate():GetClosestAirbase(nil, self:GetCoalition()) local _airbase=self:GetCoordinate():GetClosestAirbase(nil, self:GetCoalition())
@ -1862,7 +1884,7 @@ function WAREHOUSE:New(warehouse, alias)
self:AddTransition("*", "Stop", "Stopped") -- Stop the warehouse. self:AddTransition("*", "Stop", "Stopped") -- Stop the warehouse.
self:AddTransition("Stopped", "Restart", "Running") -- Restart the warehouse when it was stopped before. self:AddTransition("Stopped", "Restart", "Running") -- Restart the warehouse when it was stopped before.
self:AddTransition("Loaded", "Restart", "Running") -- Restart the warehouse when assets were loaded from file before. self:AddTransition("Loaded", "Restart", "Running") -- Restart the warehouse when assets were loaded from file before.
self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. self:AddTransition("*", "Save", "*") -- Save the warehouse state to disk.
self:AddTransition("*", "Attacked", "Attacked") -- Warehouse is under attack by enemy coalition. self:AddTransition("*", "Attacked", "Attacked") -- Warehouse is under attack by enemy coalition.
self:AddTransition("Attacked", "Defeated", "Running") -- Attack by other coalition was defeated! self:AddTransition("Attacked", "Defeated", "Running") -- Attack by other coalition was defeated!
self:AddTransition("*", "ChangeCountry", "*") -- Change country (and coalition) of the warehouse. Warehouse is respawned! self:AddTransition("*", "ChangeCountry", "*") -- Change country (and coalition) of the warehouse. Warehouse is respawned!
@ -2363,6 +2385,24 @@ function WAREHOUSE:SetReportOff()
return self return self
end end
--- Enable safe parking option, i.e. parking spots at an airbase will be considered as occupied when a client aircraft is parked there (even if the client slot is not taken by a player yet).
-- Note that also incoming aircraft can reserve/occupie parking spaces.
-- @param #WAREHOUSE self
-- @return #WAREHOUSE self
function WAREHOUSE:SetSafeParkingOn()
self.safeparking=true
return self
end
--- Disable safe parking option. Note that is the default setting.
-- @param #WAREHOUSE self
-- @return #WAREHOUSE self
function WAREHOUSE:SetSafeParkingOff()
self.safeparking=false
return self
end
--- Set interval of status updates. Note that normally only one request can be processed per time interval. --- Set interval of status updates. Note that normally only one request can be processed per time interval.
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
-- @param #number timeinterval Time interval in seconds. -- @param #number timeinterval Time interval in seconds.
@ -2875,6 +2915,7 @@ function WAREHOUSE:GetAssignment(request)
return tostring(request.assignment) return tostring(request.assignment)
end end
--[[
--- Get warehouse unique ID from static warehouse object. This is the ID under which you find the @{#WAREHOUSE} object in the global data base. --- Get warehouse unique ID from static warehouse object. This is the ID under which you find the @{#WAREHOUSE} object in the global data base.
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
-- @param #string staticname Name of the warehouse static object. -- @param #string staticname Name of the warehouse static object.
@ -2884,6 +2925,7 @@ function WAREHOUSE:GetWarehouseID(staticname)
local uid=tonumber(warehouse:GetID()) local uid=tonumber(warehouse:GetID())
return uid return uid
end end
]]
--- Find a warehouse in the global warehouse data base. --- Find a warehouse in the global warehouse data base.
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
@ -3531,11 +3573,11 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu
self:T(warehouse.wid..string.format("WARNING: Group %s is neither cargo nor transport!", group:GetName())) self:T(warehouse.wid..string.format("WARNING: Group %s is neither cargo nor transport!", group:GetName()))
end end
end -- If no assignment was given we take the assignment of the request if there is any.
if assignment==nil and request.assignment~=nil then
assignment=request.assignment
end
-- If no assignment was given we take the assignment of the request if there is any.
if assignment==nil and request.assignment~=nil then
assignment=request.assignment
end end
end end
@ -3588,6 +3630,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu
else else
self:E(self.wid.."ERROR: Unknown group added as asset!") self:E(self.wid.."ERROR: Unknown group added as asset!")
self:E({unknowngroup=group})
end end
-- Update status. -- Update status.
@ -4620,7 +4663,7 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country)
text=text..string.format("Deploying all %d ground assets.", nground) text=text..string.format("Deploying all %d ground assets.", nground)
-- Add self request. -- Add self request.
self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, WAREHOUSE.Quantity.ALL, nil, nil , 0) self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, WAREHOUSE.Quantity.ALL, nil, nil , 0, "AutoDefence")
else else
text=text..string.format("No ground assets currently available.") text=text..string.format("No ground assets currently available.")
end end
@ -6296,25 +6339,26 @@ function WAREHOUSE:_CheckRequestValid(request)
-- TODO: maybe only check if spots > 0 for the necessary terminal type? At least for FARPS. -- TODO: maybe only check if spots > 0 for the necessary terminal type? At least for FARPS.
-- Get necessary terminal type. -- Get necessary terminal type.
local termtype=self:_GetTerminal(asset.attribute) local termtype_dep=self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
local termtype_des=self:_GetTerminal(asset.attribute, request.warehouse:GetAirbaseCategory())
-- Get number of parking spots. -- Get number of parking spots.
local np_departure=self.airbase:GetParkingSpotsNumber(termtype) local np_departure=self.airbase:GetParkingSpotsNumber(termtype_dep)
local np_destination=request.airbase:GetParkingSpotsNumber(termtype) local np_destination=request.airbase:GetParkingSpotsNumber(termtype_des)
-- Debug info. -- Debug info.
self:T(string.format("Asset attribute = %s, terminal type = %d, spots at departure = %d, destination = %d", asset.attribute, termtype, np_departure, np_destination)) self:T(string.format("Asset attribute = %s, DEPARTURE: terminal type = %d, spots = %d, DESTINATION: terminal type = %d, spots = %d", asset.attribute, termtype_dep, np_departure, termtype_des, np_destination))
-- Not enough parking at sending warehouse. -- Not enough parking at sending warehouse.
--if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) then --if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) then
if np_departure < nasset then if np_departure < nasset then
self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.", termtype, np_departure, nasset)) self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.", termtype_dep, np_departure, nasset))
valid=false valid=false
end end
-- No parking at requesting warehouse. -- No parking at requesting warehouse.
if np_destination == 0 then if np_destination == 0 then
self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse. Available spots = %d!", termtype, np_destination)) self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse. Available spots = %d!", termtype_des, np_destination))
valid=false valid=false
end end
@ -6452,7 +6496,7 @@ function WAREHOUSE:_CheckRequestValid(request)
self:T(text) self:T(text)
-- Get necessary terminal type for helos or transport aircraft. -- Get necessary terminal type for helos or transport aircraft.
local termtype=self:_GetTerminal(request.transporttype) local termtype=self:_GetTerminal(request.transporttype, self:GetAirbaseCategory())
-- Get number of parking spots. -- Get number of parking spots.
local np_departure=self.airbase:GetParkingSpotsNumber(termtype) local np_departure=self.airbase:GetParkingSpotsNumber(termtype)
@ -6471,6 +6515,7 @@ function WAREHOUSE:_CheckRequestValid(request)
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
-- Total number of parking spots for transport planes at destination. -- Total number of parking spots for transport planes at destination.
termtype=self:_GetTerminal(request.transporttype, request.warehouse:GetAirbaseCategory())
local np_destination=request.airbase:GetParkingSpotsNumber(termtype) local np_destination=request.airbase:GetParkingSpotsNumber(termtype)
-- Debug info. -- Debug info.
@ -6898,10 +6943,14 @@ function WAREHOUSE:_SimpleTaskFunction(Function, group)
-- Task script. -- Task script.
local DCSScript = {} local DCSScript = {}
--DCSScript[#DCSScript+1] = string.format('env.info(\"WAREHOUSE: Simple task function called!\") ') --DCSScript[#DCSScript+1] = string.format('env.info(\"WAREHOUSE: Simple task function called!\") ')
DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(\"%s\") ', groupname) -- The group that executes the task function. Very handy with the "...". DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(\"%s\") ', groupname) -- The group that executes the task function. Very handy with the "...".
DCSScript[#DCSScript+1] = string.format("local mystatic = STATIC:FindByName(\"%s\") ", warehouse) -- The static that holds the warehouse self object. if self.isunit then
DCSScript[#DCSScript+1] = string.format('local warehouse = mystatic:GetState(mystatic, \"WAREHOUSE\") ') -- Get the warehouse self object from the static. DCSScript[#DCSScript+1] = string.format("local mywarehouse = UNIT:FindByName(\"%s\") ", warehouse) -- The unit that holds the warehouse self object.
DCSScript[#DCSScript+1] = string.format('%s(mygroup)', Function) -- Call the function, e.g. myfunction.(warehouse,mygroup) else
DCSScript[#DCSScript+1] = string.format("local mywarehouse = STATIC:FindByName(\"%s\") ", warehouse) -- The static that holds the warehouse self object.
end
DCSScript[#DCSScript+1] = string.format('local warehouse = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ') -- Get the warehouse self object from the static.
DCSScript[#DCSScript+1] = string.format('%s(mygroup)', Function) -- Call the function, e.g. myfunction.(warehouse,mygroup)
-- Create task. -- Create task.
local DCSTask = CONTROLLABLE.TaskWrappedAction(self, CONTROLLABLE.CommandDoScript(self, table.concat(DCSScript))) local DCSTask = CONTROLLABLE.TaskWrappedAction(self, CONTROLLABLE.CommandDoScript(self, table.concat(DCSScript)))
@ -6912,13 +6961,13 @@ end
--- Get the proper terminal type based on generalized attribute of the group. --- Get the proper terminal type based on generalized attribute of the group.
--@param #WAREHOUSE self --@param #WAREHOUSE self
--@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit. --@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit.
--@param #number _category Airbase category.
--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group. --@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group.
function WAREHOUSE:_GetTerminal(_attribute) function WAREHOUSE:_GetTerminal(_attribute, _category)
-- Default terminal is "large". -- Default terminal is "large".
local _terminal=AIRBASE.TerminalType.OpenBig local _terminal=AIRBASE.TerminalType.OpenBig
if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER then if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER then
-- Fighter ==> small. -- Fighter ==> small.
_terminal=AIRBASE.TerminalType.FighterAircraft _terminal=AIRBASE.TerminalType.FighterAircraft
@ -6928,6 +6977,15 @@ function WAREHOUSE:_GetTerminal(_attribute)
elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then
-- Helicopter. -- Helicopter.
_terminal=AIRBASE.TerminalType.HelicopterUsable _terminal=AIRBASE.TerminalType.HelicopterUsable
else
--_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
-- For ships, we allow medium spots for all fixed wing aircraft. There are smaller tankers and AWACS aircraft that can use a carrier.
if _category==Airbase.Category.SHIP then
if not (_attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO) then
_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
end end
return _terminal return _terminal
@ -7002,20 +7060,6 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"}) table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
end end
--[[
-- TODO Clients? Unoccupied client aircraft are also important! Are they already included in scanned units maybe?
local clients=_DATABASE.CLIENTS
for _,_client in pairs(clients) do
local client=_client --Wrapper.Client#CLIENT
env.info(string.format("FF Client name %s", client:GetName()))
local unit=UNIT:FindByName(client:GetName())
--local unit=client:GetClientGroupUnit()
local _coord=unit:GetCoordinate()
local _name=unit:GetName()
local _size=self:_GetObjectSize(client:GetClientGroupDCSUnit())
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="client"})
end
]]
end end
-- Parking data for all assets. -- Parking data for all assets.
@ -7026,7 +7070,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
local _asset=asset --#WAREHOUSE.Assetitem local _asset=asset --#WAREHOUSE.Assetitem
-- Get terminal type of this asset -- Get terminal type of this asset
local terminaltype=self:_GetTerminal(asset.attribute) local terminaltype=self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
-- Asset specific parking. -- Asset specific parking.
parking[_asset.uid]={} parking[_asset.uid]={}
@ -7049,9 +7093,16 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
--env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles)) --env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles))
-- Loop over all obstacles.
local free=true local free=true
local problem=nil local problem=nil
-- Safe parking using TO_AC from DCS result.
if self.safeparking and _toac then
free=false
self:T("Parking spot %d is occupied by other aircraft taking off or landing.", _termid)
end
-- Loop over all obstacles.
for _,obstacle in pairs(obstacles) do for _,obstacle in pairs(obstacles) do
-- Check if aircraft overlaps with any obstacle. -- Check if aircraft overlaps with any obstacle.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,19 @@ BIGSMOKEPRESET = {
HugeSmoke=7, HugeSmoke=7,
} }
--- DCS map as returned by env.mission.theatre.
-- @type DCSMAP
-- @field #string Caucasus Caucasus map.
-- @field #string Normandy Normandy map.
-- @field #string NTTR Nevada Test and Training Range map.
-- @field #string PersianGulf Persian Gulf map.
DCSMAP = {
Caucasus="Caucasus",
NTTR="Nevada",
Normandy="Normandy",
PersianGulf="PersianGulf"
}
--- Utilities static class. --- Utilities static class.
-- @type UTILS -- @type UTILS
UTILS = { UTILS = {
@ -250,7 +263,11 @@ UTILS.FeetToMeters = function(feet)
end end
UTILS.KnotsToKmph = function(knots) UTILS.KnotsToKmph = function(knots)
return knots* 1.852 return knots * 1.852
end
UTILS.KmphToKnots = function(knots)
return knots / 1.852
end end
UTILS.KmphToMps = function( kmph ) UTILS.KmphToMps = function( kmph )
@ -281,7 +298,26 @@ UTILS.CelciusToFarenheit = function( Celcius )
return Celcius * 9/5 + 32 return Celcius * 9/5 + 32
end end
--- Convert pressure from hecto Pascal (hPa) to inches of mercury (inHg).
-- @param #number hPa Pressure in hPa.
-- @return #number Pressure in inHg.
UTILS.hPa2inHg = function( hPa )
return hPa * 0.0295299830714
end
--- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg).
-- @param #number hPa Pressure in hPa.
-- @return #number Pressure in mmHg.
UTILS.hPa2mmHg = function( hPa )
return hPa * 0.7500615613030
end
--- Convert kilo gramms (kg) to pounds (lbs).
-- @param #number kg Mass in kg.
-- @return #number Mass in lbs.
UTILS.kg2lbs = function( kg )
return kg * 2.20462
end
--[[acc: --[[acc:
in DM: decimal point of minutes. in DM: decimal point of minutes.
@ -526,7 +562,7 @@ function UTILS.SecondsToClock(seconds)
-- Seconds of this day. -- Seconds of this day.
local _seconds=seconds%(60*60*24) local _seconds=seconds%(60*60*24)
if seconds <= 0 then if seconds<0 then
return nil return nil
else else
local hours = string.format("%02.f", math.floor(_seconds/3600)) local hours = string.format("%02.f", math.floor(_seconds/3600))
@ -539,7 +575,7 @@ end
--- Convert clock time from hours, minutes and seconds to seconds. --- Convert clock time from hours, minutes and seconds to seconds.
-- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days. -- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days.
-- @param #number Seconds. Corresponds to what you cet from timer.getAbsTime() function. -- @return #number Seconds. Corresponds to what you cet from timer.getAbsTime() function.
function UTILS.ClockToSeconds(clock) function UTILS.ClockToSeconds(clock)
-- Nil check. -- Nil check.
@ -551,7 +587,7 @@ function UTILS.ClockToSeconds(clock)
local seconds=0 local seconds=0
-- Split additional days. -- Split additional days.
local dsplit=UTILS.split(clock, "+") local dsplit=UTILS.Split(clock, "+")
-- Convert days to seconds. -- Convert days to seconds.
if #dsplit>1 then if #dsplit>1 then
@ -680,3 +716,116 @@ function UTILS.VecCross(a, b)
return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x} return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x}
end end
--- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return DCS#Vec3 Vector c=a-b with c(i)=a(i)-b(i), i=x,y,z.
function UTILS.VecSubstract(a, b)
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
end
--- Calculate the angle between two 3D vectors.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return #number Angle alpha between and b in degrees. alpha=acos(a*b)/(|a||b|), (* denotes the dot product).
function UTILS.VecAngle(a, b)
local alpha=math.acos(UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b)))
return math.deg(alpha)
end
--- Rotate 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.
-- @param #number angle Rotation angle in degrees.
-- @return DCS#Vec3 Vector rotated in the (x,z) plane.
function UTILS.Rotate2D(a, angle)
local phi=math.rad(angle)
local x=a.z
local y=a.x
local Z=x*math.cos(phi)-y*math.sin(phi)
local X=x*math.sin(phi)+y*math.cos(phi)
local Y=a.y
local A={x=X, y=Y, z=Z}
return A
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz.
-- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X".
-- @param #string TACANMode The TACAN mode, i.e. the "X" in "10X".
-- @return #number Frequency in Hz or #nil if parameters are invalid.
function UTILS.TACANToFrequency(TACANChannel, TACANMode)
if type(TACANChannel) ~= "number" then
return nil -- error in arguments
end
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end
--- Returns the DCS map/theatre as optained by env.mission.theatre
-- @return #string DCS map name .
function UTILS.GetDCSMap()
return env.mission.theatre
end
--- Returns the magnetic declination of the map.
-- Returned values for the current maps are:
--
-- * Caucasus +6 (East), year ~ 2011
-- * NTTR +12 (East), year ~ 2011
-- * Normandy -10 (West), year ~ 1944
-- * Persian Gulf +2 (East), year ~ 2011
-- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre
-- @return #number Declination in degrees.
function UTILS.GetMagneticDeclination(map)
-- Map.
map=map or UTILS.GetDCSMap()
local declination=0
if map==DCSMAP.Caucasus then
declination=6
elseif map==DCSMAP.NTTR then
declination=12
elseif map==DCSMAP.Normandy then
declination=-10
elseif map==DCSMAP.PersianGulf then
declination=2
else
declination=0
end
return declination
end

View File

@ -238,6 +238,8 @@ AIRBASE.Normandy = {
-- * AIRBASE.PersianGulf.Sharjah_Intl -- * AIRBASE.PersianGulf.Sharjah_Intl
-- * AIRBASE.PersianGulf.Shiraz_International_Airport -- * AIRBASE.PersianGulf.Shiraz_International_Airport
-- * AIRBASE.PersianGulf.Kerman_Airport -- * AIRBASE.PersianGulf.Kerman_Airport
-- * AIRBASE.PersianGulf.Jiroft_Airport
-- * AIRBASE.PersianGulf.Lavan_Island_Airport
-- @field PersianGulf -- @field PersianGulf
AIRBASE.PersianGulf = { AIRBASE.PersianGulf = {
["Fujairah_Intl"] = "Fujairah Intl", ["Fujairah_Intl"] = "Fujairah Intl",
@ -259,6 +261,8 @@ AIRBASE.PersianGulf = {
["Sharjah_Intl"] = "Sharjah Intl", ["Sharjah_Intl"] = "Sharjah Intl",
["Shiraz_International_Airport"] = "Shiraz International Airport", ["Shiraz_International_Airport"] = "Shiraz International Airport",
["Kerman_Airport"] = "Kerman Airport", ["Kerman_Airport"] = "Kerman Airport",
["Jiroft_Airport"] = "Jiroft Airport",
["Lavan_Island_Airport"] = "Lavan Island Airport",
} }
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".

View File

@ -148,7 +148,7 @@
-- * @{#CONTROLLABLE.OptionROEReturnFirePossible} -- * @{#CONTROLLABLE.OptionROEReturnFirePossible}
-- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} -- * @{#CONTROLLABLE.OptionROEEvadeFirePossible}
-- --
-- ## 5.2) Rule on thread: -- ## 5.2) Reaction On Thread:
-- --
-- * @{#CONTROLLABLE.OptionROTNoReaction} -- * @{#CONTROLLABLE.OptionROTNoReaction}
-- * @{#CONTROLLABLE.OptionROTPassiveDefense} -- * @{#CONTROLLABLE.OptionROTPassiveDefense}
@ -347,16 +347,26 @@ function CONTROLLABLE:PushTask( DCSTask, WaitTime )
local DCSControllable = self:GetDCSObject() local DCSControllable = self:GetDCSObject()
if DCSControllable then if DCSControllable then
local Controller = self:_GetController()
local DCSControllableName = self:GetName()
-- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results.
-- Therefore we schedule the functions to set the mission and options for the Controllable. -- Therefore we schedule the functions to set the mission and options for the Controllable.
-- Controller:pushTask( DCSTask ) -- Controller:pushTask( DCSTask )
if WaitTime then local function PushTask( Controller, DCSTask )
self.TaskScheduler:Schedule( Controller, Controller.pushTask, { DCSTask }, WaitTime ) if self and self:IsAlive() then
local Controller = self:_GetController()
Controller:pushTask( DCSTask )
else
BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } )
end
end
if not WaitTime or WaitTime == 0 then
PushTask( self, DCSTask )
else else
Controller:pushTask( DCSTask ) self.TaskScheduler:Schedule( self, PushTask, { DCSTask }, WaitTime )
end end
return self return self
@ -367,7 +377,7 @@ end
--- Clearing the Task Queue and Setting the Task on the queue from the controllable. --- Clearing the Task Queue and Setting the Task on the queue from the controllable.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #DCS.Task DCSTask DCS Task array. -- @param DCS#Task DCSTask DCS Task array.
-- @param #number WaitTime Time in seconds, before the task is set. -- @param #number WaitTime Time in seconds, before the task is set.
-- @return Wrapper.Controllable#CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE self
function CONTROLLABLE:SetTask( DCSTask, WaitTime ) function CONTROLLABLE:SetTask( DCSTask, WaitTime )
@ -547,9 +557,9 @@ end
--- Executes a command action --- Executes a command action for the CONTROLLABLE.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param DCS#Command DCSCommand -- @param DCS#Command DCSCommand The command to be executed.
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:SetCommand( DCSCommand ) function CONTROLLABLE:SetCommand( DCSCommand )
self:F2( DCSCommand ) self:F2( DCSCommand )
@ -637,9 +647,122 @@ function CONTROLLABLE:StartUncontrolled(delay)
return self return self
end end
--- Give the CONTROLLABLE the command to activate a beacon. See [DCS_command_activateBeacon](https://wiki.hoggitworld.com/view/DCS_command_activateBeacon) on Hoggit.
-- For specific beacons like TACAN use the more convenient @{#BEACON} class.
-- Note that a controllable can only have one beacon activated at a time with the execption of ICLS.
-- @param #CONTROLLABLE self
-- @param Core.Radio#BEACON.Type Type Beacon type (VOR, DME, TACAN, RSBN, ILS etc).
-- @param Core.Radio#BEACON.System System Beacon system (VOR, DME, TACAN, RSBN, ILS etc).
-- @param #number Frequency Frequency in Hz the beacon is running on. Use @{#UTILS.TACANToFrequency} to generate a frequency for TACAN beacons.
-- @param #number UnitID The ID of the unit the beacon is attached to. Usefull if more units are in one group.
-- @param #number Channel Channel the beacon is using. For, e.g. TACAN beacons.
-- @param #string ModeChannel The TACAN mode of the beacon, i.e. "X" or "Y".
-- @param #boolean AA If true, create and Air-Air beacon. IF nil, automatically set if CONTROLLABLE depending on whether unit is and aircraft or not.
-- @param #string Callsign Morse code identification callsign.
-- @param #boolean Bearing If true, beacon provides bearing information - if supported by the unit the beacon is attached to.
-- @param #number Delay (Optional) Delay in seconds before the beacon is activated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing, Delay)
AA=AA or self:IsAir()
UnitID=UnitID or self:GetID()
-- Command
local CommandActivateBeacon= {
id = "ActivateBeacon",
params = {
["type"] = Type,
["system"] = System,
["frequency"] = Frequency,
["unitId"] = UnitID,
["channel"] = Channel,
["modeChannel"] = ModeChannel,
["AA"] = AA,
["callsign"] = Callsign,
["bearing"] = Bearing,
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateBeacon, {self, Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing}, Delay)
else
self:SetCommand(CommandActivateBeacon)
end
return self
end
--- Activate ICLS system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Channel ICLS channel.
-- @param #number UnitID The ID of the unit the ICLS system is attached to. Useful if more units are in one group.
-- @param #string Callsign Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateICLS(Channel, UnitID, Callsign, Delay)
self:F()
-- Command to activate ICLS system.
local CommandActivateICLS= {
id = "ActivateICLS",
params= {
["type"] = BEACON.Type.ICLS,
["channel"] = Channel,
["unitId"] = UnitID,
["callsign"] = Callsign,
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateICLS, {self}, Delay)
else
self:SetCommand(CommandActivateICLS)
end
return self
end
--- Deactivate the active beacon of the CONTROLLABLE.
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the beacon is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandDeactivateBeacon(Delay)
self:F()
-- Command to deactivate
local CommandDeactivateBeacon={id='DeactivateBeacon', params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateBeacon, {self}, Delay)
else
self:SetCommand(CommandDeactivateBeacon)
end
return self
end
--- Deactivate the ICLS of the CONTROLLABLE.
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandDeactivateICLS(Delay)
self:F()
-- Command to deactivate
local CommandDeactivateICLS={id='DeactivateICLS', params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandDeactivateICLS, {self}, Delay)
else
self:SetCommand(CommandDeactivateICLS)
end
return self
end
-- TASKS FOR AIR CONTROLLABLES -- TASKS FOR AIR CONTROLLABLES
--- (AIR) Attack a Controllable. --- (AIR) Attack a Controllable.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked.
@ -877,6 +1000,38 @@ function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed )
return DCSTask return DCSTask
end end
--- (AIR) Orbit at a position with at a given altitude and speed. Optionally, a race track pattern can be specified.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE Coord Coordinate at which the CONTROLLABLE orbits.
-- @param #number Altitude Altitude in meters of the orbit pattern.
-- @param #number Speed Speed [m/s] flying the orbit pattern
-- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate.
-- @return #CONTROLLABLE self
function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack)
local Pattern=AI.Task.OrbitPattern.CIRCLE
local P1=Coord:GetVec2()
local P2=nil
if CoordRaceTrack then
Pattern=AI.Task.OrbitPattern.RACE_TRACK
P2=CoordRaceTrack:GetVec2()
end
local Task = {
id = 'Orbit',
params = {
pattern = Pattern,
point = P1,
point2 = P2,
speed = Speed,
altitude = Altitude,
}
}
return Task
end
--- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #number Altitude The altitude [m] to hold the position. -- @param #number Altitude The altitude [m] to hold the position.
@ -965,11 +1120,7 @@ function CONTROLLABLE:TaskRefueling()
-- params = {} -- params = {}
-- } -- }
local DCSTask local DCSTask={id='Refueling', params={}}
DCSTask = { id = 'Refueling',
params = {
},
},
self:T3( { DCSTask } ) self:T3( { DCSTask } )
return DCSTask return DCSTask
@ -2109,7 +2260,7 @@ do -- Route methods
FromCoordinate = FromCoordinate or self:GetCoordinate() FromCoordinate = FromCoordinate or self:GetCoordinate()
-- Get path and path length on road including the end points (From and To). -- Get path and path length on road including the end points (From and To).
local PathOnRoad, LengthOnRoad=FromCoordinate:GetPathOnRoad(ToCoordinate, true) local PathOnRoad, LengthOnRoad, GotPath =FromCoordinate:GetPathOnRoad(ToCoordinate, true)
-- Get the length only(!) on the road. -- Get the length only(!) on the road.
local _,LengthRoad=FromCoordinate:GetPathOnRoad(ToCoordinate, false) local _,LengthRoad=FromCoordinate:GetPathOnRoad(ToCoordinate, false)
@ -2121,7 +2272,7 @@ do -- Route methods
-- Calculate the direct distance between the initial and final points. -- Calculate the direct distance between the initial and final points.
local LengthDirect=FromCoordinate:Get2DDistance(ToCoordinate) local LengthDirect=FromCoordinate:Get2DDistance(ToCoordinate)
if PathOnRoad then if GotPath then
-- Off road part of the rout: Total=OffRoad+OnRoad. -- Off road part of the rout: Total=OffRoad+OnRoad.
LengthOffRoad=LengthOnRoad-LengthRoad LengthOffRoad=LengthOnRoad-LengthRoad
@ -2144,7 +2295,7 @@ do -- Route methods
local canroad=false local canroad=false
-- Check if a valid path on road could be found. -- Check if a valid path on road could be found.
if PathOnRoad and LengthDirect > 2000 then -- if the length of the movement is less than 1 km, drive directly. if GotPath and LengthDirect > 2000 then -- if the length of the movement is less than 1 km, drive directly.
-- Check whether the road is very long compared to direct path. -- Check whether the road is very long compared to direct path.
if LongRoad and Shortcut then if LongRoad and Shortcut then
@ -3073,6 +3224,3 @@ function CONTROLLABLE:IsAirPlane()
return nil return nil
end end
-- Message APIs

View File

@ -325,7 +325,7 @@ end
-- So all event listeners will catch the destroy event of this group for each unit in the group. -- So all event listeners will catch the destroy event of this group for each unit in the group.
-- To raise these events, provide the `GenerateEvent` parameter. -- To raise these events, provide the `GenerateEvent` parameter.
-- @param #GROUP self -- @param #GROUP self
-- @param #boolean GenerateEvent true if you want to generate a crash or dead event for each unit. -- @param #boolean GenerateEvent If true, a crash or dead event for each unit is generated. If false, if no event is triggered. If nil, a RemoveUnit event is triggered.
-- @usage -- @usage
-- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. -- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group.
-- Helicopter = GROUP:FindByName( "Helicopter" ) -- Helicopter = GROUP:FindByName( "Helicopter" )
@ -1477,29 +1477,61 @@ end
-- --
-- @param Wrapper.Group#GROUP self -- @param Wrapper.Group#GROUP self
-- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. -- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself.
-- @param #boolean Reset Reset positons if TRUE.
-- @return Wrapper.Group#GROUP self
function GROUP:Respawn( Template, Reset ) function GROUP:Respawn( Template, Reset )
if not Template then -- Given template or get old.
Template = self:GetTemplate() Template = Template or self:GetTemplate()
-- Get correct heading.
local function _Heading(course)
local h
if course<=180 then
h=math.rad(course)
else
h=-math.rad(360-course)
end
return h
end end
-- First check if group is alive.
if self:IsAlive() then if self:IsAlive() then
-- Respawn zone.
local Zone = self.InitRespawnZone -- Core.Zone#ZONE local Zone = self.InitRespawnZone -- Core.Zone#ZONE
-- Zone position or current group position.
local Vec3 = Zone and Zone:GetVec3() or self:GetVec3() local Vec3 = Zone and Zone:GetVec3() or self:GetVec3()
-- From point of the template.
local From = { x = Template.x, y = Template.y } local From = { x = Template.x, y = Template.y }
-- X, Y
Template.x = Vec3.x Template.x = Vec3.x
Template.y = Vec3.z Template.y = Vec3.z
--Template.x = nil --Template.x = nil
--Template.y = nil --Template.y = nil
-- Debug number of units.
self:F( #Template.units ) self:F( #Template.units )
-- Reset position etc?
if Reset == true then if Reset == true then
-- Loop over units in group.
for UnitID, UnitData in pairs( self:GetUnits() ) do for UnitID, UnitData in pairs( self:GetUnits() ) do
local GroupUnit = UnitData -- Wrapper.Unit#UNIT local GroupUnit = UnitData -- Wrapper.Unit#UNIT
self:F( GroupUnit:GetName() ) self:F(GroupUnit:GetName())
if GroupUnit:IsAlive() then if GroupUnit:IsAlive() then
self:F( "Alive" ) self:F("Alive")
-- Get unit position vector.
local GroupUnitVec3 = GroupUnit:GetVec3() local GroupUnitVec3 = GroupUnit:GetVec3()
-- Check if respawn zone is set.
if Zone then if Zone then
if self.InitRespawnRandomizePositionZone then if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3() GroupUnitVec3 = Zone:GetRandomVec3()
@ -1512,17 +1544,38 @@ function GROUP:Respawn( Template, Reset )
end end
end end
-- Altitude
Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
Template.units[UnitID].x = ( Template.units[UnitID].x - From.x ) + GroupUnitVec3.x -- Keep the original x position of the template and translate to the new position.
Template.units[UnitID].y = ( Template.units[UnitID].y - From.y ) + GroupUnitVec3.z -- Keep the original z position of the template and translate to the new position. -- Unit position. Why not simply take the current positon?
Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading() if Zone then
Template.units[UnitID].x = ( Template.units[UnitID].x - From.x ) + GroupUnitVec3.x -- Keep the original x position of the template and translate to the new position.
Template.units[UnitID].y = ( Template.units[UnitID].y - From.y ) + GroupUnitVec3.z -- Keep the original z position of the template and translate to the new position.
else
Template.units[UnitID].x=GroupUnitVec3.x
Template.units[UnitID].y=GroupUnitVec3.z
end
-- Set heading.
Template.units[UnitID].heading = _Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading())
Template.units[UnitID].psi = -Template.units[UnitID].heading
-- Debug.
self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end end
end end
else
else -- Reset=false or nil
-- Loop over template units.
for UnitID, TemplateUnitData in pairs( Template.units ) do for UnitID, TemplateUnitData in pairs( Template.units ) do
self:F( "Reset" ) self:F( "Reset" )
-- Position from template.
local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y } local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y }
-- Respawn zone position.
if Zone then if Zone then
if self.InitRespawnRandomizePositionZone then if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3() GroupUnitVec3 = Zone:GetRandomVec3()
@ -1535,23 +1588,36 @@ function GROUP:Respawn( Template, Reset )
end end
end end
-- Set altitude.
Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
-- Unit position.
Template.units[UnitID].x = ( Template.units[UnitID].x - From.x ) + GroupUnitVec3.x -- Keep the original x position of the template and translate to the new position. Template.units[UnitID].x = ( Template.units[UnitID].x - From.x ) + GroupUnitVec3.x -- Keep the original x position of the template and translate to the new position.
Template.units[UnitID].y = ( Template.units[UnitID].y - From.y ) + GroupUnitVec3.z -- Keep the original z position of the template and translate to the new position. Template.units[UnitID].y = ( Template.units[UnitID].y - From.y ) + GroupUnitVec3.z -- Keep the original z position of the template and translate to the new position.
-- Heading
Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading
-- Debug.
self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end end
end end
end end
self:Destroy() -- Destroy old group. Dont trigger any dead/crash events since this is a respawn.
_DATABASE:Spawn( Template ) self:Destroy(false)
self:T({Template=Template})
-- Spawn new group.
_DATABASE:Spawn(Template)
-- Reset events.
self:ResetEvents() self:ResetEvents()
return self return self
end end
@ -1652,6 +1718,7 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) --
-- Destroy old group. -- Destroy old group.
self:Destroy(false) self:Destroy(false)
-- Spawn new group.
_DATABASE:Spawn( SpawnTemplate ) _DATABASE:Spawn( SpawnTemplate )
-- Reset events. -- Reset events.
@ -1800,8 +1867,8 @@ do -- Route methods
-- --
-- @param #GROUP self -- @param #GROUP self
-- @param Wrapper.Airbase#AIRBASE RTBAirbase (optional) The @{Wrapper.Airbase} to return to. If blank, the controllable will return to the nearest friendly airbase. -- @param Wrapper.Airbase#AIRBASE RTBAirbase (optional) The @{Wrapper.Airbase} to return to. If blank, the controllable will return to the nearest friendly airbase.
-- @param #number Speed (optional) The Speed, if no Speed is given, the maximum Speed of the first unit is selected. -- @param #number Speed (optional) The Speed, if no Speed is given, 80% of maximum Speed of the group is selected.
-- @return #GROUP -- @return #GROUP self
function GROUP:RouteRTB( RTBAirbase, Speed ) function GROUP:RouteRTB( RTBAirbase, Speed )
self:F( { RTBAirbase:GetName(), Speed } ) self:F( { RTBAirbase:GetName(), Speed } )
@ -1811,9 +1878,12 @@ do -- Route methods
if RTBAirbase then if RTBAirbase then
-- If speed is not given take 80% of max speed.
local Speed=Speed or self:GetSpeedMax()*0.8
--[[
local GroupPoint = self:GetVec2() local GroupPoint = self:GetVec2()
local GroupVelocity = self:GetUnit(1):GetDesc().speedMax local GroupVelocity = self:GetUnit(1):GetDesc().speedMax
local PointFrom = {} local PointFrom = {}
PointFrom.x = GroupPoint.x PointFrom.x = GroupPoint.x
PointFrom.y = GroupPoint.y PointFrom.y = GroupPoint.y
@ -1821,7 +1891,6 @@ do -- Route methods
PointFrom.action = "Turning Point" PointFrom.action = "Turning Point"
PointFrom.speed = GroupVelocity PointFrom.speed = GroupVelocity
local PointTo = {} local PointTo = {}
local AirbasePointVec2 = RTBAirbase:GetPointVec2() local AirbasePointVec2 = RTBAirbase:GetPointVec2()
local AirbaseAirPoint = AirbasePointVec2:WaypointAir( local AirbaseAirPoint = AirbasePointVec2:WaypointAir(
@ -1832,21 +1901,42 @@ do -- Route methods
) )
AirbaseAirPoint["airdromeId"] = RTBAirbase:GetID() AirbaseAirPoint["airdromeId"] = RTBAirbase:GetID()
AirbaseAirPoint["speed_locked"] = true, AirbaseAirPoint["speed_locked"] = true
]]
self:F(AirbaseAirPoint ) -- Curent (from) waypoint.
local coord=self:GetCoordinate()
local PointFrom=coord:WaypointAirTurningPoint(nil, Speed)
local Points = { PointFrom, AirbaseAirPoint } -- Airbase coordinate.
--local PointAirbase=RTBAirbase:GetCoordinate():SetAltitude(coord.y):WaypointAirTurningPoint(nil ,Speed)
self:T3( Points ) -- Landing waypoint. More general than prev version since it should also work with FAPRS and ships.
local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed, RTBAirbase)
local Template = self:GetTemplate() -- Waypoint table.
Template.route.points = Points local Points={PointFrom, PointLanding}
self:Respawn( Template ) --local Points={PointFrom, PointAirbase, PointLanding}
--self:Route( Points ) -- Debug info.
self:T3(Points)
-- Get group template.
local Template=self:GetTemplate()
-- Set route points.
Template.route.points=Points
-- Respawn the group.
self:Respawn(Template, true)
-- Route the group or this will not work.
self:Route(Points)
else else
-- Clear all tasks.
self:ClearTasks() self:ClearTasks()
end end
end end

View File

@ -656,6 +656,14 @@ function POSITIONABLE:GetVelocityMPS()
return 0 return 0
end end
--- Returns the POSITIONABLE velocity in knots.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number The velocity in knots.
function POSITIONABLE:GetVelocityKNOTS()
self:F2( self.PositionableName )
return UTILS.MpsToKnots(self:GetVelocityMPS())
end
--- Returns the Angle of Attack of a positionable. --- Returns the Angle of Attack of a positionable.
-- @param Wrapper.Positionable#POSITIONABLE self -- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number Angle of attack in degrees. -- @return #number Angle of attack in degrees.
@ -706,8 +714,8 @@ end
--- Returns the unit's climb or descent angle. --- Returns the unit's climb or descent angle.
-- @param Wrapper.Positionable#POSITIONABLE self -- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number Climb or descent angle in degrees. -- @return #number Climb or descent angle in degrees. Or 0 if velocity vector norm is zero (or nil). Or nil, if the position of the POSITIONABLE returns nil.
function POSITIONABLE:GetClimbAnge() function POSITIONABLE:GetClimbAngle()
-- Get position of the unit. -- Get position of the unit.
local unitpos = self:GetPosition() local unitpos = self:GetPosition()
@ -719,10 +727,17 @@ function POSITIONABLE:GetClimbAnge()
if unitvel and UTILS.VecNorm(unitvel)~=0 then if unitvel and UTILS.VecNorm(unitvel)~=0 then
return math.asin(unitvel.y/UTILS.VecNorm(unitvel)) -- Calculate climb angle.
local angle=math.asin(unitvel.y/UTILS.VecNorm(unitvel))
-- Return angle in degrees.
return math.deg(angle)
else
return 0
end end
end end
return nil
end end
--- Returns the pitch angle of a unit. --- Returns the pitch angle of a unit.

View File

@ -902,29 +902,31 @@ end
function UNIT:InAir() function UNIT:InAir()
self:F2( self.UnitName ) self:F2( self.UnitName )
-- Get DCS unit object.
local DCSUnit = self:GetDCSObject() --DCS#Unit local DCSUnit = self:GetDCSObject() --DCS#Unit
if DCSUnit then if DCSUnit then
-- Implementation of workaround. The original code is below.
-- This to simulate the landing on buildings.
local UnitInAir = true -- Get DCS result of whether unit is in air or not.
local UnitInAir = DCSUnit:inAir()
-- Get unit category.
local UnitCategory = DCSUnit:getDesc().category local UnitCategory = DCSUnit:getDesc().category
if UnitCategory == Unit.Category.HELICOPTER then
-- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not.
-- This is a workaround since DCS currently does not acknoledge that helos land on buildings.
-- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier!
if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER then
local VelocityVec3 = DCSUnit:getVelocity() local VelocityVec3 = DCSUnit:getVelocity()
local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec local Velocity = UTILS.VecNorm(VelocityVec3)
local Coordinate = DCSUnit:getPoint() local Coordinate = DCSUnit:getPoint()
local LandHeight = land.getHeight( { x = Coordinate.x, y = Coordinate.z } ) local LandHeight = land.getHeight( { x = Coordinate.x, y = Coordinate.z } )
local Height = Coordinate.y - LandHeight local Height = Coordinate.y - LandHeight
if Velocity < 1 and Height <= 60 then if Velocity < 1 and Height <= 60 then
UnitInAir = false UnitInAir = false
end end
else
UnitInAir = DCSUnit:inAir()
end end
self:T3( UnitInAir ) self:T3( UnitInAir )
return UnitInAir return UnitInAir
end end

View File

@ -59,6 +59,10 @@ Functional/Suppression.lua
Functional/PseudoATC.lua Functional/PseudoATC.lua
Functional/Warehouse.lua Functional/Warehouse.lua
Ops/Airboss.lua
Ops/RecoveryTanker.lua
Ops/RescueHelo.lua
AI/AI_Balancer.lua AI/AI_Balancer.lua
AI/AI_Air.lua AI/AI_Air.lua
AI/AI_A2A.lua AI/AI_A2A.lua