Improvements and Fixes from FF/Develop

This commit is contained in:
Frank 2018-12-09 12:01:15 +01:00
parent 921ec8732c
commit 9795d5655f
17 changed files with 911 additions and 289 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,12 +459,12 @@ 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. -- @return Core.Point#COORDINATE The new calculated COORDINATE.
function COORDINATE:Translate( Distance, Angle ) function COORDINATE:Translate( Distance, Angle )
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
@ -1121,6 +1120,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 +1131,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,
@ -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
end
self:E({"Power is invalid. Power unchanged.", self.Power}) self:E({"Power is invalid. Power unchanged.", self.Power})
end
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
return self else
end
end
self.SubtitleDuration = 0 self.SubtitleDuration = 0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration}) self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end
return self
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
-- Check.
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end
-- Beacon type.
local Type=BEACON.Type.TACAN
-- Beacon system.
local System=BEACON.System.TACAN
-- Check if unit is an aircraft and set system accordingly.
local AA=self.Positionable:IsAir()
if AA then
System=BEACON.System.TACAN_TANKER
-- Check if "Y" mode is selected for aircraft.
if Mode~="Y" 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})
end end
end end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. -- Attached unit.
-- I have no idea what it does but it seems to work local UnitID=self.Positionable:GetID()
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then -- Debug.
B = 1 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 end
if TACANMode == 'Y' then return self
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 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

@ -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
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil, USERSOUND.ToGroup,{self, Group}, Delay)
else
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName ) 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,38 @@ 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.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments )
self:F2(FlareColor)
Segments=Segments or 10
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 ):Flare(FlareColor)
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,15 +4253,24 @@ 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)))
if _remove then
-- The ARTY group is immobile and not cargo but the target is not in range!
table.insert(targets2delete, _target.name)
else
-- Init default for assigning moves into range. -- Init default for assigning moves into range.
local _movetowards=false local _movetowards=false
local _moveaway=false local _moveaway=false
@ -4346,8 +4355,14 @@ function ARTY:_CheckTargetsInRange()
_target.inrange=_inrange _target.inrange=_inrange
self:T3(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange))) self:T3(ARTY.id..string.format("After: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
end 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

@ -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 = {
@ -281,7 +294,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.
@ -539,7 +571,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 +583,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 +712,77 @@ 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
--- 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

@ -342,16 +342,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
else local Controller = self:_GetController()
Controller:pushTask( DCSTask ) 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
self.TaskScheduler:Schedule( self, PushTask, { DCSTask }, WaitTime )
end end
return self return self
@ -362,7 +372,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 )
@ -540,9 +550,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 )
@ -630,9 +640,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.
@ -870,6 +993,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.
@ -958,11 +1113,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
@ -2101,7 +2252,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)
@ -2113,7 +2264,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
@ -2136,7 +2287,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
@ -3024,6 +3175,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" )
@ -1483,6 +1483,17 @@ function GROUP:Respawn( Template, Reset )
Template = self:GetTemplate() Template = self:GetTemplate()
end end
-- 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
if self:IsAlive() then if self:IsAlive() then
local Zone = self.InitRespawnZone -- Core.Zone#ZONE local Zone = self.InitRespawnZone -- Core.Zone#ZONE
local Vec3 = Zone and Zone:GetVec3() or self:GetVec3() local Vec3 = Zone and Zone:GetVec3() or self:GetVec3()
@ -1515,7 +1526,8 @@ function GROUP:Respawn( Template, Reset )
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].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.
Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading() Template.units[UnitID].heading = _Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading())
Template.units[UnitID].psi = -Template.units[UnitID].heading
self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end end
end end

View File

@ -706,8 +706,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 +719,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