Merged 'develop'.

This commit is contained in:
FlightControl
2019-08-07 16:08:10 +02:00
18 changed files with 1421 additions and 368 deletions

View File

@@ -386,7 +386,7 @@ function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit )
if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement. if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement.
if AICap:IsAlive() then if AICap and AICap:IsAlive() then
local EngageRoute = {} local EngageRoute = {}
@@ -417,6 +417,8 @@ function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit )
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
if AttackUnit:IsAlive() and AttackUnit:IsAir() then if AttackUnit:IsAlive() and AttackUnit:IsAir() then
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
-- Maybe the detected set also contains
AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit ) AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit )
end end
end end

File diff suppressed because it is too large Load Diff

View File

@@ -257,6 +257,35 @@ function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self.PatrolCeilingAltitude = PatrolCeilingAltitude self.PatrolCeilingAltitude = PatrolCeilingAltitude
end end
--- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone.
-- @param #AI_A2A_PATROL self
-- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m.
-- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m.
-- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North.
-- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North.
-- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage.
-- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage.
-- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading.
-- @return #AI_A2A_PATROL self
function AI_A2A_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates)
self:F2({leglength, duration})
self.racetrack=true
self.racetracklegmin=LegMin or 10000
self.racetracklegmax=LegMax or 15000
self.racetrackheadingmin=HeadingMin or 0
self.racetrackheadingmax=HeadingMax or 180
self.racetrackdurationmin=DurationMin
self.racetrackdurationmax=DurationMax
if self.racetrackdurationmax and not self.racetrackdurationmin then
self.racetrackdurationmin=self.racetrackdurationmax
end
self.racetrackcapcoordinates=CapCoordinates
end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. --- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2A_PATROL self -- @param #AI_A2A_PATROL self
@@ -312,7 +341,7 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
end end
if AIPatrol:IsAlive() then if AIPatrol and AIPatrol:IsAlive() then
local PatrolRoute = {} local PatrolRoute = {}
@@ -320,31 +349,78 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
local CurrentCoord = AIPatrol:GetCoordinate() local CurrentCoord = AIPatrol:GetCoordinate()
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2() -- Random altitude.
ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) ) local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude)
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
-- Random speed in km/h.
local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed)
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) -- First waypoint is current position.
PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current")
--- Create a route point of type air. if self.racetrack then
local ToPatrolRoutePoint = ToTargetCoord:WaypointAir(
self.PatrolAltType, -- Random heading.
POINT_VEC3.RoutePointType.TurningPoint, local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed, -- Random leg length.
true local leg=math.random(self.racetracklegmin, self.racetracklegmax)
)
-- Random duration if any.
local duration = self.racetrackdurationmin
if self.racetrackdurationmax then
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
end
-- CAP coordinate.
local c0=self.PatrolZone:GetRandomCoordinate()
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
end
-- Race track points.
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
self:SetTargetDistance(c0) -- For RTB status check
-- Debug:
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
--c1:MarkToAll("Race track c1")
--c2:MarkToAll("Race track c2")
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint -- Task to orbit.
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
local Tasks = {} -- Task function to redo the patrol at other random position.
Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self ) local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self)
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
-- Controlled task with task condition.
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
-- Second waypoint
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
else
-- Target coordinate.
local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() --Core.Point#COORDINATE
ToTargetCoord:SetAltitude(altitude)
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
local taskReRoute=AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskReRoute}, "Patrol Point")
end
-- ROE
AIPatrol:OptionROEReturnFire() AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire() AIPatrol:OptionROTEvadeFire()
-- Patrol.
AIPatrol:Route( PatrolRoute, 0.5) AIPatrol:Route( PatrolRoute, 0.5)
end end

View File

@@ -517,7 +517,7 @@ end
-- local myUnit = UNIT:FindByName("MyUnit") -- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon -- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
-- --
-- myBeacon:TACAN(20, "Y", "TEXACO", true) -- Activate the beacon -- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})

View File

@@ -319,7 +319,7 @@ function SPAWN:New( SpawnTemplatePrefix )
self.SpawnUnControlled = false self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group. self.DelayOnOff = false -- No intial delay when spawning the first group.
self.Grouping = nil -- No grouping. self.SpawnGrouping = nil -- No grouping.
self.SpawnInitLivery = nil -- No special livery. self.SpawnInitLivery = nil -- No special livery.
self.SpawnInitSkill = nil -- No special skill. self.SpawnInitSkill = nil -- No special skill.
self.SpawnInitFreq = nil -- No special frequency. self.SpawnInitFreq = nil -- No special frequency.
@@ -371,7 +371,7 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
self.SpawnUnControlled = false self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group. self.DelayOnOff = false -- No intial delay when spawning the first group.
self.Grouping = nil -- No grouping. self.SpawnGrouping = nil -- No grouping.
self.SpawnInitLivery = nil -- No special livery. self.SpawnInitLivery = nil -- No special livery.
self.SpawnInitSkill = nil -- No special skill. self.SpawnInitSkill = nil -- No special skill.
self.SpawnInitFreq = nil -- No special frequency. self.SpawnInitFreq = nil -- No special frequency.
@@ -549,7 +549,7 @@ end
--- Sets the country of the spawn group. Note that the country determins the coalition of the group depending on which country is defined to be on which side for each specific mission! --- Sets the country of the spawn group. Note that the country determins the coalition of the group depending on which country is defined to be on which side for each specific mission!
-- @param #SPAWN self -- @param #SPAWN self
-- @param #DCS.country Country Country id as number or enumerator: -- @param #number Country Country id as number or enumerator:
-- --
-- * @{DCS#country.id.RUSSIA} -- * @{DCS#country.id.RUSSIA}
-- * @{DCS#county.id.USA} -- * @{DCS#county.id.USA}
@@ -1438,7 +1438,7 @@ end
-- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}.
-- @param #boolean EmergencyAirSpawn (optional) If true (default), groups are spawned in air if there is no parking spot at the airbase. If false, nothing is spawned if no parking spot is available. -- @param #boolean EmergencyAirSpawn (optional) If true (default), groups are spawned in air if there is no parking spot at the airbase. If false, nothing is spawned if no parking spot is available.
-- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots! -- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots!
-- @return Wrapper.Group#GROUP that was spawned or nil when nothing was spawned. -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned.
-- @usage -- @usage
-- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane = SPAWN:New( "Plane" )
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold )
@@ -1498,11 +1498,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix)
local TemplateUnit=TemplateGroup:GetUnit(1) local TemplateUnit=TemplateGroup:GetUnit(1)
--local ishelo=TemplateUnit:HasAttribute("Helicopters") -- General category of spawned group.
--local isbomber=TemplateUnit:HasAttribute("Bombers")
--local istransport=TemplateUnit:HasAttribute("Transports")
--local isfighter=TemplateUnit:HasAttribute("Battleplanes")
local group=TemplateGroup local group=TemplateGroup
local istransport=group:HasAttribute("Transports") and group:HasAttribute("Planes") local istransport=group:HasAttribute("Transports") and group:HasAttribute("Planes")
local isawacs=group:HasAttribute("AWACS") local isawacs=group:HasAttribute("AWACS")
@@ -1577,8 +1573,17 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
-- Set terminal type. -- Set terminal type.
local termtype=TerminalType local termtype=TerminalType
if spawnonrunway then if spawnonrunway then
termtype=AIRBASE.TerminalType.Runway if spawnonship then
-- Looks like there are no runway spawn spots on the stennis!
if ishelo then
termtype=AIRBASE.TerminalType.HelicopterUsable
else
termtype=AIRBASE.TerminalType.OpenMedOrBig
end
else
termtype=AIRBASE.TerminalType.Runway
end
end end
-- Scan options. Might make that input somehow. -- Scan options. Might make that input somehow.
@@ -1647,9 +1652,9 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
-- Get parking data. -- Get parking data.
local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype)
self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) self:T(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype)))
for _,_spot in pairs(parkingdata) do for _,_spot in pairs(parkingdata) do
self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", self:T(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d",
SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy))
end end
self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits))
@@ -1802,8 +1807,8 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
end end
-- Debug output. -- Debug output.
self:T2(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) self:T(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking)))
self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) self:T(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id)))
self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end end
end end
@@ -1816,7 +1821,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
SpawnTemplate.x = PointVec3.x SpawnTemplate.x = PointVec3.x
SpawnTemplate.y = PointVec3.z SpawnTemplate.y = PointVec3.z
SpawnTemplate.uncontrolled = nil SpawnTemplate.uncontrolled = self.SpawnUnControlled
-- Spawn group. -- Spawn group.
local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex ) local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex )
@@ -1840,6 +1845,66 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
return nil return nil
end end
--- Spawn a group on an @{Wrapper.Airbase} at a specific parking spot.
-- @param #SPAWN self
-- @param Wrapper.Airbase#AIRBASE Airbase The @{Wrapper.Airbase} where to spawn the group.
-- @param #table Spots Table of parking spot IDs. Note that these in general are different from the numbering in the mission editor!
-- @param #SPAWN.Takeoff Takeoff (Optional) Takeoff type, i.e. either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is Hot.
-- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned.
function SPAWN:SpawnAtParkingSpot(Airbase, Spots, Takeoff) -- R2.5
self:F({Airbase=Airbase, Spots=Spots, Takeoff=Takeoff})
-- Ensure that Spots parameter is a table.
if type(Spots)~="table" then
Spots={Spots}
end
-- Get template group.
local group=GROUP:FindByName(self.SpawnTemplatePrefix)
-- Get number of units in group.
local nunits=self.SpawnGrouping or #group:GetUnits()
-- Quick check.
if nunits then
-- Check that number of provided parking spots is large enough.
if #Spots<nunits then
self:E("ERROR: Number of provided parking spots is less than number of units in group!")
return nil
end
-- Table of parking data.
local Parkingdata={}
-- Loop over provided Terminal IDs.
for _,TerminalID in pairs(Spots) do
-- Get parking spot data.
local spot=Airbase:GetParkingSpotData(TerminalID)
self:T2({spot=spot})
if spot and spot.Free then
self:T(string.format("Adding parking spot ID=%d TermType=%d", spot.TerminalID, spot.TerminalType))
table.insert(Parkingdata, spot)
end
end
if #Parkingdata>=nunits then
return self:SpawnAtAirbase(Airbase, Takeoff, nil, nil, nil, Parkingdata)
else
self:E("ERROR: Could not find enough free parking spots!")
end
else
self:E("ERROR: Could not get number of units in group!")
end
return nil
end
--- Will park a group at an @{Wrapper.Airbase}. --- Will park a group at an @{Wrapper.Airbase}.
-- --
@@ -3024,6 +3089,9 @@ function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, Spa
end end
--- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces. --- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces.
-- @param #SPAWN self
-- @param #number SpawnIndex Spawn index.
-- @return #number self.SpawnIndex
function SPAWN:_GetSpawnIndex( SpawnIndex ) function SPAWN:_GetSpawnIndex( SpawnIndex )
self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } )

View File

@@ -442,6 +442,33 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius )
return self return self
end end
--- Mark the zone with markers on the F10 map.
-- @param #ZONE_RADIUS self
-- @param #number Points (Optional) The amount of points in the circle. Default 360.
-- @return #ZONE_RADIUS self
function ZONE_RADIUS:MarkZone(Points)
local Point = {}
local Vec2 = self:GetVec2()
Points = Points and Points or 360
local Angle
local RadialBase = math.pi*2
for Angle = 0, 360, (360 / Points ) do
local Radial = Angle * RadialBase / 360
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName())
end
end
--- Bounds the zone with tires. --- Bounds the zone with tires.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param #number Points (optional) The amount of points in the circle. Default 360. -- @param #number Points (optional) The amount of points in the circle. Default 360.

View File

