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 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 #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.
@ -106,6 +107,7 @@ AI_FORMATION = {
FollowScheduler = nil,
OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE,
OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
dtFollow = 0.5,
}
--- AI_FORMATION.Mode class
@ -125,6 +127,7 @@ AI_FORMATION = {
-- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet.
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
-- @param #string FollowName Name of the escort.
-- @param #string FollowBriefing Briefing.
-- @return #AI_FORMATION self
function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1
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( "None", "Start", "Following" )
self:AddTransition( {"None", "Stopped"}, "Start", "Following" )
self:AddTransition( "*", "FormationLine", "*" )
--- FormationLine Handler OnBefore for AI_FORMATION
@ -620,6 +623,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
return self
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 allows to visualize where the escort is flying to.
-- @param #AI_FORMATION self
@ -893,7 +906,30 @@ function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1
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
self:F( )
@ -1033,7 +1069,7 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
self, ClientUnit, CT1, CV1, CT2, CV2
)
self:__Follow( -0.5 )
self:__Follow( -self.dtFollow )
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
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 #number radius (Optional) Scan radius in meters. Default 100 m.
-- @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 scanscenery (Optional) If true scan for scenery objects. Default false.
-- @return True if units were found.
-- @return True if statics were found.
-- @return True if scenery objects were found.
-- @return Unit objects found.
-- @return Static objects found.
-- @return Scenery objects found.
-- @return #boolean True if units were found.
-- @return #boolean True if statics were found.
-- @return #boolean True if scenery objects were found.
-- @return #table Table of MOOSE @[#Wrapper.Unit#UNIT} objects found.
-- @return #table Table of DCS static objects found.
-- @return #table Table of DCS scenery objects found.
function COORDINATE:ScanObjects(radius, scanunits, scanstatics, scanscenery)
self:F(string.format("Scanning in radius %.1f m.", radius))
@ -405,13 +405,12 @@ do -- COORDINATE
local ObjectCategory = ZoneObject:getCategory()
-- 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))
gotunits=true
elseif (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then
elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then
table.insert(Statics, ZoneObject)
gotstatics=true
@ -460,17 +459,46 @@ do -- COORDINATE
--- Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE.
-- @param #COORDINATE self
-- @param DCS#Distance Distance The Distance to be added in meters.
-- @param DCS#Angle Angle The Angle in degrees.
-- @return #COORDINATE The new calculated COORDINATE.
function COORDINATE:Translate( Distance, Angle )
-- @param DCS#Angle Angle The Angle in degrees. Defaults to 0 if not specified (nil).
-- @param #boolean Keepalt If true, keep altitude of original coordinate. Default is that the new coordinate is created at the translated land height.
-- @return Core.Point#COORDINATE The new calculated COORDINATE.
function COORDINATE:Translate( Distance, Angle, Keepalt )
local SX = self.x
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 TY = Distance * math.sin( Radians ) + SY
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
--- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE.
-- @param #COORDINATE self
@ -1003,11 +1031,15 @@ do -- COORDINATE
function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description )
self:F2( { AltType, Type, Action, Speed, SpeedLocked } )
-- Defaults
-- Set alttype or "RADIO" which is AGL.
AltType=AltType or "RADIO"
-- Speedlocked by default
if SpeedLocked==nil then
SpeedLocked=true
end
-- Speed or default 500 km/h.
Speed=Speed or 500
-- Waypoint array.
@ -1016,19 +1048,26 @@ do -- COORDINATE
-- Coordinates.
RoutePoint.x = self.x
RoutePoint.y = self.z
-- Altitude.
RoutePoint.alt = self.y
RoutePoint.alt_type = AltType
-- Waypoint type.
RoutePoint.type = Type or nil
RoutePoint.action = Action or nil
-- Set speed/ETA.
-- Speed.
RoutePoint.speed = Speed/3.6
RoutePoint.speed_locked = SpeedLocked
-- ETA.
RoutePoint.ETA=nil
RoutePoint.ETA_locked = false
-- Waypoint description.
RoutePoint.name=description
-- Airbase parameters for takeoff and landing points.
if airbase then
local AirbaseID = airbase:GetID()
@ -1041,27 +1080,20 @@ do -- COORDINATE
else
self:T("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!")
end
--self:MarkToAll(string.format("Landing waypoint at airbase %s", airbase:GetName()))
end
-- ["task"] =
-- {
-- ["id"] = "ComboTask",
-- ["params"] =
-- {
-- ["tasks"] =
-- {
-- }, -- end of ["tasks"]
-- }, -- end of ["params"]
-- }, -- end of ["task"]
-- Waypoint tasks.
RoutePoint.task = {}
RoutePoint.task.id = "ComboTask"
RoutePoint.task.params = {}
RoutePoint.task.params.tasks = DCSTasks or {}
-- Debug.
self:T({RoutePoint=RoutePoint})
-- Return waypoint.
return RoutePoint
end
@ -1121,6 +1153,9 @@ do -- COORDINATE
--- Build a Waypoint Air "Landing".
-- @param #COORDINATE self
-- @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.
-- @usage
--
@ -1129,8 +1164,8 @@ do -- COORDINATE
-- LandingWaypoint = LandingCoord:WaypointAirLanding( 60 )
-- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second.
--
function COORDINATE:WaypointAirLanding( Speed )
return self:WaypointAir( nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed )
function COORDINATE:WaypointAirLanding( Speed, airbase, DCSTasks, description )
return self:WaypointAir(nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, nil, airbase, DCSTasks, description)
end