@@ -291,17 +291,34 @@ do -- DETECTION_BASE
--- @type DETECTION_BASE.DetectedItems --- @type DETECTION_BASE.DetectedItems
-- @list <#DETECTION_BASE.DetectedItem> -- @list <#DETECTION_BASE.DetectedItem>
--- @type DETECTION_BASE.DetectedItem --- Detected item data structrue.
-- @type DETECTION_BASE.DetectedItem
-- @field #boolean IsDetected Indicates if the DetectedItem has been detected or not. -- @field #boolean IsDetected Indicates if the DetectedItem has been detected or not.
-- @field Core.Set#SET_UNIT Set -- @field Core.Set#SET_UNIT Set The Set of Units in the detected area.
-- @field Core.Set#SET_UNIT Set -- The Set of Units in the detected area. -- @field Core.Zone#ZONE_UNIT Zone The Zone of the detected area.
-- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. -- @field #boolean Changed Documents if the detected area has changed.
-- @field #boolean Changed Documents if the detected area has changes.
-- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). -- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes).
-- @field #number ID -- The identifier of the detected area. -- @field #number ID The identifier of the detected area.
-- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area.
-- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. -- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area.
-- @field Core.Point#COORDINATE Coordinate The last known coordinate of the DetectedItem. -- @field Core.Point#COORDINATE Coordinate The last known coordinate of the DetectedItem.
-- @field Core.Point#COORDINATE InterceptCoord Intercept coordiante.
-- @field #number DistanceRecce Distance in meters of the Recce.
-- @field #number Index Detected item key. Could also be a string.
-- @field #string ItemID ItemPrefix .. "." .. self.DetectedItemMax.
-- @field #boolean Locked Lock detected item.
-- @field #table PlayersNearBy Table of nearby players.
-- @field #table FriendliesDistance Table of distances to friendly units.
-- @field #string TypeName Type name of the detected unit.
-- @field #string CategoryName Catetory name of the detected unit.
-- @field #string Name Name of the detected object.
-- @field #boolean IsVisible If true, detected object is visible.
-- @field #number LastTime Last time the detected item was seen.
-- @field DCS#Vec3 LastPos Last known position of the detected item.
-- @field DCS#Vec3 LastVelocity Last recorded 3D velocity vector of the detected item.
-- @field #boolean KnowType Type of detected item is known.
-- @field #boolean KnowDistance Distance to the detected item is known.
-- @field #number Distance Distance to the detected item.
--- DETECTION constructor. --- DETECTION constructor.
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
@@ -441,15 +458,18 @@ do -- DETECTION_BASE
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event string. -- @param #string Event The Event string.
-- @param #string To The To State string. -- @param #string To The To State string.
-- @param #table Units Table of detected units.
--- Synchronous Event Trigger for Event Detected. --- Synchronous Event Trigger for Event Detected.
-- @function [parent=#DETECTION_BASE] Detected -- @function [parent=#DETECTION_BASE] Detected
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #table Units Table of detected units.
--- Asynchronous Event Trigger for Event Detected. --- Asynchronous Event Trigger for Event Detected.
-- @function [parent=#DETECTION_BASE] __Detected -- @function [parent=#DETECTION_BASE] __Detected
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #number Delay The delay in seconds. -- @param #number Delay The delay in seconds.
-- @param #table Units Table of detected units.
self:AddTransition( "Detecting", "DetectedItem", "Detecting" ) self:AddTransition( "Detecting", "DetectedItem", "Detecting" )
@@ -569,7 +589,7 @@ do -- DETECTION_BASE
local HasDetectedObjects = false local HasDetectedObjects = false
if Detection:IsAlive() then if Detection and Detection:IsAlive() then
--self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) --self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } )
@@ -800,12 +820,17 @@ do -- DETECTION_BASE
end end
self:CreateDetectionItems() -- Polymorphic call to Create/Update the DetectionItems list for the DETECTION_ class grouping method. self:CreateDetectionItems() -- Polymorphic call to Create/Update the DetectionItems list for the DETECTION_ class grouping method.
for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do
self:UpdateDetectedItemDetection( DetectedItem ) self:UpdateDetectedItemDetection( DetectedItem )
self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list. self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list.
if DetectedItem then if DetectedItem then
self:__DetectedItem( 0.1, DetectedItem ) self:__DetectedItem( 0.1, DetectedItem )
end end
end end
end end
@@ -817,7 +842,7 @@ do -- DETECTION_BASE
do -- DetectionItems Creation do -- DetectionItems Creation
-- Clean the DetectedItem table. --- Clean the DetectedItem table.
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @return #DETECTION_BASE -- @return #DETECTION_BASE
function DETECTION_BASE:CleanDetectionItem( DetectedItem, DetectedItemID ) function DETECTION_BASE:CleanDetectionItem( DetectedItem, DetectedItemID )
@@ -1234,7 +1259,7 @@ do -- DETECTION_BASE
--- Returns if there are friendlies nearby the FAC units ... --- Returns if there are friendlies nearby the FAC units ...
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param DetectedItem -- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @param DCS#Unit.Category Category The category of the unit. -- @param DCS#Unit.Category Category The category of the unit.
-- @return #boolean true if there are friendlies nearby -- @return #boolean true if there are friendlies nearby
function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category ) function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category )
@@ -1244,7 +1269,7 @@ do -- DETECTION_BASE
--- Returns friendly units nearby the FAC units ... --- Returns friendly units nearby the FAC units ...
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param DetectedItem -- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @param DCS#Unit.Category Category The category of the unit. -- @param DCS#Unit.Category Category The category of the unit.
-- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs.
function DETECTION_BASE:GetFriendliesNearBy( DetectedItem, Category ) function DETECTION_BASE:GetFriendliesNearBy( DetectedItem, Category )
@@ -1254,6 +1279,7 @@ do -- DETECTION_BASE
--- Returns if there are friendlies nearby the intercept ... --- Returns if there are friendlies nearby the intercept ...
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @return #boolean trhe if there are friendlies near the intercept. -- @return #boolean trhe if there are friendlies near the intercept.
function DETECTION_BASE:IsFriendliesNearIntercept( DetectedItem ) function DETECTION_BASE:IsFriendliesNearIntercept( DetectedItem )
@@ -1262,6 +1288,7 @@ do -- DETECTION_BASE
--- Returns friendly units nearby the intercept point ... --- Returns friendly units nearby the intercept point ...
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs.
function DETECTION_BASE:GetFriendliesNearIntercept( DetectedItem ) function DETECTION_BASE:GetFriendliesNearIntercept( DetectedItem )
@@ -1270,7 +1297,8 @@ do -- DETECTION_BASE
--- Returns the distance used to identify friendlies near the deteted item ... --- Returns the distance used to identify friendlies near the deteted item ...
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @return #number The distance. -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return #table A table of distances to friendlies.
function DETECTION_BASE:GetFriendliesDistance( DetectedItem ) function DETECTION_BASE:GetFriendliesDistance( DetectedItem )
return DetectedItem.FriendliesDistance return DetectedItem.FriendliesDistance
@@ -1278,6 +1306,7 @@ do -- DETECTION_BASE
--- Returns if there are friendlies nearby the FAC units ... --- Returns if there are friendlies nearby the FAC units ...
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @return #boolean trhe if there are friendlies nearby -- @return #boolean trhe if there are friendlies nearby
function DETECTION_BASE:IsPlayersNearBy( DetectedItem ) function DETECTION_BASE:IsPlayersNearBy( DetectedItem )
@@ -1286,6 +1315,7 @@ do -- DETECTION_BASE
--- Returns friendly units nearby the FAC units ... --- Returns friendly units nearby the FAC units ...
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item.
-- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs.
function DETECTION_BASE:GetPlayersNearBy( DetectedItem ) function DETECTION_BASE:GetPlayersNearBy( DetectedItem )
@@ -1294,10 +1324,11 @@ do -- DETECTION_BASE
--- Background worker function to determine if there are friendlies nearby ... --- Background worker function to determine if there are friendlies nearby ...
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #table TargetData
function DETECTION_BASE:ReportFriendliesNearBy( TargetData ) function DETECTION_BASE:ReportFriendliesNearBy( TargetData )
--self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } ) --self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } )
local DetectedItem = TargetData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedItem = TargetData.DetectedItem --#DETECTION_BASE.DetectedItem
local DetectedSet = TargetData.DetectedItem.Set local DetectedSet = TargetData.DetectedItem.Set
local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT
@@ -1519,13 +1550,13 @@ do -- DETECTION_BASE
--- Adds a new DetectedItem to the DetectedItems list. --- Adds a new DetectedItem to the DetectedItems list.
-- The DetectedItem is a table and contains a SET_UNIT in the field Set. -- The DetectedItem is a table and contains a SET_UNIT in the field Set.
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param ItemPrefix -- @param #string ItemPrefix Prefix of detected item.
-- @param DetectedItemKey The key of the DetectedItem. -- @param #number DetectedItemKey The key of the DetectedItem. Default self.DetectedItemMax. Could also be a string in principle.
-- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added. -- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added.
-- @return #DETECTION_BASE.DetectedItem -- @return #DETECTION_BASE.DetectedItem
function DETECTION_BASE:AddDetectedItem( ItemPrefix, DetectedItemKey, Set ) function DETECTION_BASE:AddDetectedItem( ItemPrefix, DetectedItemKey, Set )
local DetectedItem = {} local DetectedItem = {} --#DETECTION_BASE.DetectedItem
self.DetectedItemCount = self.DetectedItemCount + 1 self.DetectedItemCount = self.DetectedItemCount + 1
self.DetectedItemMax = self.DetectedItemMax + 1 self.DetectedItemMax = self.DetectedItemMax + 1
@@ -1706,6 +1737,7 @@ do -- DETECTION_BASE
--- Checks if there is at least one UNIT detected in the Set of the the DetectedItem. --- Checks if there is at least one UNIT detected in the Set of the the DetectedItem.
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem
-- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet. -- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet.
function DETECTION_BASE:IsDetectedItemDetected( DetectedItem ) function DETECTION_BASE:IsDetectedItemDetected( DetectedItem )
@@ -1832,8 +1864,7 @@ do -- DETECTION_BASE
--- Get a list of the detected item coordinates. --- Get a list of the detected item coordinates.
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem to set the coordinate at. -- @return #table A table of Core.Point#COORDINATE
-- @return Core.Point#COORDINATE
function DETECTION_BASE:GetDetectedItemCoordinates() function DETECTION_BASE:GetDetectedItemCoordinates()
local Coordinates = {} local Coordinates = {}
@@ -2033,7 +2064,8 @@ do -- DETECTION_UNITS
function DETECTION_UNITS:CreateDetectionItems() function DETECTION_UNITS:CreateDetectionItems()
-- Loop the current detected items, and check if each object still exists and is detected. -- Loop the current detected items, and check if each object still exists and is detected.
for DetectedItemKey, DetectedItem in pairs( self.DetectedItems ) do for DetectedItemKey, _DetectedItem in pairs( self.DetectedItems ) do
local DetectedItem=_DetectedItem --#DETECTION_BASE.DetectedItem
local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT

View File

@@ -40,7 +40,9 @@
-- @field #table launchzones Table of launch zones. -- @field #table launchzones Table of launch zones.
-- @field Core.Set#SET_GROUP protectedset Set of protected groups. -- @field Core.Set#SET_GROUP protectedset Set of protected groups.
-- @field #number explosionpower Power of explostion when destroying the missile in kg TNT. Default 5 kg TNT. -- @field #number explosionpower Power of explostion when destroying the missile in kg TNT. Default 5 kg TNT.
-- @field #number explosiondist Missile player distance in meters for destroying the missile. Default 100 m. -- @field #number explosiondist Missile player distance in meters for destroying smaller missiles. Default 200 m.
-- @field #number explosiondist2 Missile player distance in meters for destroying big missiles. Default 500 m.
-- @field #number bigmissilemass Explosion power of big missiles. Default 50 kg TNT. Big missiles will be destroyed earlier.
-- @field #number dt50 Time step [sec] for missile position updates if distance to target > 50 km. Default 5 sec. -- @field #number dt50 Time step [sec] for missile position updates if distance to target > 50 km. Default 5 sec.
-- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec. -- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec.
-- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec. -- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec.
@@ -136,8 +138,10 @@ FOX = {
safezones = {}, safezones = {},
launchzones = {}, launchzones = {},
protectedset = nil, protectedset = nil,
explosionpower = 5, explosionpower = 0.1,
explosiondist = 100, explosiondist = 200,
explosiondist2 = 500,
bigmissilemass = 50,
destroy = nil, destroy = nil,
dt50 = 5, dt50 = 5,
dt10 = 1, dt10 = 1,
@@ -169,14 +173,19 @@ FOX = {
-- @field Wrapper.Unit#UNIT weapon Missile weapon unit. -- @field Wrapper.Unit#UNIT weapon Missile weapon unit.
-- @field #boolean active If true the missile is active. -- @field #boolean active If true the missile is active.
-- @field #string missileType Type of missile. -- @field #string missileType Type of missile.
-- @field #string missileName Name of missile.
-- @field #number missileRange Range of missile in meters. -- @field #number missileRange Range of missile in meters.
-- @field #number fuseDist Fuse distance in meters.
-- @field #number explosive Explosive mass in kg TNT.
-- @field Wrapper.Unit#UNIT shooterUnit Unit that shot the missile. -- @field Wrapper.Unit#UNIT shooterUnit Unit that shot the missile.
-- @field Wrapper.Group#GROUP shooterGroup Group that shot the missile. -- @field Wrapper.Group#GROUP shooterGroup Group that shot the missile.
-- @field #number shooterCoalition Coalition side of the shooter. -- @field #number shooterCoalition Coalition side of the shooter.
-- @field #string shooterName Name of the shooter unit. -- @field #string shooterName Name of the shooter unit.
-- @field #number shotTime Abs mission time in seconds the missile was fired. -- @field #number shotTime Abs. mission time in seconds the missile was fired.
-- @field Core.Point#COORDINATE shotCoord Coordinate where the missile was fired. -- @field Core.Point#COORDINATE shotCoord Coordinate where the missile was fired.
-- @field Wrapper.Unit#UNIT targetUnit Unit that was targeted. -- @field Wrapper.Unit#UNIT targetUnit Unit that was targeted.
-- @field #string targetName Name of the target unit or "unknown".
-- @field #string targetOrig Name of the "original" target, i.e. the one right after launched.
-- @field #FOX.PlayerData targetPlayer Player that was targeted or nil. -- @field #FOX.PlayerData targetPlayer Player that was targeted or nil.
--- Main radio menu on group level. --- Main radio menu on group level.
@@ -189,7 +198,7 @@ FOX.MenuF10Root=nil
--- FOX class version. --- FOX class version.
-- @field #string version -- @field #string version
FOX.version="0.5.1" FOX.version="0.6.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -217,6 +226,10 @@ function FOX:New()
self:SetDefaultMissileDestruction(true) self:SetDefaultMissileDestruction(true)
self:SetDefaultLaunchAlerts(true) self:SetDefaultLaunchAlerts(true)
self:SetDefaultLaunchMarks(true) self:SetDefaultLaunchMarks(true)
-- Explosion/destruction defaults.
self:SetExplosionDistance()
self:SetExplosionDistanceBigMissiles()
self:SetExplosionPower() self:SetExplosionPower()
-- Start State. -- Start State.
@@ -358,12 +371,16 @@ function FOX:onafterStart(From, Event, To)
self:HandleEvent(EVENTS.Birth) self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Shot) self:HandleEvent(EVENTS.Shot)
if self.Debug then
self:HandleEvent(EVENTS.Hit)
end
if self.Debug then if self.Debug then
self:TraceClass(self.ClassName) self:TraceClass(self.ClassName)
self:TraceLevel(2) self:TraceLevel(2)
end end
self:__Status(-10) self:__Status(-20)
end end
--- On after Stop event. Stops the missile trainer and unhandles events. --- On after Stop event. Stops the missile trainer and unhandles events.
@@ -378,8 +395,12 @@ function FOX:onafterStop(From, Event, To)
env.info(text) env.info(text)
-- Handle events: -- Handle events:
self:UnhandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.Birth)
self:UnhandleEvent(EVENTS.Shot) self:UnHandleEvent(EVENTS.Shot)
if self.Debug then
self:UnhandleEvent(EVENTS.Hit)
end
end end
@@ -433,28 +454,42 @@ function FOX:AddProtectedGroup(group)
return self return self
end end
--- Set explosion power. --- Set explosion power. This is an "artificial" explosion generated when the missile is destroyed. Just for the visual effect.
-- Don't set the explosion power too big or it will harm the aircraft in the vicinity.
-- @param #FOX self -- @param #FOX self
-- @param #number power Explosion power in kg TNT. Default 5. -- @param #number power Explosion power in kg TNT. Default 0.1 kg.
-- @return #FOX self -- @return #FOX self
function FOX:SetExplosionPower(power) function FOX:SetExplosionPower(power)
self.explosionpower=power or 5 self.explosionpower=power or 0.1
return self return self
end end
--- Set missile-player distance when missile is destroyed. --- Set missile-player distance when missile is destroyed.
-- @param #FOX self -- @param #FOX self
-- @param #number distance Distance in meters. Default 100 m. -- @param #number distance Distance in meters. Default 200 m.
-- @return #FOX self -- @return #FOX self
function FOX:SetExplosionDistance(distance) function FOX:SetExplosionDistance(distance)
self.explosiondist=distance or 100 self.explosiondist=distance or 200
return self return self
end end
--- Set missile-player distance when BIG missiles are destroyed.
-- @param #FOX self
-- @param #number distance Distance in meters. Default 500 m.
-- @param #number explosivemass Explosive mass of missile threshold in kg TNT. Default 50 kg.
-- @return #FOX self
function FOX:SetExplosionDistanceBigMissiles(distance, explosivemass)
self.explosiondist2=distance or 500
self.bigmissilemass=explosivemass or 50
return self
end
--- Disable F10 menu for all players. --- Disable F10 menu for all players.
-- @param #FOX self -- @param #FOX self
@@ -558,8 +593,11 @@ function FOX:onafterStatus(From, Event, To)
-- Get FSM state. -- Get FSM state.
local fsmstate=self:GetState() local fsmstate=self:GetState()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time)
-- Status. -- Status.
self:I(self.lid..string.format("Missile trainer status: %s", fsmstate)) self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate))
-- Check missile status. -- Check missile status.
self:_CheckMissileStatus() self:_CheckMissileStatus()
@@ -664,6 +702,9 @@ function FOX:_CheckMissileStatus()
text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s", i, mtype, active, range, heading, targetname, playername, missile.missileName) text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s", i, mtype, active, range, heading, targetname, playername, missile.missileName)
end end
if #self.missiles==0 then
text=text.." none"
end
self:I(self.lid..text) self:I(self.lid..text)
-- Remove inactive missiles. -- Remove inactive missiles.
@@ -722,7 +763,9 @@ end
function FOX:onafterMissileLaunch(From, Event, To, missile) function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Tracking info and init of last bomb position. -- Tracking info and init of last bomb position.
self:I(FOX.lid..string.format("FOX: Tracking %s - %s.", missile.missileType, missile.missileName)) local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName)
self:I(FOX.lid..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Loop over players. -- Loop over players.
for _,_player in pairs(self.players) do for _,_player in pairs(self.players) do
@@ -803,6 +846,9 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Missile velocity in m/s. -- Missile velocity in m/s.
local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity()) local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity())
-- Update missile target if necessary.
self:GetMissileTarget(missile)
if missile.targetUnit then if missile.targetUnit then
----------------------------------- -----------------------------------
@@ -825,7 +871,30 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
------------------------------------ ------------------------------------
-- Missile has NO specific target -- -- Missile has NO specific target --
------------------------------------ ------------------------------------
-- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not.
-- That would trigger the distance check right after missile launch if things to wrong.
--
-- Possible solutions:
-- * Time check: enable this check after X seconds after missile was fired. What is X?
-- * Coalition check. But would not work in training situations where blue on blue is valid!
-- * At least enable it for surface-to-air missiles.
local function _GetTarget(_unit)
local unit=_unit --Wrapper.Unit#UNIT
-- Player position.
local playerCoord=unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if dist<=self.explosiondist then
return unit
end
end
-- Distance to closest player. -- Distance to closest player.
local mindist=nil local mindist=nil
@@ -843,17 +912,57 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Distance. -- Distance.
local dist=missileCoord:Get3DDistance(playerCoord) local dist=missileCoord:Get3DDistance(playerCoord)
-- Maxrange from launch point to player. -- Distance from shooter to player.
local maxrange=playerCoord:Get3DDistance(missile.shotCoord) local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
-- Update mindist if necessary. Only include players in range of missile. -- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and dist<=maxrange then if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
mindist=dist mindist=dist
target=player.unit target=player.unit
end end
end end
end end
if self.protectedset then
-- Distance to closest protected unit.
mindist=nil
for _,_group in pairs(self.protectedset:GetSet()) do
local group=_group --Wrapper.Group#GROUP
for _,_unit in pairs(group:GetUnits()) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
-- Check that player was not the one who launched the missile.
if unit:GetName()~=missile.shooterName then
-- Player position.
local playerCoord=unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Distance from shooter to player.
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
mindist=dist
target=unit
end
end
end
end
end
end
if target then
self:I(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m", missile.missileType, target:GetName(), tostring(mindist)))
end
end end
-- Check if missile has a valid target. -- Check if missile has a valid target.
@@ -865,39 +974,69 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Distance from missile to target. -- Distance from missile to target.
local distance=missileCoord:Get3DDistance(targetCoord) local distance=missileCoord:Get3DDistance(targetCoord)
local bearing=targetCoord:HeadingTo(missileCoord) -- Distance missile to shooter.
local eta=distance/missileVelocity local distShooter=nil
if missile.shooterUnit and missile.shooterUnit:IsAlive() then
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetCoordinate())
end
self:T2(self.lid..string.format("Distance = %.1f m, v=%.1f m/s, bearing=%03d°, eta=%.1f sec", distance, missileVelocity, bearing, eta))
-- Debug output.
if self.Debug then
local bearing=targetCoord:HeadingTo(missileCoord)
local eta=distance/missileVelocity
-- Debug distance check.
self:I(self.lid..string.format("Missile %s Target %s: Distance = %.1f m, v=%.1f m/s, bearing=%03d°, ETA=%.1f sec", missile.missileType, target:GetName(), distance, missileVelocity, bearing, eta))
end
-- Distroy missile if it's getting too close.
local destroymissile=distance<=self.explosiondist
-- Check BIG missiles.
if self.explosiondist2 and distance<=self.explosiondist2 and not destroymissile then
destroymissile=missile.explosive>=self.bigmissilemass
end
-- If missile is 100 m from target ==> destroy missile if in safe zone. -- If missile is 150 m from target ==> destroy missile if in safe zone.
if distance<=self.explosiondist and self:_CheckCoordSafe(targetCoord)then if destroymissile and self:_CheckCoordSafe(targetCoord) then
-- Destroy missile. -- Destroy missile.
self:T(self.lid..string.format("Destroying missile at distance %.1f m", distance)) self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m",
missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance))
_ordnance:destroy() _ordnance:destroy()
-- Missile is not active any more. -- Missile is not active any more.
missile.active=false missile.active=false
-- Debug smoke.
if self.Debug then
missileCoord:SmokeRed()
targetCoord:SmokeGreen()
end
-- Create event. -- Create event.
self:MissileDestroyed(missile) self:MissileDestroyed(missile)
-- Little explosion for the visual effect. -- Little explosion for the visual effect.
if self.explosionpower>0 then if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then
missileCoord:Explosion(self.explosionpower) missileCoord:Explosion(self.explosionpower)
end end
local text=string.format("Destroying missile. %s", self:_DeadText()) -- Target was a player.
MESSAGE:New(text, 10):ToGroup(target:GetGroup())
-- Increase dead counter.
if missile.targetPlayer then if missile.targetPlayer then
-- Message to target.
local text=string.format("Destroying missile. %s", self:_DeadText())
MESSAGE:New(text, 10):ToGroup(target:GetGroup())
-- Increase dead counter.
missile.targetPlayer.dead=missile.targetPlayer.dead+1 missile.targetPlayer.dead=missile.targetPlayer.dead+1
end end
-- Terminate timer. -- Terminate timer.
return nil return nil
else else
-- Time step. -- Time step.
@@ -922,10 +1061,15 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Check again in dt seconds. -- Check again in dt seconds.
return timer.getTime()+dt return timer.getTime()+dt
end end
else else
-- Destroy missile.
self:I(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName))
return timer.getTime()+0.1
-- No target ==> terminate timer. -- No target ==> terminate timer.
return nil --return nil
end end
else else
@@ -945,7 +1089,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
MESSAGE:New(text, 10):ToClient(player.client) MESSAGE:New(text, 10):ToClient(player.client)
-- Increase defeated counter. -- Increase defeated counter.
player.defeated=player.defeated+1 player.defeated=player.defeated+1
end end
end end
@@ -1041,11 +1185,52 @@ function FOX:OnEventBirth(EventData)
end end
end end
--- Get missile target.
-- @param #FOX self
-- @param #FOX.MissileData missile The missile data table.
function FOX:GetMissileTarget(missile)
local target=nil
local targetName="unknown"
local targetUnit=nil --Wrapper.Unit#UNIT
if missile.weapon and missile.weapon:isExist() then
-- Get target of missile.
target=missile.weapon:getTarget()
-- Get the target unit. Note if if _target is not nil, the unit can sometimes not be found!
if target then
self:T2({missiletarget=target})
-- Get target unit.
targetUnit=UNIT:Find(target)
if targetUnit then
targetName=targetUnit:GetName()
missile.targetUnit=targetUnit
missile.targetPlayer=self:_GetPlayerFromUnit(missile.targetUnit)
end
end
end
-- Missile got new target.
if missile.targetName and missile.targetName~=targetName then
self:I(self.lid..string.format("Missile %s(%s) changed target to %s. Previous target was %s.", missile.missileType, missile.missileName, targetName, missile.targetName))
end
-- Set target name.
missile.targetName=targetName
end
--- FOX event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). --- FOX event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
-- @param #FOX self -- @param #FOX self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function FOX:OnEventShot(EventData) function FOX:OnEventShot(EventData)
self:I({eventshot = EventData}) self:T2({eventshot=EventData})
if EventData.Weapon==nil then if EventData.Weapon==nil then
return return
@@ -1062,7 +1247,7 @@ function FOX:OnEventShot(EventData)
-- Weapon descriptor. -- Weapon descriptor.
local desc=EventData.Weapon:getDesc() local desc=EventData.Weapon:getDesc()
self:E({desc=desc}) self:T2({desc=desc})
-- Weapon category: 0=Shell, 1=Missile, 2=Rocket, 3=BOMB -- Weapon category: 0=Shell, 1=Missile, 2=Rocket, 3=BOMB
local weaponcategory=desc.category local weaponcategory=desc.category
@@ -1091,15 +1276,6 @@ function FOX:OnEventShot(EventData)
return return
end end
-- Get the target unit. Note if if _target is not nil, the unit can sometimes not be found!
if _target then
self:E({target=_target})
--_targetName=Unit.getName(_target)
--_targetUnit=UNIT:FindByName(_targetName)
_targetUnit=UNIT:Find(_target)
end
self:E(FOX.lid..string.format("EVENT SHOT: Target name = %s", tostring(_targetName)))
-- Track missiles of type AAM=1, SAM=2 or OTHER=6 -- Track missiles of type AAM=1, SAM=2 or OTHER=6
local _track = weaponcategory==1 and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6) local _track = weaponcategory==1 and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6)
@@ -1119,11 +1295,18 @@ function FOX:OnEventShot(EventData)
missile.shooterName=EventData.IniUnitName missile.shooterName=EventData.IniUnitName
missile.shotTime=timer.getAbsTime() missile.shotTime=timer.getAbsTime()
missile.shotCoord=EventData.IniUnit:GetCoordinate() missile.shotCoord=EventData.IniUnit:GetCoordinate()
missile.targetUnit=_targetUnit missile.fuseDist=desc.fuseDist
missile.targetPlayer=self:_GetPlayerFromUnit(missile.targetUnit) missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass
missile.targetOrig=missile.targetName
-- Only track if target was a player or target is protected. -- Set missile target name, unit and player.
if missile.targetPlayer or self:_IsProtected(missile.targetUnit) then self:GetMissileTarget(missile)
self:I(FOX.lid..string.format("EVENT SHOT: Shooter=%s %s(%s) ==> Target=%s, fuse dist=%s, explosive=%s",
tostring(missile.shooterName), tostring(missile.missileType), tostring(missile.missileName), tostring(missile.targetName), tostring(missile.fuseDist), tostring(missile.explosive)))
-- Only track if target was a player or target is protected. Saw the 9M311 missiles have no target!
if missile.targetPlayer or self:_IsProtected(missile.targetUnit) or missile.targetName=="unknown" then
-- Add missile table. -- Add missile table.
table.insert(self.missiles, missile) table.insert(self.missiles, missile)
@@ -1137,6 +1320,36 @@ function FOX:OnEventShot(EventData)
end end
--- FOX event handler for event hit.
-- @param #FOX self
-- @param Core.Event#EVENTDATA EventData
function FOX:OnEventHit(EventData)
self:T({eventhit = EventData})
-- Nil checks.
if EventData.Weapon==nil then
return
end
if EventData.IniUnit==nil then
return
end
if EventData.TgtUnit==nil then
return
end
local weapon=EventData.Weapon
local weaponname=weapon:getName()
for i,_missile in pairs(self.missiles) do
local missile=_missile --#FOX.MissileData
if missile.missileName==weaponname then
self:I(self.lid..string.format("WARNING: Missile %s (%s) hit target %s. Missile trainer target was %s.", missile.missileType, missile.missileName, EventData.TgtUnitName, missile.targetName))
self:I({missile=missile})
return
end
end
end
----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- RADIO MENU Functions -- RADIO MENU Functions