View File

@ -33,7 +33,7 @@
--
-- ===
--
-- ### Author: Hugues "Grey_Echo" Bousquet
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
--
-- @module Core.Radio
-- @image Core_Radio.JPG
@ -70,20 +70,21 @@
--
-- * 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,
-- * 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,
-- * 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.
--
-- @type RADIO
-- @field Positionable#POSITIONABLE Positionable The transmiter
-- @field #string FileName Name of the sound file
-- @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 #string Subtitle Subtitle of the transmission
-- @field #number SubtitleDuration Duration of the Subtitle in seconds
-- @field #number Power Power of the antenna is Watts
-- @field #boolean Loop (default true)
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
-- @field #string FileName Name of the sound file played.
-- @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 #string Subtitle Subtitle of the transmission.
-- @field #number SubtitleDuration Duration of the Subtitle in seconds.
-- @field #number Power Power of the antenna is Watts.
-- @field #boolean Loop Transmission is repeated (default true).
-- @field #string alias Name of the radio transmitter.
-- @extends Core.Base#BASE
RADIO = {
ClassName = "RADIO",
@ -93,19 +94,19 @@ RADIO = {
Subtitle = "",
SubtitleDuration = 0,
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
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead
--- 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.
-- @param #RADIO self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #RADIO Radio
-- @return #nil If Positionable is invalid
-- @return #RADIO The RADIO object or #nil if Positionable is invalid.
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)
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
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
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 #string FileName File name of the sound file (i.e. "Noise.ogg")
-- @return #RADIO self
@ -125,49 +142,63 @@ function RADIO:SetFileName(FileName)
self:F2(FileName)
if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName
end
self.FileName = FileName
return self
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
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 #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
function RADIO:SetFrequency(Frequency)
self:F2(Frequency)
if type(Frequency) == "number" then
-- If frequency is in range
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 self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
self.Positionable:SetCommand({
local commandSetFrequency={
id = "SetFrequency",
params = {
frequency = self.Frequency,
modulation = self.Modulation,
}
})
}
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end
return self
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
end
--- Check validity of the frequency passed and sets RADIO.Modulation
--- Set AM or FM modulation of the radio transmitter.
-- @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
function RADIO:SetModulation(Modulation)
self:F2(Modulation)
@ -183,23 +214,24 @@ end
--- Check validity of the power passed and sets RADIO.Power
-- @param #RADIO self
-- @param #number Power in W
-- @param #number Power Power in W.
-- @return #RADIO self
function RADIO:SetPower(Power)
self:F2(Power)
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
return self
end
else
self:E({"Power is invalid. Power unchanged.", self.Power})
end
return self
end
--- Check validity of the loop passed and sets RADIO.Loop
--- Set message looping on or off.
-- @param #RADIO self
-- @param #boolean Loop
-- @param #boolean Loop If true, message is repeated indefinitely.
-- @return #RADIO self
-- @usage
function RADIO:SetLoop(Loop)
self:F2(Loop)
if type(Loop) == "boolean" then
@ -232,24 +264,23 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
end
if type(SubtitleDuration) == "number" then
if math.floor(math.abs(SubtitleDuration)) == SubtitleDuration then
self.SubtitleDuration = SubtitleDuration
return self
end
end
else
self.SubtitleDuration = 0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
-- but it will work with a UNIT or a GROUP anyway.
-- Only the #RADIO and the Filename are mandatory
-- @param #RADIO self
-- @param #string FileName
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #number Power in W
-- @param #string FileName Name of the sound file that will be transmitted.
-- @param #number Frequency Frequency in MHz.
-- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
-- @param #number Power Power in W.
-- @return #RADIO self
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
self:F({FileName, Frequency, Modulation, Power})
@ -269,31 +300,43 @@ end
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self
-- @param #string FileName
-- @param #string Subtitle
-- @param #number SubtitleDuration in s
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #boolean Loop
-- @param #string FileName Name of sound file.
-- @param #string Subtitle Subtitle to be displayed with sound file.
-- @param #number SubtitleDuration Duration of subtitle display in seconds.
-- @param #number Frequency Frequency in MHz.
-- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
-- @param #boolean Loop If true, loop message.
-- @return #RADIO self
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
-- Set file name.
self:SetFileName(FileName)
local Duration = 5
if SubtitleDuration then Duration = SubtitleDuration end
-- SubtitleDuration argument was missing, adding it
if Subtitle then self:SetSubtitle(Subtitle, Duration) end
-- self:SetSubtitleDuration is non existent, removing faulty line
-- if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration) end
if Frequency then self:SetFrequency(Frequency) end
if Modulation then self:SetModulation(Modulation) end
if Loop then self:SetLoop(Loop) end
-- Set modulation AM/FM.
if Modulation then
self:SetModulation(Modulation)
end
-- Set frequency.
if Frequency then
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
end
--- Actually Broadcast the transmission
--- Broadcast the transmission.
-- * The Radio has to be populated with the new transmission before broadcasting.
-- * 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
@ -302,31 +345,38 @@ end
-- * 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
-- @param #RADIO self
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
-- @return #RADIO self
function RADIO:Broadcast()
self:F()
function RADIO:Broadcast(viatrigger)
self:F({viatrigger=viatrigger})
-- 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
self:T2("Broadcasting from a UNIT or a GROUP")
self.Positionable:SetCommand({
-- 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") and (not viatrigger) then
self:T("Broadcasting from a UNIT or a GROUP")
local commandTransmitMessage={
id = "TransmitMessage",
params = {
file = self.FileName,
duration = self.SubtitleDuration,
subtitle = self.Subtitle,
loop = self.Loop,
}
})
}}
self:T3(commandTransmitMessage)
self.Positionable:SetCommand(commandTransmitMessage)
else
-- 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
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))
end
return self
end
--- Stops a transmission
-- This function is especially usefull to stop the broadcast of looped transmissions
-- @param #RADIO self
@ -335,10 +385,10 @@ function RADIO:StopBroadcast()
self:F()
-- 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
self.Positionable:SetCommand({
id = "StopTransmission",
params = {}
})
local commandStopTransmission={id="StopTransmission", params={}}
self.Positionable:SetCommand(commandStopTransmission)
else
-- Else, we use the appropriate singleton funciton
trigger.action.stopRadioTransmission(tostring(self.ID))
@ -364,22 +414,86 @@ end
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
--
-- @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
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.
-- @param #BEACON self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #BEACON Beacon
-- @return #nil If Positionable is invalid
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
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)
-- Set positionable.
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
return self
@ -390,44 +504,95 @@ function BEACON:New(Positionable)
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
--- Activates a TACAN BEACON.
-- @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})
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @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
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
-- Get frequency.
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
-- 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
-- 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
-- Attached unit.
local UnitID=self.Positionable:GetID()
if TACANChannel < 64 then
B = 1
-- 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
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
return self
end
return (A + TACANChannel - B) * 1000000
--- 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.
-- @param #BEACON self
@ -591,4 +756,44 @@ function BEACON:StopRadioBeacon()
self:F()
-- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID))
return self
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
if NearestObject == nil then
NearestObject = ObjectData
ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetVec2() )
ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() )
else
local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetVec2() )
local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() )
if Distance < ClosestDistance then
NearestObject = ObjectData
ClosestDistance = Distance