View File

@@ -57,8 +57,10 @@ SEAD = {
-- -- Defends the Russian SA installations from SEAD attacks. -- -- Defends the Russian SA installations from SEAD attacks.
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
function SEAD:New( SEADGroupPrefixes ) function SEAD:New( SEADGroupPrefixes )
local self = BASE:Inherit( self, BASE:New() ) local self = BASE:Inherit( self, BASE:New() )
self:F( SEADGroupPrefixes ) self:F( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
@@ -85,7 +87,29 @@ function SEAD:OnEventShot( EventData )
local SEADWeaponName = EventData.WeaponName -- return weapon type local SEADWeaponName = EventData.WeaponName -- return weapon type
-- Start of the 2nd loop -- Start of the 2nd loop
self:T( "Missile Launched = " .. SEADWeaponName ) self:T( "Missile Launched = " .. SEADWeaponName )
if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD
--if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired
then
local _evade = math.random (1,100) -- random number for chance of evading action local _evade = math.random (1,100) -- random number for chance of evading action
local _targetMim = EventData.Weapon:getTarget() -- Identify target local _targetMim = EventData.Weapon:getTarget() -- Identify target
local _targetMimname = Unit.getName(_targetMim) local _targetMimname = Unit.getName(_targetMim)
@@ -111,47 +135,62 @@ function SEAD:OnEventShot( EventData )
self:T( _targetskill ) self:T( _targetskill )
if self.TargetSkill[_targetskill] then if self.TargetSkill[_targetskill] then
if (_evade > self.TargetSkill[_targetskill].Evade) then if (_evade > self.TargetSkill[_targetskill].Evade) then
self:T( string.format("Evading, target skill " ..string.format(_targetskill)) ) self:T( string.format("Evading, target skill " ..string.format(_targetskill)) )
local _targetMim = Weapon.getTarget(SEADWeapon) local _targetMim = Weapon.getTarget(SEADWeapon)
local _targetMimname = Unit.getName(_targetMim) local _targetMimname = Unit.getName(_targetMim)
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
local _targetMimcont= _targetMimgroup:getController() local _targetMimcont= _targetMimgroup:getController()
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
local SuppressedGroups1 = {} -- unit suppressed radar off for a random time local SuppressedGroups1 = {} -- unit suppressed radar off for a random time
local function SuppressionEnd1(id) local function SuppressionEnd1(id)
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
SuppressedGroups1[id.groupName] = nil SuppressedGroups1[id.groupName] = nil
end end
local id = { local id = {
groupName = _targetMimgroup, groupName = _targetMimgroup,
ctrl = _targetMimcont ctrl = _targetMimcont
} }
local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2]) local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2])
if SuppressedGroups1[id.groupName] == nil then if SuppressedGroups1[id.groupName] == nil then
SuppressedGroups1[id.groupName] = { SuppressedGroups1[id.groupName] = {
SuppressionEndTime1 = timer.getTime() + delay1, SuppressionEndTime1 = timer.getTime() + delay1,
SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function
} }
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function
--trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20) --trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20)
end end
local SuppressedGroups = {} local SuppressedGroups = {}
local function SuppressionEnd(id) local function SuppressionEnd(id)
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
SuppressedGroups[id.groupName] = nil SuppressedGroups[id.groupName] = nil
end end
local id = { local id = {
groupName = _targetMimgroup, groupName = _targetMimgroup,
ctrl = _targetMimcont ctrl = _targetMimcont
} }
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
if SuppressedGroups[id.groupName] == nil then if SuppressedGroups[id.groupName] == nil then
SuppressedGroups[id.groupName] = { SuppressedGroups[id.groupName] = {
SuppressionEndTime = timer.getTime() + delay, SuppressionEndTime = timer.getTime() + delay,
SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function
} }
timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function
--trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20) --trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20)
end end

View File

@@ -205,7 +205,7 @@
-- @field Core.Set#SET_GROUP excludesetAI AI groups in this set will be explicitly excluded from handling by the airboss and not forced into the Marshal pattern. -- @field Core.Set#SET_GROUP excludesetAI AI groups in this set will be explicitly excluded from handling by the airboss and not forced into the Marshal pattern.
-- @field #boolean menusingle If true, menu is optimized for a single carrier. -- @field #boolean menusingle If true, menu is optimized for a single carrier.
-- @field #number collisiondist Distance up to which collision checks are done. -- @field #number collisiondist Distance up to which collision checks are done.
-- @field #nubmer holdtimestamp Timestamp when the carrier first came to an unexpected hold. -- @field #number holdtimestamp Timestamp when the carrier first came to an unexpected hold.
-- @field #number Tmessage Default duration in seconds messages are displayed to players. -- @field #number Tmessage Default duration in seconds messages are displayed to players.
-- @field #string soundfolder Folder within the mission (miz) file where airboss sound files are located. -- @field #string soundfolder Folder within the mission (miz) file where airboss sound files are located.
-- @field #string soundfolderLSO Folder withing the mission (miz) file where LSO sound files are stored. -- @field #string soundfolderLSO Folder withing the mission (miz) file where LSO sound files are stored.
@@ -231,6 +231,7 @@
-- @field #number skipperSpeed Speed in knots for manual recovery start. -- @field #number skipperSpeed Speed in knots for manual recovery start.
-- @field #number skipperCase Manual recovery case. -- @field #number skipperCase Manual recovery case.
-- @field #boolean skipperUturn U-turn on/off via menu. -- @field #boolean skipperUturn U-turn on/off via menu.
-- @field #number skipperOffset Holding offset angle in degrees for Case II/III manual recoveries.
-- @field #number skipperTime Recovery time in min for manual recovery. -- @field #number skipperTime Recovery time in min for manual recovery.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@@ -1234,6 +1235,7 @@ AIRBOSS = {
skipperMenu = nil, skipperMenu = nil,
skipperSpeed = nil, skipperSpeed = nil,
skipperTime = nil, skipperTime = nil,
skipperOffset = nil,
skipperUturn = nil, skipperUturn = nil,
} }
@@ -1559,7 +1561,7 @@ AIRBOSS.Difficulty={
-- @field #number Time Time in seconds. -- @field #number Time Time in seconds.
-- @field #number Rho Distance in meters. -- @field #number Rho Distance in meters.
-- @field #number X Distance in meters. -- @field #number X Distance in meters.
-- @field #nubmer Z Distance in meters. -- @field #number Z Distance in meters.
-- @field #number AoA Angle of Attack. -- @field #number AoA Angle of Attack.
-- @field #number Alt Altitude in meters. -- @field #number Alt Altitude in meters.
-- @field #number GSE Glideslope error in degrees. -- @field #number GSE Glideslope error in degrees.
@@ -1681,12 +1683,13 @@ AIRBOSS.MenuF10Root=nil
--- Airboss class version. --- Airboss class version.
-- @field #string version -- @field #string version
AIRBOSS.version="1.0.3" AIRBOSS.version="1.0.6"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Handle tanker and AWACS. Put them into pattern.
-- TODO: Handle cases where AI crashes on carrier deck ==> Clean up deck. -- TODO: Handle cases where AI crashes on carrier deck ==> Clean up deck.
-- TODO: Player eject and crash debrief "gradings". -- TODO: Player eject and crash debrief "gradings".
-- TODO: PWO during case 2/3. -- TODO: PWO during case 2/3.
@@ -1950,6 +1953,7 @@ function AIRBOSS:New(carriername, alias)
local case=2 local case=2
self.holdingoffset=30 self.holdingoffset=30
self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5) self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5)
self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green, 5)
self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White, 45) self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White, 45)
self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45) self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45)
self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue, 45) self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue, 45)
@@ -2127,6 +2131,13 @@ function AIRBOSS:New(carriername, alias)
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
--- On after "RecoveryStop" user function. Called when recovery of aircraft is stopped.
-- @function [parent=#AIRBOSS] OnAfterRecoveryStop
-- @param #AIRBOSS self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- Triggers the FSM event "RecoveryPause" that pauses the recovery of aircraft. --- Triggers the FSM event "RecoveryPause" that pauses the recovery of aircraft.
-- @function [parent=#AIRBOSS] RecoveryPause -- @function [parent=#AIRBOSS] RecoveryPause
@@ -2332,12 +2343,15 @@ end
-- @param #number duration Default duration of the recovery in minutes. Default 30 min. -- @param #number duration Default duration of the recovery in minutes. Default 30 min.
-- @param #number windondeck Default wind on deck in knots. Default 25 knots. -- @param #number windondeck Default wind on deck in knots. Default 25 knots.
-- @param #boolean uturn U-turn after recovery window closes on=true or off=false/nil. Default off. -- @param #boolean uturn U-turn after recovery window closes on=true or off=false/nil. Default off.
-- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°.
-- @return #AIRBOSS self -- @return #AIRBOSS self
function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn) function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn, offset)
self.skipperMenu=true self.skipperMenu=true
self.skipperTime=duration or 30 self.skipperTime=duration or 30
self.skipperSpeed=windondeck or 25 self.skipperSpeed=windondeck or 25
self.skipperOffset=offset or 30
if uturn then if uturn then
self.skipperUturn=true self.skipperUturn=true
else else
@@ -3062,6 +3076,15 @@ function AIRBOSS:SetRecoveryTanker(recoverytanker)
return self return self
end end
--- Define an AWACS associated with the carrier.
-- @param #AIRBOSS self
-- @param Ops.RecoveryTanker#RECOVERYTANKER awacs AWACS (recovery tanker) object.
-- @return #AIRBOSS self
function AIRBOSS:SetAWACS(awacs)
self.awacs=awacs
return self
end
--- Set default player skill. New players will be initialized with this skill. --- Set default player skill. New players will be initialized with this skill.
-- --
-- * "Flight Student" = @{#AIRBOSS.Difficulty.Easy} -- * "Flight Student" = @{#AIRBOSS.Difficulty.Easy}
@@ -5913,38 +5936,48 @@ function AIRBOSS:_ScanCarrierZone()
-- Debug info. -- Debug info.
self:T3(self.lid..string.format("Known AI flight group %s closed in by %.1f NM", knownflight.groupname, UTILS.MetersToNM(closein))) self:T3(self.lid..string.format("Known AI flight group %s closed in by %.1f NM", knownflight.groupname, UTILS.MetersToNM(closein)))
-- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value. -- Is this group the tanker?
if closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad then local istanker=self.tanker and self.tanker.tanker:GetName()==groupname
-- Check that we do not add a recovery tanker for marshaling. -- Is this group the AWACS?
if self.tanker and self.tanker.tanker:GetName()==groupname then local isawacs=self.awacs and self.awacs.tanker:GetName()==groupname
-- Send tanker to marshal stack?
local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.tanker.recovery==true
-- Send AWACS to marhsal stack?
local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.awacs.recovery==true
-- Put flight into Marshal.
local putintomarshal=closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad and istanker==false and isawacs==false
-- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value.
if putintomarshal or tanker2marshal or awacs2marshal then
-- Get the next free stack for current recovery case.
local stack=self:_GetFreeStack(knownflight.ai)
-- Repawn.
local respawn=self.respawnAI --or tanker2marshal
-- Don't touch the recovery tanker! if stack then
-- Send AI to marshal stack. We respawn the group to clean possible departure and destination airbases.
self:_MarshalAI(knownflight, stack, respawn)
else else
-- Get the next free stack for current recovery case. -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available.
local stack=self:_GetFreeStack(knownflight.ai) if not self:_InQueue(self.Qwaiting, knownflight.group) then
self:_WaitAI(knownflight, respawn) -- Group is respawned to clear any attached airfields.
if stack then
-- Send AI to marshal stack. We respawn the group to clean possible departure and destination airbases.
self:_MarshalAI(knownflight, stack, self.respawnAI)
else
-- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available.
if not self:_InQueue(self.Qwaiting, knownflight.group) then
self:_WaitAI(knownflight, self.respawnAI) -- Group is respawned to clear any attached airfields.
end
end end
-- Break the loop to not have all flights at once! Spams the message screen. end
break
-- Break the loop to not have all flights at once! Spams the message screen.
end -- Tanker break
end -- Closed in
end -- Closed in or tanker/AWACS
end -- AI end -- AI
else else
@@ -6862,8 +6895,8 @@ function AIRBOSS:_GetFreeStack(ai, case, empty)
local n=flight.flag local n=flight.flag
if n>0 then if n>0 then
if flight.ai then if flight.ai or flight.case>1 then
stack[n]=0 -- AI get one stack on their own. stack[n]=0 -- AI get one stack on their own. Also CASE II/III get one stack each.
else else
stack[n]=stack[n]-1 stack[n]=stack[n]-1
end end
@@ -6878,7 +6911,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty)
local nfree=nil local nfree=nil
for i=1,nmaxstacks do for i=1,nmaxstacks do
self:T2(self.lid..string.format("FF Stack[%d]=%d", i, stack[i])) self:T2(self.lid..string.format("FF Stack[%d]=%d", i, stack[i]))
if ai or empty then if ai or empty or case>1 then
-- AI need the whole stack. -- AI need the whole stack.
if stack[i]==self.NmaxStack then if stack[i]==self.NmaxStack then
nfree=i nfree=i
@@ -8368,28 +8401,35 @@ function AIRBOSS:OnEventEngineShutdown(EventData)
-- Debug message. -- Debug message.
self:T(self.lid..string.format("AI unit %s shut down its engines!", _unitName)) self:T(self.lid..string.format("AI unit %s shut down its engines!", _unitName))
if self.despawnshutdown then -- Get flight.
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights)
-- Only AI flights.
if flight and flight.ai then
-- Get flight. -- Check if all elements were recovered.
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) local recovered=self:_CheckSectionRecovered(flight)
-- Only AI flights.
if flight and flight.ai then
-- Check if all elements were recovered. -- Despawn group and completely remove flight.
local recovered=self:_CheckSectionRecovered(flight) if recovered then
self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.", tostring(EventData.IniGroupName)))
-- Despawn group and completely remove flight. -- Remove flight.
if recovered then self:_RemoveFlight(flight)
self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.", tostring(EventData.IniGroupName)))
-- Check if this is a tanker or AWACS associated with the carrier.
local istanker=self.tanker and self.tanker.tanker:GetName()==EventData.IniGroupName
local isawacs=self.awacs and self.awacs.tanker:GetName()==EventData.IniGroupName
-- Destroy group if desired. Recovery tankers have their own logic for despawning.
if self.despawnshutdown and not (istanker or isawacs) then
EventData.IniGroup:Destroy(nil, 5) EventData.IniGroup:Destroy(nil, 5)
self:_RemoveFlight(flight)
end end
end end
end end
end
end
end end
--- Airboss event handler for event that a unit takes off. --- Airboss event handler for event that a unit takes off.
@@ -9500,7 +9540,7 @@ function AIRBOSS:_Final(playerData, nocheck)
local inzone=playerData.unit:IsInZone(zone) local inzone=playerData.unit:IsInZone(zone)
-- Check. -- Check.
if inzone then if inzone then --and math.abs(groovedata.Roll)<5 then
-- Hint for player about altitude, AoA etc. Sound is off. -- Hint for player about altitude, AoA etc. Sound is off.
self:_PlayerHint(playerData, nil, true) self:_PlayerHint(playerData, nil, true)
@@ -9559,12 +9599,11 @@ function AIRBOSS:_Groove(playerData)
local glideslopeError=groovedata.GSE local glideslopeError=groovedata.GSE
local AoA=groovedata.AoA local AoA=groovedata.AoA
-- Start time in groove when "wings are level", i.e. <= 5°.
if playerData.TIG0==nil and math.abs(groovedata.Roll)<=5.0 then
playerData.TIG0=timer.getTime()
end
if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX then if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX and (math.abs(groovedata.Roll)<=4.0 or playerData.unit:IsInZone(self:_GetZoneLineup())) then
-- Start time in groove
playerData.TIG0=timer.getTime()
-- LSO "Call the ball" call. -- LSO "Call the ball" call.
self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true)
@@ -9772,7 +9811,7 @@ function AIRBOSS:_Groove(playerData)
-- Wait until player passed the 0.75 NM distance. -- Wait until player passed the 0.75 NM distance.
local _advice=true local _advice=true
if rho>RXX and playerData.difficulty~=AIRBOSS.Difficulty.EASY then if playerData.TIG0==nil and playerData.difficulty~=AIRBOSS.Difficulty.EASY then --rho>RXX
_advice=false _advice=false
end end
@@ -10283,15 +10322,45 @@ function AIRBOSS:_GetZoneInitial(case)
return zone return zone
end end
--- Get lineup groove zone.
-- @param #AIRBOSS self
-- @return Core.Zone#ZONE_POLYGON_BASE Lineup zone.
function AIRBOSS:_GetZoneLineup()
-- Get radial, i.e. inverse of BRC.
local fbi=self:GetRadial(1, false, false)
-- Stern coordinate.
local st=self:_GetOptLandingCoordinate()
-- Zone points.
local c1=st
local c2=st:Translate(UTILS.NMToMeters(0.50), fbi+15)
local c3=st:Translate(UTILS.NMToMeters(0.50), fbi+self.lue._max-0.05)
local c4=st:Translate(UTILS.NMToMeters(0.77), fbi+self.lue._max-0.05)
local c5=c4:Translate(UTILS.NMToMeters(0.25), fbi-90)
-- Vec2 array.
local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()}
-- Polygon zone.
local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2)
return zone
end
--- Get groove zone. --- Get groove zone.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #number l Length of the groove in NM. Default 1.5 NM. -- @param #number l Length of the groove in NM. Default 1.5 NM.
-- @param #number w Width of the groove in NM. Default 0.25 NM. -- @param #number w Width of the groove in NM. Default 0.25 NM.
-- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. -- @param #number b Width of the beginning in NM. Default 0.10 NM.
function AIRBOSS:_GetZoneGroove(l, w) -- @return Core.Zone#ZONE_POLYGON_BASE Groove zone.
function AIRBOSS:_GetZoneGroove(l, w, b)
l=l or 1.50 l=l or 1.50
w=w or 0.25 w=w or 0.25
b=b or 0.10
-- Get radial, i.e. inverse of BRC. -- Get radial, i.e. inverse of BRC.
local fbi=self:GetRadial(1, false, false) local fbi=self:GetRadial(1, false, false)
@@ -10302,9 +10371,9 @@ function AIRBOSS:_GetZoneGroove(l, w)
-- Zone points. -- Zone points.
local c1=st:Translate(self.carrierparam.totwidthstarboard, fbi-90) local c1=st:Translate(self.carrierparam.totwidthstarboard, fbi-90)
local c2=st:Translate(UTILS.NMToMeters(0.10), fbi-90):Translate(UTILS.NMToMeters(0.3), fbi) local c2=st:Translate(UTILS.NMToMeters(0.10), fbi-90):Translate(UTILS.NMToMeters(0.3), fbi)
local c3=st:Translate(UTILS.NMToMeters(w/2), fbi-90):Translate(UTILS.NMToMeters(l), fbi) local c3=st:Translate(UTILS.NMToMeters(0.25), fbi-90):Translate(UTILS.NMToMeters(l), fbi)
local c4=st:Translate(UTILS.NMToMeters(w/2), fbi+90):Translate(UTILS.NMToMeters(l), fbi) local c4=st:Translate(UTILS.NMToMeters(w/2), fbi+90):Translate(UTILS.NMToMeters(l), fbi)
local c5=st:Translate(UTILS.NMToMeters(0.10), fbi+90):Translate(UTILS.NMToMeters(0.3), fbi) local c5=st:Translate(UTILS.NMToMeters(b), fbi+90):Translate(UTILS.NMToMeters(0.3), fbi)
local c6=st:Translate(self.carrierparam.totwidthport, fbi+90) local c6=st:Translate(self.carrierparam.totwidthport, fbi+90)
-- Vec2 array. -- Vec2 array.
@@ -14328,7 +14397,7 @@ end
-- @param #number delay Delay in seconds, before the message is broadcasted. -- @param #number delay Delay in seconds, before the message is broadcasted.
-- @param #number interval Interval in seconds after the last sound has been played. -- @param #number interval Interval in seconds after the last sound has been played.
-- @param #boolean click If true, play radio click at the end. -- @param #boolean click If true, play radio click at the end.
-- @param #booelan pilotcall If true, it's a pilot call. -- @param #boolean pilotcall If true, it's a pilot call.
function AIRBOSS:RadioTransmission(radio, call, loud, delay, interval, click, pilotcall) function AIRBOSS:RadioTransmission(radio, call, loud, delay, interval, click, pilotcall)
self:F2({radio=radio, call=call, loud=loud, delay=delay, interval=interval, click=click}) self:F2({radio=radio, call=call, loud=loud, delay=delay, interval=interval, click=click})
@@ -15471,6 +15540,12 @@ function AIRBOSS:_AddF10Commands(_unitName)
missionCommands.addCommandForGroup(gid, "45 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 45) missionCommands.addCommandForGroup(gid, "45 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 45)
missionCommands.addCommandForGroup(gid, "60 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 60) missionCommands.addCommandForGroup(gid, "60 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 60)
missionCommands.addCommandForGroup(gid, "90 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 90) missionCommands.addCommandForGroup(gid, "90 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 90)
local _menusetrtime=missionCommands.addSubMenuForGroup(gid, "Set Marshal Radial", _skipperPath)
missionCommands.addCommandForGroup(gid, "+30°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, 30)
missionCommands.addCommandForGroup(gid, "+15°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, 15)
missionCommands.addCommandForGroup(gid, "", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, 0)
missionCommands.addCommandForGroup(gid, "-15°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, -15)
missionCommands.addCommandForGroup(gid, "-30°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, -30)
missionCommands.addCommandForGroup(gid, "U-turn On/Off", _skipperPath, self._SkipperRecoveryUturn, self, _unitName) missionCommands.addCommandForGroup(gid, "U-turn On/Off", _skipperPath, self._SkipperRecoveryUturn, self, _unitName)
missionCommands.addCommandForGroup(gid, "Start CASE I", _skipperPath, self._SkipperStartRecovery, self, _unitName, 1) missionCommands.addCommandForGroup(gid, "Start CASE I", _skipperPath, self._SkipperStartRecovery, self, _unitName, 1)
missionCommands.addCommandForGroup(gid, "Start CASE II", _skipperPath, self._SkipperStartRecovery, self, _unitName, 2) missionCommands.addCommandForGroup(gid, "Start CASE II", _skipperPath, self._SkipperStartRecovery, self, _unitName, 2)
@@ -15526,6 +15601,9 @@ function AIRBOSS:_SkipperStartRecovery(_unitName, case)
-- Inform player. -- Inform player.
local text=string.format("affirm, Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.", case, self.skipperTime, self.skipperSpeed, tostring(self.skipperUturn)) local text=string.format("affirm, Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.", case, self.skipperTime, self.skipperSpeed, tostring(self.skipperUturn))
if case>1 then
text=text..string.format(" Marshal radial %d°.", self.skipperOffset)
end
if self:IsRecovering() then if self:IsRecovering() then
text="negative, carrier is already recovering." text="negative, carrier is already recovering."
self:MessageToPlayer(playerData, text, "AIRBOSS") self:MessageToPlayer(playerData, text, "AIRBOSS")
@@ -15540,7 +15618,7 @@ function AIRBOSS:_SkipperStartRecovery(_unitName, case)
local C9=UTILS.SecondsToClock(t9) local C9=UTILS.SecondsToClock(t9)
-- Carrier will turn into the wind. Wind on deck 25 knots. U-turn on. -- Carrier will turn into the wind. Wind on deck 25 knots. U-turn on.
self:AddRecoveryWindow(C0, C9, case, 30, true, self.skipperSpeed, self.skipperUturn) self:AddRecoveryWindow(C0, C9, case, self.skipperOffset, true, self.skipperSpeed, self.skipperUturn)
end end
end end
@@ -15574,6 +15652,30 @@ function AIRBOSS:_SkipperStopRecovery(_unitName)
end end
end end
--- Skipper set recovery offset angle.
-- @param #AIRBOSS self
-- @param #string _unitName Name fo the player unit.
-- @param #number offset Recovery holding offset angle in degrees for Case II/III.
function AIRBOSS:_SkipperRecoveryOffset(_unitName, offset)
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
-- Check if we have a unit which is a player.
if _unit and _playername then
local playerData=self.players[_playername] --#AIRBOSS.PlayerData
if playerData then
-- Inform player.
local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset)
self:MessageToPlayer(playerData, text, "AIRBOSS")
self.skipperOffset=offset
end
end
end
--- Skipper set recovery time. --- Skipper set recovery time.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #string _unitName Name fo the player unit. -- @param #string _unitName Name fo the player unit.

View File

@@ -60,6 +60,8 @@
-- @field #number callsignname Number for the callsign name. -- @field #number callsignname Number for the callsign name.
-- @field #number callsignnumber Number of the callsign name. -- @field #number callsignnumber Number of the callsign name.
-- @field #string modex Tail number of the tanker. -- @field #string modex Tail number of the tanker.
-- @field #boolean eplrs If true, enable data link, e.g. if used as AWACS.
-- @field #boolean recovery If true, tanker will recover using the AIRBOSS marshal pattern.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Recovery Tanker. --- Recovery Tanker.
@@ -295,15 +297,16 @@ RECOVERYTANKER = {
callsignnumber = nil, callsignnumber = nil,
modex = nil, modex = nil,
eplrs = nil, eplrs = nil,
recovery = nil,
} }
--- Unique ID (global). --- Unique ID (global).
-- @field #number UID Unique ID (global). -- @field #number UID Unique ID (global).
RECOVERYTANKER.UID=0 _RECOVERYTANKERID=0
--- Class version. --- Class version.
-- @field #string version -- @field #string version
RECOVERYTANKER.version="1.0.7" RECOVERYTANKER.version="1.0.8"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -349,16 +352,16 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname)
self.tankergroupname=tankergroupname self.tankergroupname=tankergroupname
-- Increase unique ID. -- Increase unique ID.
RECOVERYTANKER.UID=RECOVERYTANKER.UID+1 _RECOVERYTANKERID=_RECOVERYTANKERID+1
-- Unique ID of this tanker. -- Unique ID of this tanker.
self.uid=RECOVERYTANKER.UID self.uid=_RECOVERYTANKERID
-- Save self in static object. Easier to retrieve later. -- Save self in static object. Easier to retrieve later.
self.carrier:SetState(self.carrier, string.format("RECOVERYTANKER_%d", self.uid) , self) self.carrier:SetState(self.carrier, string.format("RECOVERYTANKER_%d", self.uid) , self)
-- Set unique spawn alias. -- Set unique spawn alias.
self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.tankergroupname, RECOVERYTANKER.UID) self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.tankergroupname, _RECOVERYTANKERID)
-- Log ID. -- Log ID.
self.lid=string.format("RECOVERYTANKER %s | ", self.alias) self.lid=string.format("RECOVERYTANKER %s | ", self.alias)
@@ -377,6 +380,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname)
self:SetPatternUpdateHeading() self:SetPatternUpdateHeading()
self:SetPatternUpdateInterval() self:SetPatternUpdateInterval()
self:SetAWACS(false) self:SetAWACS(false)
self:SetRecoveryAirboss(false)
-- Debug trace. -- Debug trace.
if false then if false then
@@ -399,6 +403,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname)
self:AddTransition("*", "RefuelStop", "Running") -- Tanker starts to refuel. self:AddTransition("*", "RefuelStop", "Running") -- Tanker starts to refuel.
self:AddTransition("*", "Run", "Running") -- Tanker starts normal operation again. self:AddTransition("*", "Run", "Running") -- Tanker starts normal operation again.
self:AddTransition("Running", "RTB", "Returning") -- Tanker is returning to base (for fuel). self:AddTransition("Running", "RTB", "Returning") -- Tanker is returning to base (for fuel).
self:AddTransition("Returning", "Returned", "Returned") -- Tanker has returned to its airbase (i.e. landed).
self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "Status", "*") -- Status update.
self:AddTransition("Running", "PatternUpdate", "*") -- Update pattern wrt to carrier. self:AddTransition("Running", "PatternUpdate", "*") -- Update pattern wrt to carrier.
self:AddTransition("*", "Stop", "Stopped") -- Stop the FSM. self:AddTransition("*", "Stop", "Stopped") -- Stop the FSM.
@@ -472,6 +477,26 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname)
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where the tanker should return to. -- @param Wrapper.Airbase#AIRBASE airbase The airbase where the tanker should return to.
--- Triggers the FSM event "Returned" after the tanker has landed.
-- @function [parent=#RECOVERYTANKER] Returned
-- @param #RECOVERYTANKER self
-- @param Wrapper.Airbase#AIRBASE airbase The airbase the tanker has landed.
--- Triggers the delayed FSM event "Returned" after the tanker has landed.
-- @function [parent=#RECOVERYTANKER] __Returned
-- @param #RECOVERYTANKER self
-- @param #number delay Delay in seconds.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase the tanker has landed.
--- On after "Returned" event user function. Called when a the the tanker has landed at an airbase.
-- @function [parent=#RECOVERYTANKER] OnAfterReturned
-- @param #RECOVERYTANKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase the tanker has landed.
--- Triggers the FSM event "Status" that updates the tanker status. --- Triggers the FSM event "Status" that updates the tanker status.
-- @function [parent=#RECOVERYTANKER] Status -- @function [parent=#RECOVERYTANKER] Status
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
@@ -596,6 +621,19 @@ function RECOVERYTANKER:SetHomeBase(airbase)
return self return self
end end
--- Activate recovery by the AIRBOSS class. Tanker will get a Marshal stack and perform a CASE I, II or III recovery when RTB.
-- @param #RECOVERYTANKER self
-- @param #boolean switch If true or nil, recovery is done by AIRBOSS.
-- @return #RECOVERYTANKER self
function RECOVERYTANKER:SetRecoveryAirboss(switch)
if switch==true or switch==nil then
self.recovery=true
else
self.recovery=false
end
return self
end
--- Set that the group takes the roll of an AWACS instead of a refueling tanker. --- Set that the group takes the roll of an AWACS instead of a refueling tanker.
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
-- @param #boolean switch If true or nil, set roll AWACS. -- @param #boolean switch If true or nil, set roll AWACS.
@@ -775,6 +813,13 @@ function RECOVERYTANKER:IsReturning()
return self:is("Returning") return self:is("Returning")
end end
--- Check if tanker has returned to base.
-- @param #RECOVERYTANKER self
-- @return #boolean If true, tanker has returned to base.
function RECOVERYTANKER:IsReturned()
return self:is("Returned")
end
--- Check if tanker is currently operating. --- Check if tanker is currently operating.
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
-- @return #boolean If true, tanker is operating. -- @return #boolean If true, tanker is operating.
@@ -830,6 +875,7 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
-- Handle events. -- Handle events.
self:HandleEvent(EVENTS.EngineShutdown) self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Refueling, self._RefuelingStart) --Need explicit functions since OnEventRefueling and OnEventRefuelingStop did not hook! self:HandleEvent(EVENTS.Refueling, self._RefuelingStart) --Need explicit functions since OnEventRefueling and OnEventRefuelingStop did not hook!
self:HandleEvent(EVENTS.RefuelingStop, self._RefuelingStop) self:HandleEvent(EVENTS.RefuelingStop, self._RefuelingStop)
self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrDead) self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrDead)
@@ -865,7 +911,7 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
else else
-- Check if an uncontrolled tanker group was requested. -- Check if an uncontrolled tanker group was requested.
if self.useuncontrolled then if self.uncontrolledac then
-- Use an uncontrolled aircraft group. -- Use an uncontrolled aircraft group.
self.tanker=GROUP:FindByName(self.tankergroupname) self.tanker=GROUP:FindByName(self.tankergroupname)
@@ -884,14 +930,14 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
else else
-- Spawn tanker at airbase. -- Spawn tanker at airbase.
self.tanker=Spawn:SpawnAtAirbase(self.airbase, self.takeoff) self.tanker=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.OpenMedOrBig)
end end
end end
-- Initialize route. self.distStern<0! -- Initialize route. self.distStern<0!
SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 1) self:ScheduleOnce(1, self._InitRoute, self, -self.distStern+UTILS.NMToMeters(3))
-- Create tanker beacon. -- Create tanker beacon.
if self.TACANon then if self.TACANon then
@@ -929,7 +975,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
-- Get current time. -- Get current time.
local time=timer.getTime() local time=timer.getTime()
if self.tanker:IsAlive() then if self.tanker and self.tanker:IsAlive() then
--------------------- ---------------------
-- TANKER is ALIVE -- -- TANKER is ALIVE --
@@ -937,8 +983,14 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
-- Get fuel of tanker. -- Get fuel of tanker.
local fuel=self.tanker:GetFuel()*100 local fuel=self.tanker:GetFuel()*100
local text=string.format("Recovery tanker %s: state=%s fuel=%.1f", self.tanker:GetName(), self:GetState(), fuel) local life=self.tanker:GetUnit(1):GetLife()
local life0=self.tanker:GetUnit(1):GetLife0()
local lifeR=self.tanker:GetUnit(1):GetLifeRelative()
-- Report fuel and life.
local text=string.format("Recovery tanker %s: state=%s fuel=%.1f, life=%.1f/%.1f=%d", self.tanker:GetName(), self:GetState(), fuel, life, life0, lifeR*100)
self:T(self.lid..text) self:T(self.lid..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Check if tanker is running and not RTBing or refueling. -- Check if tanker is running and not RTBing or refueling.
if self:IsRunning() then if self:IsRunning() then
@@ -947,7 +999,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
if fuel<self.lowfuel then if fuel<self.lowfuel then
-- Check if spawn in air is activated. -- Check if spawn in air is activated.
if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then if (self.takeoff==SPAWN.Takeoff.Air or self.respawninair) and not self.recovery then
-- Check that respawn should happen. -- Check that respawn should happen.
if self.respawn then if self.respawn then
@@ -1013,6 +1065,16 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
end end
end end
elseif self:IsReturning() then
-- Tanker is returning to its base.
self:T2(self.lid.."Tanker is returning.")
elseif self:IsReturned() then
-- Tanker landed. Waiting for engine shutdown...
self:T2(self.lid.."Tanker returned. waiting for engine shutdown.")
end end
-- Call status again in 30 seconds. -- Call status again in 30 seconds.
@@ -1028,6 +1090,8 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
if not self:IsStopped() then if not self:IsStopped() then
self:E(self.lid.."Recovery tanker is NOT alive (and not stopped)!")
-- Stop FSM. -- Stop FSM.
self:Stop() self:Stop()
@@ -1037,6 +1101,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To)
end end
end end
end end
end end
@@ -1133,7 +1198,14 @@ function RECOVERYTANKER:onafterRTB(From, Event, To, airbase)
-- Set landing waypoint. -- Set landing waypoint.
wp[1]=self.tanker:GetCoordinate():WaypointAirTurningPoint(nil, speed, {}, "Current Position") wp[1]=self.tanker:GetCoordinate():WaypointAirTurningPoint(nil, speed, {}, "Current Position")
wp[2]=airbase:GetCoordinate():SetAltitude(500):WaypointAirLanding(speed, airbase, nil, "Land at airbase")
if self.recovery then
-- Fly a bit until the airboss takes over.
wp[2]=self.tanker:GetCoordinate():Translate(UTILS.NMToMeters(10), self.tanker:GetHeading(), true):WaypointAirTurningPoint(nil, speed, {}, "WP")
else
wp[2]=airbase:GetCoordinate():SetAltitude(500):WaypointAirLanding(speed, airbase, nil, "Land at airbase")
end
-- Initialize WP and route tanker. -- Initialize WP and route tanker.
self.tanker:WayPointInitialize(wp) self.tanker:WayPointInitialize(wp)
@@ -1142,6 +1214,25 @@ function RECOVERYTANKER:onafterRTB(From, Event, To, airbase)
self.tanker:Route(wp, 1) self.tanker:Route(wp, 1)
end end
--- On after Returned event. The tanker has landed.
-- @param #RECOVERYTANKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The base at which the tanker landed.
function RECOVERYTANKER:onafterReturned(From, Event, To, airbase)
if airbase then
local airbasename=airbase:GetName()
self:I(self.lid..string.format("Tanker returned to airbase %s", tostring(airbasename)))
else
self:E(self.lid..string.format("WARNING: Tanker landed but airbase (EventData.Place) is nil!"))
end
end
--- On after Stop event. Unhandle events and stop status updates. --- On after Stop event. Unhandle events and stop status updates.
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
-- @param #string From From state. -- @param #string From From state.
@@ -1150,14 +1241,18 @@ end
function RECOVERYTANKER:onafterStop(From, Event, To) function RECOVERYTANKER:onafterStop(From, Event, To)
-- Unhandle events. -- Unhandle events.
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown) self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Refueling) self:UnHandleEvent(EVENTS.Refueling)
self:UnHandleEvent(EVENTS.RefuelingStop) self:UnHandleEvent(EVENTS.RefuelingStop)
self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.Crash)
-- Clear all pending FSM events.
self.CallScheduler:Clear()
-- If tanker is alive, despawn it. -- If tanker is alive, despawn it.
if self.helo and self.helo:IsAlive() then if self.tanker and self.tanker:IsAlive() then
self:I(self.lid.."Stopping FSM and despawning tanker.") self:I(self.lid.."Stopping FSM and despawning tanker.")
self.tanker:Destroy() self.tanker:Destroy()
else else
@@ -1170,6 +1265,42 @@ end
-- EVENT functions -- EVENT functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Event handler for landing of recovery tanker.
-- @param #RECOVERYTANKER self
-- @param Core.Event#EVENTDATA EventData Event data.
function RECOVERYTANKER:OnEventLand(EventData)
-- Group that shut down the engine.
local group=EventData.IniGroup --Wrapper.Group#GROUP
-- Check if group is alive.
if group and group:IsAlive() then
-- Group name. When spawning it will have #001 attached.
local groupname=group:GetName()
-- Check that we have the right group and that it should be respawned.
if groupname==self.tanker:GetName() then
local airbase=nil --Wrapper.Airbase#AIRBASE
local airbasename="unknown"
if EventData.Place then
airbase=EventData.Place
airbasename=airbase:GetName()
end
-- Debug info.
local text=string.format("Recovery tanker group %s landed at airbase %s.", group:GetName(), airbasename)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Trigger returned event.
self:__Returned(1, airbase)
end
end
end
--- Event handler for engine shutdown of recovery tanker. --- Event handler for engine shutdown of recovery tanker.
-- Respawn tanker group once it landed because it was out of fuel. -- Respawn tanker group once it landed because it was out of fuel.
-- @param #RECOVERYTANKER self -- @param #RECOVERYTANKER self
@@ -1180,7 +1311,7 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData)
local group=EventData.IniGroup --Wrapper.Group#GROUP local group=EventData.IniGroup --Wrapper.Group#GROUP
-- Check if group is alive. -- Check if group is alive.
if group:IsAlive() then if group and group:IsAlive() then
-- Group name. When spawning it will have #001 attached. -- Group name. When spawning it will have #001 attached.
local groupname=group:GetName() local groupname=group:GetName()
@@ -1199,9 +1330,9 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData)
group:InitRadioModulation(self.RadioModu) group:InitRadioModulation(self.RadioModu)
group:InitModex(self.modex) group:InitModex(self.modex)
-- Respawn tanker. -- Respawn tanker. Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076
-- Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076 --SCHEDULER:New(nil , group.RespawnAtCurrentAirbase, {group}, 1)
SCHEDULER:New(nil , group.RespawnAtCurrentAirbase, {group}, 1) self:ScheduleOnce(1, GROUP.RespawnAtCurrentAirbase, group)
-- Create tanker beacon and activate TACAN. -- Create tanker beacon and activate TACAN.
if self.TACANon then if self.TACANon then
@@ -1219,8 +1350,8 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData)
end end
-- Initial route. -- Initial route.
SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 2) --SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 2)
self:ScheduleOnce(2, RECOVERYTANKER._InitRoute, self, -self.distStern+UTILS.NMToMeters(3))
end end
end end
@@ -1250,8 +1381,7 @@ function RECOVERYTANKER:_RefuelingStart(EventData)
self:T(self.lid..text) self:T(self.lid..text)
-- FMS state "Refueling". -- FMS state "Refueling".
self:RefuelStart(receiver) self:RefuelStart(receiver)
end end
end end
@@ -1483,7 +1613,8 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
if delay and delay>0 then if delay and delay>0 then
-- Schedule TACAN activation. -- Schedule TACAN activation.
SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay) --SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay)
self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self)
else else
@@ -1494,7 +1625,7 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
if unit and unit:IsAlive() then if unit and unit:IsAlive() then
-- Debug message. -- Debug message.
local text=string.format("Activating recovery tanker TACAN beacon: channel=%d mode=%s, morse=%s.", self.TACANchannel, self.TACANmode, self.TACANmorse) local text=string.format("Activating TACAN beacon: channel=%d mode=%s, morse=%s.", self.TACANchannel, self.TACANmode, self.TACANmorse)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text) self:T(self.lid..text)
@@ -1503,7 +1634,7 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
self.beacon:ActivateTACAN(self.TACANchannel, self.TACANmode, self.TACANmorse, true) self.beacon:ActivateTACAN(self.TACANchannel, self.TACANmode, self.TACANmorse, true)
else else
self:E("ERROR: Recovery tanker is not alive!") self:E(self.lid.."ERROR: Recovery tanker is not alive!")
end end
end end