View File

@ -2637,7 +2637,9 @@ function SPAWN:_OnEngineShutDown( EventData )
if Landed and self.RepeatOnEngineShutDown then
local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup )
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

View File

@ -195,6 +195,49 @@ function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
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}.
-- @param #SPAWNSTATIC self
-- @return #SPAWNSTATIC

View File

@ -70,7 +70,7 @@ do -- UserFlag
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- 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 )
end

View File

@ -118,15 +118,21 @@ do -- UserSound
--- Play the usersound to the given @{Wrapper.Group}.
-- @param #USERSOUND self
-- @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.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- 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.
--
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 )
end
return self
end

View File

@ -1398,16 +1398,15 @@ end
--- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self
-- @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
function ZONE_POLYGON_BASE:SmokeZone( SmokeColor )
function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments )
self:F2( SmokeColor )
local i
local j
local Segments = 10
Segments=Segments or 10
i = 1
j = #self._.Polygon
local i=1
local j=#self._.Polygon
while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
@ -1428,6 +1427,42 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor )
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.

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}().
-- 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.
-- This is done via the *weapontype* parameter of the @{#ARTY.AssignTargetCoord}(..., *weapontype*, ...) function.
@ -674,11 +674,13 @@ ARTY.id="ARTY | "
--- Arty script version.
-- @field #string version
ARTY.version="1.0.6"
ARTY.version="1.0.7"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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 entire target queue user function.
-- 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: remove schedulers for status event.
-- 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: Add set commands via markers. E.g. set rearming place.
-- DONE: Test stationary types like mortas ==> rearming etc.
-- TODO: Add hit event and make the arty group relocate.
-- DONE: Add illumination and smoke.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -2878,7 +2878,7 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target)
self.Controllable:ClearTasks()
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
-- Set number of shots to zero.
@ -4253,15 +4253,24 @@ end
-- @param #ARTY self
function ARTY:_CheckTargetsInRange()
local targets2delete={}
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(ARTY.id..string.format("Before: Target %s - in range = %s", _target.name, tostring(_target.inrange)))
-- 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)))
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.
local _movetowards=false
local _moveaway=false
@ -4346,10 +4355,16 @@ function ARTY:_CheckTargetsInRange()
_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
--- Check all normal (untimed) targets and return the target with the highest priority which has been engaged the fewest times.
-- @param #ARTY self
-- @return #table Target which is due to be attacked now or nil if no target could be found.
@ -4728,6 +4743,7 @@ end
-- @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 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)
self:F3(target)
@ -4763,11 +4779,13 @@ function ARTY:_TargetInRange(target, message)
end
-- 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
self:RemoveTarget(target.name)
--self:RemoveTarget(target.name)
_remove=true
end
return _inrange,_toofar,_tooclose
return _inrange,_toofar,_tooclose,_remove
end
--- 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.
-- @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 #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
function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay )
self:F2()