View File

@@ -231,11 +231,11 @@ RESCUEHELO = {
--- Unique ID (global). --- Unique ID (global).
-- @field #number uid Unique ID (global). -- @field #number uid Unique ID (global).
RESCUEHELO.UID=0 _RESCUEHELOID=0
--- Class version. --- Class version.
-- @field #string version -- @field #string version
RESCUEHELO.version="1.0.7" RESCUEHELO.version="1.0.8"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -277,16 +277,16 @@ function RESCUEHELO:New(carrierunit, helogroupname)
self.helogroupname=helogroupname self.helogroupname=helogroupname
-- Increase ID. -- Increase ID.
RESCUEHELO.UID=RESCUEHELO.UID+1 _RESCUEHELOID=_RESCUEHELOID+1
-- Unique ID of this helo. -- Unique ID of this helo.
self.uid=RESCUEHELO.UID self.uid=_RESCUEHELOID
-- Save self in static object. Easier to retrieve later. -- Save self in static object. Easier to retrieve later.
self.carrier:SetState(self.carrier, string.format("RESCUEHELO_%d", self.uid) , self) self.carrier:SetState(self.carrier, string.format("RESCUEHELO_%d", self.uid) , self)
-- Set unique spawn alias. -- Set unique spawn alias.
self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.helogroupname, RESCUEHELO.UID) self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.helogroupname, _RESCUEHELOID)
-- Log ID. -- Log ID.
self.lid=string.format("RESCUEHELO %s | ", self.alias) self.lid=string.format("RESCUEHELO %s | ", self.alias)
@@ -386,6 +386,7 @@ function RESCUEHELO:New(carrierunit, helogroupname)
-- @param #string To To state. -- @param #string To To state.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase to return to. Default is the home base. -- @param Wrapper.Airbase#AIRBASE airbase The airbase to return to. Default is the home base.
--- Triggers the FSM event "Returned" after the helo has landed. --- Triggers the FSM event "Returned" after the helo has landed.
-- @function [parent=#RESCUEHELO] Returned -- @function [parent=#RESCUEHELO] Returned
-- @param #RESCUEHELO self -- @param #RESCUEHELO self
@@ -719,7 +720,7 @@ end
function RESCUEHELO:OnEventLand(EventData) function RESCUEHELO:OnEventLand(EventData)
local group=EventData.IniGroup --Wrapper.Group#GROUP local group=EventData.IniGroup --Wrapper.Group#GROUP
if group:IsAlive() then if group and group:IsAlive() then
-- Group name that landed. -- Group name that landed.
local groupname=group:GetName() local groupname=group:GetName()
@@ -727,8 +728,15 @@ function RESCUEHELO:OnEventLand(EventData)
-- Check that it was our helo that landed. -- Check that it was our helo that landed.
if groupname==self.helo:GetName() then if groupname==self.helo:GetName() then
local airbase=nil --Wrapper.Airbase#AIRBASE
local airbasename="unknown"
if EventData.Place then
airbase=EventData.Place
airbasename=airbase:GetName()
end
-- Respawn the Helo. -- Respawn the Helo.
local text=string.format("Respawning rescue helo group %s at home base.", groupname) local text=string.format("Rescue helo group %s landed at airbase %s.", groupname, airbasename)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text) self:T(self.lid..text)
@@ -749,7 +757,7 @@ function RESCUEHELO:OnEventLand(EventData)
end end
-- Trigger returned event. Respawn at current airbase. -- Trigger returned event. Respawn at current airbase.
self:__Returned(3, EventData.Place) self:__Returned(3, airbase)
end end
end end
@@ -839,7 +847,6 @@ function RESCUEHELO:onafterStart(From, Event, To)
self:I(self.lid..text) self:I(self.lid..text)
-- Handle events. -- Handle events.
--self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Land) self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrEject) self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrEject)
self:HandleEvent(EVENTS.Ejection, self._OnEventCrashOrEject) self:HandleEvent(EVENTS.Ejection, self._OnEventCrashOrEject)
@@ -899,7 +906,7 @@ function RESCUEHELO:onafterStart(From, Event, To)
else else
-- Spawn at airbase. -- Spawn at airbase.
self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff) self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable)
-- Delay before formation is started. -- Delay before formation is started.
if self.takeoff==SPAWN.Takeoff.Runway then if self.takeoff==SPAWN.Takeoff.Runway then
@@ -948,7 +955,7 @@ function RESCUEHELO:onafterStatus(From, Event, To)
local time=timer.getTime() local time=timer.getTime()
-- Check if helo is running and not RTBing already or rescuing. -- Check if helo is running and not RTBing already or rescuing.
if self.helo:IsAlive() then if self.helo and self.helo:IsAlive() then
------------------- -------------------
-- HELO is ALIVE -- -- HELO is ALIVE --
@@ -959,9 +966,10 @@ function RESCUEHELO:onafterStatus(From, Event, To)
local fuelrel=fuel/self.HeloFuel0 local fuelrel=fuel/self.HeloFuel0
local life=self.helo:GetUnit(1):GetLife() local life=self.helo:GetUnit(1):GetLife()
local life0=self.helo:GetUnit(1):GetLife0() local life0=self.helo:GetUnit(1):GetLife0()
local lifeR=self.helo:GetUnit(1):GetLifeRelative()
-- Report current fuel. -- Report current fuel.
local text=string.format("Rescue Helo %s: state=%s fuel=%.1f, rel.fuel=%.1f, life=%.1f/%.1f", self.helo:GetName(), self:GetState(), fuel, fuelrel, life, life0) local text=string.format("Rescue Helo %s: state=%s fuel=%.1f, rel.fuel=%.1f, life=%.1f/%.1f=%d", self.helo:GetName(), self:GetState(), fuel, fuelrel, life, life0, lifeR*100)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text) self:T(self.lid..text)
@@ -982,7 +990,7 @@ function RESCUEHELO:onafterStatus(From, Event, To)
-- Respawn helo in air. -- Respawn helo in air.
self.helo=self.helo:Respawn(nil, true) self.helo=self.helo:Respawn(nil, true)
-- XXX: ATTENTION: if helo automatically RTBs on low fuel, it goes a bit cazy. The formation is not stopped and he partially dives into the water. -- XXX: ATTENTION: if helo automatically RTBs on low fuel, it goes a bit crazy. The formation is not stopped and he partially dives into the water.
-- Also trying to find a ship to land on he flies right through it. -- Also trying to find a ship to land on he flies right through it.
--self.helo:OptionRTBBingoFuel(false) --self.helo:OptionRTBBingoFuel(false)
@@ -1016,6 +1024,8 @@ function RESCUEHELO:onafterStatus(From, Event, To)
if not self:IsStopped() then if not self:IsStopped() then
self:E(self.lid.."Rescue helo is NOT alive (and not stopped)!")
-- Stop FSM. -- Stop FSM.
self:Stop() self:Stop()
@@ -1047,18 +1057,6 @@ function RESCUEHELO:onafterRun(From, Event, To)
self.formation:Start() self.formation:Start()
end end
-- Restart route of carrier if it was stopped.
if self.carrierstop then
-- Debug info.
local text="Carrier resuming route after rescue operation."
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
-- Resume route of carrier.
self.carrier:RouteResume()
self.carrierstop=false
end
end end
@@ -1143,15 +1141,6 @@ function RESCUEHELO:onafterRescue(From, Event, To, RescueCoord)
-- Stop formation. -- Stop formation.
self.formation:Stop() self.formation:Stop()
-- Stop carrier.
if self.rescuestopboat then
local text="Stopping carrier for rescue operation."
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.carrier:RouteStop()
self.carrierstop=true
end
end end
--- On after RTB event. Send helo back to carrier. --- On after RTB event. Send helo back to carrier.
@@ -1170,21 +1159,6 @@ function RESCUEHELO:onafterRTB(From, Event, To, airbase)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text) self:T(self.lid..text)
--[[
-- Waypoint array.
local wp={}
-- Set landing waypoint at home base.
wp[1]=self.helo:GetCoordinate():WaypointAirTurningPoint(nil, 300, {}, "Current Position")
wp[2]=self.airbase:GetCoordinate():SetAltitude(70):WaypointAirLanding(300, self.airbase, {}, "Landing at Home Base")
-- Initialize WP and route helo.
self.helo:WayPointInitialize(wp)
-- Set task.
self.helo:Route(wp, 1)
]]
-- Stop formation. -- Stop formation.
if From=="Running" then if From=="Running" then
self.formation:Stop() self.formation:Stop()
@@ -1204,7 +1178,7 @@ function RESCUEHELO:onafterReturned(From, Event, To, airbase)
if airbase then if airbase then
local airbasename=airbase:GetName() local airbasename=airbase:GetName()
self:T(self.lid..string.format("Helo returned to airbase %s", tostring(airbasename))) self:I(self.lid..string.format("Helo returned to airbase %s", tostring(airbasename)))
else else
self:E(self.lid..string.format("WARNING: Helo landed but airbase (EventData.Place) is nil!")) self:E(self.lid..string.format("WARNING: Helo landed but airbase (EventData.Place) is nil!"))
end end
@@ -1238,6 +1212,9 @@ function RESCUEHELO:onafterStop(From, Event, To)
self:UnHandleEvent(EVENTS.Land) self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.Ejection)
-- Clear all pending FSM events.
self.CallScheduler:Clear()
-- If helo is alive, despawn it. -- If helo is alive, despawn it.
if self.helo and self.helo:IsAlive() then if self.helo and self.helo:IsAlive() then
@@ -1245,7 +1222,8 @@ function RESCUEHELO:onafterStop(From, Event, To)
self.helo:Destroy() self.helo:Destroy()
else else
self:I(self.lid.."Stopping FSM. Helo was not alive.") self:I(self.lid.."Stopping FSM. Helo was not alive.")
end end
end end
@@ -1260,13 +1238,13 @@ function RESCUEHELO:RouteRTB(RTBAirbase, Speed)
-- Curent (from) waypoint. -- Curent (from) waypoint.
local coord=self.helo:GetCoordinate() local coord=self.helo:GetCoordinate()
local PointFrom=coord:WaypointAirTurningPoint(nil, Speed) local PointFrom=coord:WaypointAirTurningPoint(nil, Speed, {}, "Current")
-- Airbase coordinate. -- Airbase coordinate.
local PointAirbase=RTBAirbase:GetCoordinate():SetAltitude(100):WaypointAirTurningPoint(nil ,Speed) --local PointAirbase=RTBAirbase:GetCoordinate():SetAltitude(100):WaypointAirTurningPoint(nil ,Speed)
-- Landing waypoint. More general than prev version since it should also work with FAPRS and ships. -- Landing waypoint. More general than prev version since it should also work with FAPRS and ships.
local PointLanding=RTBAirbase:GetCoordinate():SetAltitude(20):WaypointAirLanding(Speed, RTBAirbase) local PointLanding=RTBAirbase:GetCoordinate():SetAltitude(20):WaypointAirLanding(Speed, RTBAirbase, {}, "Landing")
-- Waypoint table. -- Waypoint table.
local Points={PointFrom, PointLanding} local Points={PointFrom, PointLanding}
@@ -1283,6 +1261,9 @@ function RESCUEHELO:RouteRTB(RTBAirbase, Speed)
-- Respawn the group. -- Respawn the group.
self.helo=self.helo:Respawn(Template, true) self.helo=self.helo:Respawn(Template, true)
-- Route the group or this will not work.
self.helo:Route(Points, 1)
return self return self
end end

View File

@@ -21,6 +21,11 @@ routines.build = 22
-- Utils- conversion, Lua utils, etc. -- Utils- conversion, Lua utils, etc.
routines.utils = {} routines.utils = {}
routines.utils.round = function(number, decimals)
local power = 10^decimals
return math.floor(number * power) / power
end
--from http://lua-users.org/wiki/CopyTable --from http://lua-users.org/wiki/CopyTable
routines.utils.deepCopy = function(object) routines.utils.deepCopy = function(object)
local lookup_table = {} local lookup_table = {}

View File

@@ -429,12 +429,12 @@ UTILS.tostringLL = function( lat, lon, acc, DMS)
local secFrmtStr -- create the formatting string for the seconds place local secFrmtStr -- create the formatting string for the seconds place
secFrmtStr = '%02d' secFrmtStr = '%02d'
-- if acc <= 0 then -- no decimal place. if acc <= 0 then -- no decimal place.
-- secFrmtStr = '%02d' secFrmtStr = '%02d'
-- else else
-- local width = 3 + acc -- 01.310 - that's a width of 6, for example. local width = 3 + acc -- 01.310 - that's a width of 6, for example.
-- secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
-- end end
return string.format('%03d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' return string.format('%03d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' '
.. string.format('%03d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi .. string.format('%03d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi

View File

@@ -311,7 +311,7 @@ AIRBASE.PersianGulf = {
-- @field #number OpenMed 72: Open/Shelter air airplane only. -- @field #number OpenMed 72: Open/Shelter air airplane only.
-- @field #number OpenBig 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. -- @field #number OpenBig 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there.
-- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots. -- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots.
-- @field #number HelicopterUnsable 216: Combines HelicopterOnly, OpenMed and OpenBig. -- @field #number HelicopterUsable 216: Combines HelicopterOnly, OpenMed and OpenBig.
-- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. -- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft.
AIRBASE.TerminalType = { AIRBASE.TerminalType = {
Runway=16, Runway=16,
@@ -578,7 +578,7 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
-- Put coordinates of free spots into table. -- Put coordinates of free spots into table.
local freespots={} local freespots={}
for _,_spot in pairs(parkingfree) do for _,_spot in pairs(parkingfree) do
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then
if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then
local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos)
table.insert(freespots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=true, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) table.insert(freespots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=true, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW})
@@ -589,6 +589,31 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
return freespots return freespots
end end
--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase.
-- @param #AIRBASE self
-- @param #number TerminalID The terminal ID of the parking spot.
-- @return #AIRBASE.ParkingSpot Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
function AIRBASE:GetParkingSpotData(TerminalID)
self:F({TerminalID=TerminalID})
-- Get parking data.
local parkingdata=self:GetParkingSpotsTable()
-- Debug output.
self:T2({parkingdata=parkingdata})
for _,_spot in pairs(parkingdata) do
local spot=_spot --#AIRBASE.ParkingSpot
self:E({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType})
if TerminalID==spot.TerminalID then
return spot
end
end
self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID))
return nil
end
--- Place markers of parking spots on the F10 map. --- Place markers of parking spots on the F10 map.
-- @param #AIRBASE self -- @param #AIRBASE self
-- @param #AIRBASE.TerminalType termtype Terminal type for which marks should be placed. -- @param #AIRBASE.TerminalType termtype Terminal type for which marks should be placed.
@@ -893,7 +918,7 @@ function AIRBASE:CheckOnRunWay(group, radius, despawn)
end end
--- Get category of airbase. --- Get category of airbase.
-- @param #WAREHOUSE self -- @param #AIRBASE self
-- @return #number Category of airbase from GetDesc().category. -- @return #number Category of airbase from GetDesc().category.
function AIRBASE:GetAirbaseCategory() function AIRBASE:GetAirbaseCategory()
return self:GetDesc().category return self:GetDesc().category

View File

@@ -2806,6 +2806,8 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad
return nil return nil
end end
--- Check if a target is detected.
-- @param Wrapper.Controllable#CONTROLLABLE self
function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK )
self:F2( self.ControllableName ) self:F2( self.ControllableName )
@@ -2851,7 +2853,7 @@ function CONTROLLABLE:OptionROEHoldFirePossible()
return nil return nil
end end
--- Holding weapons. --- Weapons Hold: AI will hold fire under all circumstances.
-- @param Wrapper.Controllable#CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE self
-- @return Wrapper.Controllable#CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE self
function CONTROLLABLE:OptionROEHoldFire() function CONTROLLABLE:OptionROEHoldFire()
@@ -2893,7 +2895,7 @@ function CONTROLLABLE:OptionROEReturnFirePossible()
return nil return nil
end end
--- Return fire. --- Return Fire: AI will only engage threats that shoot first.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:OptionROEReturnFire() function CONTROLLABLE:OptionROEReturnFire()
@@ -2935,7 +2937,7 @@ function CONTROLLABLE:OptionROEOpenFirePossible()
return nil return nil
end end
--- Openfire. --- Open Fire (Only Designated): AI will engage only targets specified in its taskings.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:OptionROEOpenFire() function CONTROLLABLE:OptionROEOpenFire()
@@ -2959,6 +2961,45 @@ function CONTROLLABLE:OptionROEOpenFire()
return nil return nil
end end
--- Can the CONTROLLABLE attack priority designated targets? Only for AIR!
-- @param #CONTROLLABLE self
-- @return #boolean
function CONTROLLABLE:OptionROEOpenFireWeaponFreePossible()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
if self:IsAir() then
return true
end
return false
end
return nil
end
--- Open Fire, Weapons Free (Priority Designated): AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking.
-- **Only for AIR units!**
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionROEOpenFireWeaponFree()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsAir() then
Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE )
end
return self
end
return nil
end
--- Can the CONTROLLABLE attack targets of opportunity? --- Can the CONTROLLABLE attack targets of opportunity?
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #boolean -- @return #boolean
@@ -3231,7 +3272,10 @@ end
function CONTROLLABLE:OptionRTBBingoFuel( RTB ) --R2.2 function CONTROLLABLE:OptionRTBBingoFuel( RTB ) --R2.2
self:F2( { self.ControllableName } ) self:F2( { self.ControllableName } )
RTB = RTB or true --RTB = RTB or true
if RTB==nil then
RTB=true
end
local DCSControllable = self:GetDCSObject() local DCSControllable = self:GetDCSObject()
if DCSControllable then if DCSControllable then