View File

@ -5435,7 +5435,7 @@ function RAT:_ATCInit(airports_map)
if not RAT.ATC.init then
local text
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
for _,ap in pairs(airports_map) do
local name=ap:GetName()
@ -5458,7 +5458,7 @@ end
-- @param #string name Group name of the flight.
-- @param #string dest Name of the destination airport.
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].destination=dest
RAT.ATC.flight[name].Tarrive=-1
@ -5483,7 +5483,7 @@ end
-- @param #string name Group name of the flight.
-- @param #number time Time the fight first registered.
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].holding=0
end
@ -5514,7 +5514,7 @@ function RAT:_ATCStatus()
-- Aircraft is holding.
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
@ -5522,7 +5522,7 @@ function RAT:_ATCStatus()
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)
self:T(RAT.id..text)
BASE:T(RAT.id..text)
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))
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
@ -5572,12 +5572,12 @@ function RAT:_ATCCheck()
-- 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)
self:T(RAT.id..text)
BASE:T(RAT.id..text)
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)
self:T(RAT.id..text)
BASE:T(RAT.id..text)
-- Clear flight for landing.
RAT:_ATCClearForLanding(name, flight)
@ -5706,11 +5706,6 @@ function RAT:_ATCQueue()
table.insert(RAT.ATC.airport[airport].queue, v[1])
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

View File

@ -276,7 +276,7 @@ RANGE.id="RANGE | "
--- Range script version.
-- @field #string version
RANGE.version="1.2.1"
RANGE.version="1.2.3"
--TODO list:
--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
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 Core.Point#COORDINATE coordinate Coordinate of the center of the range.
-- @param Core.Point#COORDINATE coordinate Coordinate of the range.
function RANGE:SetRangeLocation(coordinate)
self.location=coordinate
end
@ -471,7 +472,7 @@ end
-- If a zone is not explicitly specified, the range zone is determined by its location and radius.
-- @param #RANGE self
-- @param Core.Zone#ZONE zone MOOSE zone defining the range perimeters.
function RANGE:SetRangeLocation(zone)
function RANGE:SetRangeZone(zone)
self.rangezone=zone
end
@ -1163,11 +1164,19 @@ function RANGE:OnEventShot(EventData)
-- Coordinate of impact point.
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.
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.
if self.PlayerSettings[_playername].smokebombimpact and impactdist<self.rangeradius then
if self.PlayerSettings[_playername].smokebombimpact and insidezone then
if self.PlayerSettings[_playername].delaysmoke then
timer.scheduleFunction(self._DelayedSmoke, {coord=impactcoord, color=self.PlayerSettings[_playername].smokecolor}, timer.getTime() + self.TdelaySmoke)
else
@ -1185,6 +1194,8 @@ function RANGE:OnEventShot(EventData)
-- Distance between bomb and target.
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.
if _distance == nil or _temp < _distance then
_distance = _temp
@ -1203,7 +1214,7 @@ function RANGE:OnEventShot(EventData)
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
-- Init bomb player results.
@ -1222,10 +1233,10 @@ function RANGE:OnEventShot(EventData)
-- Send message.
self:_DisplayMessageToGroup(_unit, _message, nil, true)
elseif _distance <= self.rangeradius then
elseif insidezone then
-- Send message
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
--Terminate the timer

View File

@ -35,7 +35,7 @@
-- ===
--
-- @module Functional.Warehouse
-- @image MOOSE.JPG
-- @image Warehouse.JPG
--- WAREHOUSE class.
-- @type WAREHOUSE
@ -69,6 +69,8 @@
-- @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 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
--- 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
-- 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
-- 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.
-- In this case the event **Captured** is triggered which can be captured by the @{#WAREHOUSE.OnAfterCaptured} function.
@ -1555,6 +1558,8 @@ WAREHOUSE = {
autosave = false,
autosavepath = nil,
autosavefile = nil,
saveparking = false,
isunit = false,
}
--- 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.
-- @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 #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.
WAREHOUSE.db = {
AssetID = 0,
Assets = {},
WarehouseID = 0,
Warehouses = {}
}
--- Warehouse class version.
-- @field #string version
WAREHOUSE.version="0.6.4"
WAREHOUSE.version="0.6.7"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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: 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: Test capturing a neutral warehouse.
-- 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: 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: 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: Add possibility to set weight and cargo bay manually in AddAsset function as optional parameters.
-- 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.
-- @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
-- @return #WAREHOUSE self
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.
if type(warehouse)=="string" then
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
-- Nil check.
@ -1818,7 +1832,15 @@ function WAREHOUSE:New(warehouse, alias)
-- Set some variables.
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.
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("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("*", "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", "Defeated", "Running") -- Attack by other coalition was defeated!
self:AddTransition("*", "ChangeCountry", "*") -- Change country (and coalition) of the warehouse. Warehouse is respawned!
@ -2363,6 +2385,24 @@ function WAREHOUSE:SetReportOff()
return self
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.
-- @param #WAREHOUSE self
-- @param #number timeinterval Time interval in seconds.
@ -2875,6 +2915,7 @@ function WAREHOUSE:GetAssignment(request)
return tostring(request.assignment)
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.
-- @param #WAREHOUSE self
-- @param #string staticname Name of the warehouse static object.
@ -2884,6 +2925,7 @@ function WAREHOUSE:GetWarehouseID(staticname)
local uid=tonumber(warehouse:GetID())
return uid
end
]]
--- Find a warehouse in the global warehouse data base.
-- @param #WAREHOUSE self
@ -3531,12 +3573,12 @@ 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()))
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
end
end
-- Get the asset from the global DB.
@ -3588,6 +3630,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu
else
self:E(self.wid.."ERROR: Unknown group added as asset!")
self:E({unknowngroup=group})
end
-- Update status.
@ -4620,7 +4663,7 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country)
text=text..string.format("Deploying all %d ground assets.", nground)
-- 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
text=text..string.format("No ground assets currently available.")
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.
-- 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.
local np_departure=self.airbase:GetParkingSpotsNumber(termtype)
local np_destination=request.airbase:GetParkingSpotsNumber(termtype)
local np_departure=self.airbase:GetParkingSpotsNumber(termtype_dep)
local np_destination=request.airbase:GetParkingSpotsNumber(termtype_des)
-- 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.
--if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) 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
end
-- No parking at requesting warehouse.
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
end
@ -6452,7 +6496,7 @@ function WAREHOUSE:_CheckRequestValid(request)
self:T(text)
-- 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.
local np_departure=self.airbase:GetParkingSpotsNumber(termtype)
@ -6471,6 +6515,7 @@ function WAREHOUSE:_CheckRequestValid(request)
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
-- 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)
-- Debug info.
@ -6899,8 +6944,12 @@ function WAREHOUSE:_SimpleTaskFunction(Function, group)
local DCSScript = {}
--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 mystatic = STATIC:FindByName(\"%s\") ", warehouse) -- The static that holds the warehouse self object.
DCSScript[#DCSScript+1] = string.format('local warehouse = mystatic:GetState(mystatic, \"WAREHOUSE\") ') -- Get the warehouse self object from the static.
if self.isunit then
DCSScript[#DCSScript+1] = string.format("local mywarehouse = UNIT:FindByName(\"%s\") ", warehouse) -- The unit that holds the warehouse self object.
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.
@ -6912,13 +6961,13 @@ end
--- Get the proper terminal type based on generalized attribute of the group.
--@param #WAREHOUSE self
--@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit.
--@param #number _category Airbase category.
--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group.
function WAREHOUSE:_GetTerminal(_attribute)
function WAREHOUSE:_GetTerminal(_attribute, _category)
-- Default terminal is "large".
local _terminal=AIRBASE.TerminalType.OpenBig
if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER then
-- Fighter ==> small.
_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
-- Helicopter.
_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
return _terminal
@ -7002,20 +7060,6 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
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
-- Parking data for all assets.
@ -7026,7 +7070,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
local _asset=asset --#WAREHOUSE.Assetitem
-- Get terminal type of this asset
local terminaltype=self:_GetTerminal(asset.attribute)
local terminaltype=self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
-- Asset specific parking.
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))
-- Loop over all obstacles.
local free=true
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
-- 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,
}
--- 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.
-- @type UTILS
UTILS = {
@ -253,6 +266,10 @@ UTILS.KnotsToKmph = function(knots)
return knots * 1.852
end
UTILS.KmphToKnots = function(knots)
return knots / 1.852
end
UTILS.KmphToMps = function( kmph )
return kmph / 3.6
end
@ -281,7 +298,26 @@ UTILS.CelciusToFarenheit = function( Celcius )
return Celcius * 9/5 + 32
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:
in DM: decimal point of minutes.
@ -526,7 +562,7 @@ function UTILS.SecondsToClock(seconds)
-- Seconds of this day.
local _seconds=seconds%(60*60*24)
if seconds <= 0 then
if seconds<0 then
return nil
else
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.
-- @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)
-- Nil check.
@ -551,7 +587,7 @@ function UTILS.ClockToSeconds(clock)
local seconds=0
-- Split additional days.
local dsplit=UTILS.split(clock, "+")
local dsplit=UTILS.Split(clock, "+")
-- Convert days to seconds.
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}
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.Shiraz_International_Airport
-- * AIRBASE.PersianGulf.Kerman_Airport
-- * AIRBASE.PersianGulf.Jiroft_Airport
-- * AIRBASE.PersianGulf.Lavan_Island_Airport
-- @field PersianGulf
AIRBASE.PersianGulf = {
["Fujairah_Intl"] = "Fujairah Intl",
@ -259,6 +261,8 @@ AIRBASE.PersianGulf = {
["Sharjah_Intl"] = "Sharjah Intl",
["Shiraz_International_Airport"] = "Shiraz International Airport",
["Kerman_Airport"] = "Kerman Airport",
["Jiroft_Airport"] = "Jiroft Airport",
["Lavan_Island_Airport"] = "Lavan Island Airport",
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".

View File

@ -148,7 +148,7 @@
-- * @{#CONTROLLABLE.OptionROEReturnFirePossible}
-- * @{#CONTROLLABLE.OptionROEEvadeFirePossible}
--
-- ## 5.2) Rule on thread:
-- ## 5.2) Reaction On Thread:
--
-- * @{#CONTROLLABLE.OptionROTNoReaction}
-- * @{#CONTROLLABLE.OptionROTPassiveDefense}
@ -347,16 +347,26 @@ function CONTROLLABLE:PushTask( DCSTask, WaitTime )
local DCSControllable = self:GetDCSObject()
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.
-- Therefore we schedule the functions to set the mission and options for the Controllable.
-- Controller:pushTask( DCSTask )
if WaitTime then
self.TaskScheduler:Schedule( Controller, Controller.pushTask, { DCSTask }, WaitTime )
else
local function PushTask( Controller, DCSTask )
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
self.TaskScheduler:Schedule( self, PushTask, { DCSTask }, WaitTime )
end
return self
@ -367,7 +377,7 @@ end
--- Clearing the Task Queue and Setting the Task on the queue from the controllable.
-- @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.
-- @return Wrapper.Controllable#CONTROLLABLE self
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 DCS#Command DCSCommand
-- @param DCS#Command DCSCommand The command to be executed.
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetCommand( DCSCommand )
self:F2( DCSCommand )
@ -637,9 +647,122 @@ function CONTROLLABLE:StartUncontrolled(delay)
return self
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
--- (AIR) Attack a Controllable.
-- @param #CONTROLLABLE self
-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked.
@ -877,6 +1000,38 @@ function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed )
return DCSTask
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.
-- @param #CONTROLLABLE self
-- @param #number Altitude The altitude [m] to hold the position.
@ -965,11 +1120,7 @@ function CONTROLLABLE:TaskRefueling()
-- params = {}
-- }
local DCSTask
DCSTask = { id = 'Refueling',
params = {
},
},
local DCSTask={id='Refueling', params={}}
self:T3( { DCSTask } )
return DCSTask
@ -2109,7 +2260,7 @@ do -- Route methods
FromCoordinate = FromCoordinate or self:GetCoordinate()
-- 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.
local _,LengthRoad=FromCoordinate:GetPathOnRoad(ToCoordinate, false)
@ -2121,7 +2272,7 @@ do -- Route methods
-- Calculate the direct distance between the initial and final points.
local LengthDirect=FromCoordinate:Get2DDistance(ToCoordinate)
if PathOnRoad then
if GotPath then
-- Off road part of the rout: Total=OffRoad+OnRoad.
LengthOffRoad=LengthOnRoad-LengthRoad
@ -2144,7 +2295,7 @@ do -- Route methods
local canroad=false
-- 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.
if LongRoad and Shortcut then
@ -3073,6 +3224,3 @@ function CONTROLLABLE:IsAirPlane()
return nil
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.
-- To raise these events, provide the `GenerateEvent` parameter.
-- @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
-- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group.
-- Helicopter = GROUP:FindByName( "Helicopter" )
@ -1477,29 +1477,61 @@ end
--
-- @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 #boolean Reset Reset positons if TRUE.
-- @return Wrapper.Group#GROUP self
function GROUP:Respawn( Template, Reset )
if not Template then
Template = self:GetTemplate()
-- Given template or get old.
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
-- First check if group is alive.
if self:IsAlive() then
-- Respawn zone.
local Zone = self.InitRespawnZone -- Core.Zone#ZONE
-- Zone position or current group position.
local Vec3 = Zone and Zone:GetVec3() or self:GetVec3()
-- From point of the template.
local From = { x = Template.x, y = Template.y }
-- X, Y
Template.x = Vec3.x
Template.y = Vec3.z
--Template.x = nil
--Template.y = nil
-- Debug number of units.
self:F( #Template.units )
-- Reset position etc?
if Reset == true then
-- Loop over units in group.
for UnitID, UnitData in pairs( self:GetUnits() ) do
local GroupUnit = UnitData -- Wrapper.Unit#UNIT
self:F(GroupUnit:GetName())
if GroupUnit:IsAlive() then
self:F("Alive")
-- Get unit position vector.
local GroupUnitVec3 = GroupUnit:GetVec3()
-- Check if respawn zone is set.
if Zone then
if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3()
@ -1512,17 +1544,38 @@ function GROUP:Respawn( Template, Reset )
end
end
-- Altitude
Template.units[UnitID].alt = self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
-- Unit position. Why not simply take the current positon?
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.
Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading()
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] } )
end
end
else
else -- Reset=false or nil
-- Loop over template units.
for UnitID, TemplateUnitData in pairs( Template.units ) do
self:F( "Reset" )
-- Position from template.
local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y }
-- Respawn zone position.
if Zone then
if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3()
@ -1535,23 +1588,36 @@ function GROUP:Respawn( Template, Reset )
end
end
-- Set altitude.
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].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
-- Debug.
self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end
end
end
self:Destroy()
-- Destroy old group. Dont trigger any dead/crash events since this is a respawn.
self:Destroy(false)
self:T({Template=Template})
-- Spawn new group.
_DATABASE:Spawn(Template)
-- Reset events.
self:ResetEvents()
return self
end
@ -1652,6 +1718,7 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) --
-- Destroy old group.
self:Destroy(false)
-- Spawn new group.
_DATABASE:Spawn( SpawnTemplate )
-- Reset events.
@ -1800,8 +1867,8 @@ do -- Route methods
--
-- @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 #number Speed (optional) The Speed, if no Speed is given, the maximum Speed of the first unit is selected.
-- @return #GROUP
-- @param #number Speed (optional) The Speed, if no Speed is given, 80% of maximum Speed of the group is selected.
-- @return #GROUP self
function GROUP:RouteRTB( RTBAirbase, Speed )
self:F( { RTBAirbase:GetName(), Speed } )
@ -1811,9 +1878,12 @@ do -- Route methods
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 GroupVelocity = self:GetUnit(1):GetDesc().speedMax
local PointFrom = {}
PointFrom.x = GroupPoint.x
PointFrom.y = GroupPoint.y
@ -1821,7 +1891,6 @@ do -- Route methods
PointFrom.action = "Turning Point"
PointFrom.speed = GroupVelocity
local PointTo = {}
local AirbasePointVec2 = RTBAirbase:GetPointVec2()
local AirbaseAirPoint = AirbasePointVec2:WaypointAir(
@ -1832,21 +1901,42 @@ do -- Route methods
)
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)
-- Landing waypoint. More general than prev version since it should also work with FAPRS and ships.
local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed, RTBAirbase)
-- Waypoint table.
local Points={PointFrom, PointLanding}
--local Points={PointFrom, PointAirbase, PointLanding}
-- Debug info.
self:T3(Points)
-- Get group template.
local Template=self:GetTemplate()
Template.route.points = Points
self:Respawn( Template )
--self:Route( Points )
-- 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
-- Clear all tasks.
self:ClearTasks()
end
end

View File

@ -656,6 +656,14 @@ function POSITIONABLE:GetVelocityMPS()
return 0
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.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number Angle of attack in degrees.
@ -706,8 +714,8 @@ end
--- Returns the unit's climb or descent angle.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number Climb or descent angle in degrees.
function POSITIONABLE:GetClimbAnge()
-- @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:GetClimbAngle()
-- Get position of the unit.
local unitpos = self:GetPosition()
@ -719,10 +727,17 @@ function POSITIONABLE:GetClimbAnge()
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
return nil
end
--- Returns the pitch angle of a unit.

View File

@ -902,29 +902,31 @@ end
function UNIT:InAir()
self:F2( self.UnitName )
-- Get DCS unit object.
local DCSUnit = self:GetDCSObject() --DCS#Unit
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
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 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 LandHeight = land.getHeight( { x = Coordinate.x, y = Coordinate.z } )
local Height = Coordinate.y - LandHeight
if Velocity < 1 and Height <= 60 then
UnitInAir = false
end
else
UnitInAir = DCSUnit:inAir()
end
self:T3( UnitInAir )
return UnitInAir
end

View File

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