View File

@@ -349,7 +349,8 @@ function GROUP:Destroy( GenerateEvent, delay )
self:F2( self.GroupName ) self:F2( self.GroupName )
if delay and delay>0 then if delay and delay>0 then
SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay) --SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay)
self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent)
else else
local DCSGroup = self:GetDCSObject() local DCSGroup = self:GetDCSObject()

View File

@@ -612,8 +612,7 @@ end
--- Returns the unit's health. Dead units has health <= 1.0. --- Returns the unit's health. Dead units has health <= 1.0.
-- @param #UNIT self -- @param #UNIT self
-- @return #number The Unit's health value. -- @return #number The Unit's health value or -1 if unit does not exist any more.
-- @return #nil The DCS Unit is not existing or alive.
function UNIT:GetLife() function UNIT:GetLife()
self:F2( self.UnitName ) self:F2( self.UnitName )
@@ -629,8 +628,7 @@ end
--- Returns the Unit's initial health. --- Returns the Unit's initial health.
-- @param #UNIT self -- @param #UNIT self
-- @return #number The Unit's initial health value. -- @return #number The Unit's initial health value or 0 if unit does not exist any more.
-- @return #nil The DCS Unit is not existing or alive.
function UNIT:GetLife0() function UNIT:GetLife0()
self:F2( self.UnitName ) self:F2( self.UnitName )
@@ -644,6 +642,34 @@ function UNIT:GetLife0()
return 0 return 0
end end
--- Returns the unit's relative health.
-- @param #UNIT self
-- @return #number The Unit's relative health value, i.e. a number in [0,1] or -1 if unit does not exist any more.
function UNIT:GetLifeRelative()
self:F2(self.UnitName)
if self and self:IsAlive() then
local life0=self:GetLife0()
local lifeN=self:GetLife()
return lifeN/life0
end
return -1
end
--- Returns the unit's relative damage, i.e. 1-life.
-- @param #UNIT self
-- @return #number The Unit's relative health value, i.e. a number in [0,1] or 1 if unit does not exist any more.
function UNIT:GetDamageRelative()
self:F2(self.UnitName)
if self and self:IsAlive() then
return 1-self:GetLifeRelative()
end
return 1
end
--- Returns the category name of the #UNIT. --- Returns the category name of the #UNIT.
-- @param #UNIT self -- @param #UNIT self
-- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship -- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship