diff --git a/Moose Development/Dcs/DCStrigger.lua b/Moose Development/Dcs/DCStrigger.lua new file mode 100644 index 000000000..408e75689 --- /dev/null +++ b/Moose Development/Dcs/DCStrigger.lua @@ -0,0 +1,5 @@ +------------------------------------------------------------------------------- +-- @module DCStrigger + + +trigger = {} --#timer diff --git a/Moose Development/Documentation/FAC_Detection logic.pdf b/Moose Development/Documentation/FAC_Detection logic.pdf new file mode 100644 index 000000000..e2e1cac38 Binary files /dev/null and b/Moose Development/Documentation/FAC_Detection logic.pdf differ diff --git a/Moose Development/Moose/AIBalancer.lua b/Moose Development/Moose/AIBalancer.lua index 2c4fbbb2a..8b9e32c20 100644 --- a/Moose Development/Moose/AIBalancer.lua +++ b/Moose Development/Moose/AIBalancer.lua @@ -153,7 +153,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetPointVec2(), self.ReturnTresholdRange ) + local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) self:E( RangeZone ) @@ -183,7 +183,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() else -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetPointVec2().x, AIGroup:GetPointVec2().y ) + local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) self:T( ClosestAirbase.AirbaseName ) AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) diff --git a/Moose Development/Moose/AirbasePolice.lua b/Moose Development/Moose/AirbasePolice.lua index 24341cd04..9229a67cd 100644 --- a/Moose Development/Moose/AirbasePolice.lua +++ b/Moose Development/Moose/AirbasePolice.lua @@ -157,6 +157,7 @@ function AIRBASEPOLICE_BASE:_AirbaseMonitor() self:SetState( self, "Taxi", true ) end + -- TODO: GetVelocityKMH function usage local VelocityVec3 = Client:GetVelocity() local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec local Velocity = Velocity * 3.6 -- now it is in km/h. diff --git a/Moose Development/Moose/Base.lua b/Moose Development/Moose/Base.lua index 0c7361e41..b8559f421 100644 --- a/Moose Development/Moose/Base.lua +++ b/Moose Development/Moose/Base.lua @@ -55,8 +55,9 @@ -- -- ==== -- +-- ### Author: FlightControl +-- -- @module Base --- @author FlightControl @@ -115,7 +116,6 @@ function BASE:New() self.__index = self _ClassID = _ClassID + 1 self.ClassID = _ClassID - self.ClassNameAndID = string.format( '%s#%09d', self.ClassName, self.ClassID ) return self end @@ -132,8 +132,7 @@ function BASE:Inherit( Child, Parent ) setmetatable( Child, Parent ) Child.__index = Child end - --Child.ClassName = Child.ClassName .. '.' .. Child.ClassID - self:T( 'Inherited from ' .. Parent.ClassName ) + --self:T( 'Inherited from ' .. Parent.ClassName ) return Child end @@ -141,7 +140,7 @@ end -- @param #BASE self -- @param #BASE Child is the Child class from which the Parent class needs to be retrieved. -- @return #BASE -function BASE:Inherited( Child ) +function BASE:GetParent( Child ) local Parent = getmetatable( Child ) -- env.info('Inherited class of ' .. Child.ClassName .. ' is ' .. Parent.ClassName ) return Parent @@ -152,7 +151,7 @@ end -- @param #BASE self -- @return #string The ClassName + ClassID of the class instance. function BASE:GetClassNameAndID() - return self.ClassNameAndID + return string.format( '%s#%09d', self.ClassName, self.ClassID ) end --- Get the ClassName of the class instance. @@ -339,11 +338,9 @@ function BASE:SetState( Object, StateName, State ) local ClassNameAndID = Object:GetClassNameAndID() - if not self.States[ClassNameAndID] then - self.States[ClassNameAndID] = {} - end + self.States[ClassNameAndID] = self.States[ClassNameAndID] or {} self.States[ClassNameAndID][StateName] = State - self:F2( { ClassNameAndID, StateName, State } ) + self:T2( { ClassNameAndID, StateName, State } ) return self.States[ClassNameAndID][StateName] end @@ -354,7 +351,7 @@ function BASE:GetState( Object, StateName ) if self.States[ClassNameAndID] then local State = self.States[ClassNameAndID][StateName] - self:F2( { ClassNameAndID, StateName, State } ) + self:T2( { ClassNameAndID, StateName, State } ) return State end @@ -379,7 +376,7 @@ end -- When Moose is loaded statically, (as one file), tracing is switched off by default. -- So tracing must be switched on manually in your mission if you are using Moose statically. -- When moose is loading dynamically (for moose class development), tracing is switched on by default. --- @param BASE self +-- @param #BASE self -- @param #boolean TraceOnOff Switch the tracing on or off. -- @usage -- -- Switch the tracing On @@ -391,6 +388,19 @@ function BASE:TraceOnOff( TraceOnOff ) _TraceOnOff = TraceOnOff end + +--- Enquires if tracing is on (for the class). +-- @param #BASE self +-- @return #boolean +function BASE:IsTrace() + + if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + return true + else + return false + end +end + --- Set trace level -- @param #BASE self -- @param #number Level diff --git a/Moose Development/Moose/Controllable.lua b/Moose Development/Moose/Controllable.lua index 4c42cc639..db46f5dae 100644 --- a/Moose Development/Moose/Controllable.lua +++ b/Moose Development/Moose/Controllable.lua @@ -600,7 +600,7 @@ function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) local DCSControllable = self:GetDCSObject() if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) end @@ -777,7 +777,7 @@ function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) if RandomPoint then Point = Zone:GetRandomVec2() else - Point = Zone:GetPointVec2() + Point = Zone:GetVec2() end local DCSTask = self:TaskLandAtVec2( Point, Duration ) @@ -1377,7 +1377,7 @@ end function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) self:F2( { Point, Speed } ) - local ControllablePoint = self:GetUnit( 1 ):GetPointVec2() + local ControllablePoint = self:GetUnit( 1 ):GetVec2() local PointFrom = {} PointFrom.x = ControllablePoint.x @@ -1516,7 +1516,7 @@ function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() local PointFrom = {} PointFrom.x = ControllablePoint.x @@ -1532,7 +1532,7 @@ function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) if Randomize then ZonePoint = Zone:GetRandomVec2() else - ZonePoint = Zone:GetPointVec2() + ZonePoint = Zone:GetVec2() end PointTo.x = ZonePoint.x @@ -1614,7 +1614,7 @@ function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() local ControllableVelocity = self:GetMaxVelocity() local PointFrom = {} @@ -1626,7 +1626,7 @@ function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) local PointTo = {} - local AirbasePoint = ReturnAirbase:GetPointVec2() + local AirbasePoint = ReturnAirbase:GetVec2() PointTo.x = AirbasePoint.x PointTo.y = AirbasePoint.y diff --git a/Moose Development/Moose/Database.lua b/Moose Development/Moose/Database.lua index 1dcc70cdb..21563032f 100644 --- a/Moose Development/Moose/Database.lua +++ b/Moose Development/Moose/Database.lua @@ -120,7 +120,6 @@ function DATABASE:AddUnit( DCSUnitName ) if not self.UNITS[DCSUnitName] then local UnitRegister = UNIT:Register( DCSUnitName ) - self:E( UnitRegister.UnitName ) self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) end diff --git a/Moose Development/Moose/Detection.lua b/Moose Development/Moose/Detection.lua index b4a26d88d..bff709d9f 100644 --- a/Moose Development/Moose/Detection.lua +++ b/Moose Development/Moose/Detection.lua @@ -5,6 +5,7 @@ -- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} -- ========================================================== -- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. +-- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). -- -- 1.1) DETECTION_BASE constructor -- ------------------------------- @@ -33,16 +34,16 @@ -- -- === -- --- 2) @{Detection#DETECTION_UNITGROUPS} class, extends @{Detection#DETECTION_BASE} +-- 2) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} -- =============================================================================== --- The @{Detection#DETECTION_UNITGROUPS} class will detect units within the battle zone for a FAC group, +-- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), -- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. -- -- 2.1) Retrieve the Detected Unit sets and Detected Zones -- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_UNITGROUPS}. +-- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. -- -- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. -- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). @@ -54,53 +55,59 @@ -- -- 1.4) Flare or Smoke detected units -- ---------------------------------- --- Use the methods @{Detection#DETECTION_UNITGROUPS.FlareDetectedUnits}() or @{Detection#DETECTION_UNITGROUPS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. +-- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. -- -- 1.5) Flare or Smoke detected zones -- ---------------------------------- --- Use the methods @{Detection#DETECTION_UNITGROUPS.FlareDetectedZones}() or @{Detection#DETECTION_UNITGROUPS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. +-- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. -- -- === -- +-- ### Contributions: Mechanic - Concept & Testing +-- ### Authors: FlightControl : Design & Programming +-- -- @module Detection --- @author Mechanic : Concept & Testing --- @author FlightControl : Design & Programming --- DETECTION_BASE class -- @type DETECTION_BASE --- @field Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +-- @field Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @field DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @field #DETECTION_BASE.DetectedSets DetectedSets A list of @{Set#SET_BASE}s containing the objects in each set that were detected. The base class will not build the detected sets, but will leave that to the derived classes. +-- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. +-- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. +-- @field #number DetectionRun -- @extends Base#BASE DETECTION_BASE = { ClassName = "DETECTION_BASE", - DetectedSets = {}, - DetectedObjects = {}, - FACGroup = nil, + DetectionSetGroup = nil, DetectionRange = nil, + DetectedObjects = {}, + DetectionRun = 0, + DetectedObjectsIdentified = {}, } ---- @type DETECTION_BASE.DetectedSets --- @list - - ---- @type DETECTION_BASE.DetectedZones --- @list +--- @type DETECTION_BASE.DetectedObjects +-- @list <#DETECTION_BASE.DetectedObject> +--- @type DETECTION_BASE.DetectedObject +-- @field #string Name +-- @field #boolean Visible +-- @field #string Type +-- @field #number Distance +-- @field #boolean Identified --- DETECTION constructor. -- @param #DETECTION_BASE self --- @param Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +-- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @return #DETECTION_BASE self -function DETECTION_BASE:New( FACGroup, DetectionRange ) +function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) - self.FACGroup = FACGroup + self.DetectionSetGroup = DetectionSetGroup self.DetectionRange = DetectionRange self:InitDetectVisual( false ) @@ -172,13 +179,65 @@ function DETECTION_BASE:InitDetectDLINK( DetectDLINK ) self.DetectDLINK = DetectDLINK end ---- Gets the FAC group. +--- Determines if a detected object has already been identified during detection processing. -- @param #DETECTION_BASE self --- @return Group#GROUP self -function DETECTION_BASE:GetFACGroup() - self:F2() +-- @param #DETECTION_BASE.DetectedObject DetectedObject +-- @return #boolean true if already identified. +function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) + self:F3( DetectedObject.Name ) - return self.FACGroup + local DetectedObjectName = DetectedObject.Name + local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true + self:T3( DetectedObjectIdentified ) + return DetectedObjectIdentified +end + +--- Identifies a detected object during detection processing. +-- @param #DETECTION_BASE self +-- @param #DETECTION_BASE.DetectedObject DetectedObject +function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) + self:F( DetectedObject.Name ) + + local DetectedObjectName = DetectedObject.Name + self.DetectedObjectsIdentified[DetectedObjectName] = true +end + +--- UnIdentify a detected object during detection processing. +-- @param #DETECTION_BASE self +-- @param #DETECTION_BASE.DetectedObject DetectedObject +function DETECTION_BASE:UnIdentifyDetectedObject( DetectedObject ) + + local DetectedObjectName = DetectedObject.Name + self.DetectedObjectsIdentified[DetectedObjectName] = false +end + +--- UnIdentify all detected objects during detection processing. +-- @param #DETECTION_BASE self +function DETECTION_BASE:UnIdentifyAllDetectedObjects() + + self.DetectedObjectsIdentified = {} -- Table will be garbage collected. +end + +--- Gets a detected object with a given name. +-- @param #DETECTION_BASE self +-- @param #string ObjectName +-- @return #DETECTION_BASE.DetectedObject +function DETECTION_BASE:GetDetectedObject( ObjectName ) + self:F3( ObjectName ) + + if ObjectName then + local DetectedObject = self.DetectedObjects[ObjectName] + + -- Only return detected objects that are alive! + local DetectedUnit = UNIT:FindByName( ObjectName ) + if DetectedUnit and DetectedUnit:IsAlive() then + if self:IsDetectedObjectIdentified( DetectedObject ) == false then + return DetectedObject + end + end + end + + return nil end --- Get the detected @{Set#SET_BASE}s. @@ -213,6 +272,14 @@ function DETECTION_BASE:GetDetectedSet( Index ) return nil end +--- Get the detection Groups. +-- @param #DETECTION_BASE self +-- @return Group#GROUP +function DETECTION_BASE:GetDetectionSetGroup() + + local DetectionSetGroup = self.DetectionSetGroup + return DetectionSetGroup +end --- Make a DetectionSet table. This function will be overridden in the derived clsses. -- @param #DETECTION_BASE self @@ -224,6 +291,7 @@ function DETECTION_BASE:CreateDetectionSets() end + --- Schedule the DETECTION construction. -- @param #DETECTION_BASE self -- @param #number DelayTime The delay in seconds to wait the reporting. @@ -245,141 +313,195 @@ end function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:F2( { SchedulerName } ) - self.DetectedObjects = {} - self.DetectedSets = {} - self.DetectedZones = {} + self.DetectionRun = self.DetectionRun + 1 - if self.FACGroup:IsAlive() then - local FACGroupName = self.FACGroup:GetName() - - local FACDetectedTargets = self.FACGroup:GetDetectedTargets( - self.DetectVisual, - self.DetectOptical, - self.DetectRadar, - self.DetectIRST, - self.DetectRWR, - self.DetectDLINK - ) - - for FACDetectedTargetID, FACDetectedTarget in pairs( FACDetectedTargets ) do - local FACObject = FACDetectedTarget.object -- DCSObject#Object - self:T2( FACObject ) + self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table + + for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do + local DetectionGroup = DetectionGroupData -- Group#GROUP + + if DetectionGroup:IsAlive() then + + local DetectionGroupName = DetectionGroup:GetName() - if FACObject and FACObject:isExist() and FACObject.id_ < 50000000 then - - local FACDetectedObjectName = FACObject:getName() - - local FACDetectedObjectPositionVec3 = FACObject:getPoint() - local FACGroupPositionVec3 = self.FACGroup:GetPointVec3() - - local Distance = ( ( FACDetectedObjectPositionVec3.x - FACGroupPositionVec3.x )^2 + - ( FACDetectedObjectPositionVec3.y - FACGroupPositionVec3.y )^2 + - ( FACDetectedObjectPositionVec3.z - FACGroupPositionVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T( { FACGroupName, FACDetectedObjectName, Distance } ) - - if Distance <= self.DetectionRange then - - if not self.DetectedObjects[FACDetectedObjectName] then - self.DetectedObjects[FACDetectedObjectName] = {} - end - self.DetectedObjects[FACDetectedObjectName].Name = FACDetectedObjectName - self.DetectedObjects[FACDetectedObjectName].Visible = FACDetectedTarget.visible - self.DetectedObjects[FACDetectedObjectName].Type = FACDetectedTarget.type - self.DetectedObjects[FACDetectedObjectName].Distance = FACDetectedTarget.distance - else - -- if beyond the DetectionRange then nullify... - if self.DetectedObjects[FACDetectedObjectName] then - self.DetectedObjects[FACDetectedObjectName] = nil + local DetectionDetectedTargets = DetectionGroup:GetDetectedTargets( + self.DetectVisual, + self.DetectOptical, + self.DetectRadar, + self.DetectIRST, + self.DetectRWR, + self.DetectDLINK + ) + + for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do + local DetectionObject = DetectionDetectedTarget.object -- DCSObject#Object + self:T2( DetectionObject ) + + if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then + + local DetectionDetectedObjectName = DetectionObject:getName() + + local DetectionDetectedObjectPositionVec3 = DetectionObject:getPoint() + local DetectionGroupPositionVec3 = DetectionGroup:GetPointVec3() + + local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupPositionVec3.x )^2 + + ( DetectionDetectedObjectPositionVec3.y - DetectionGroupPositionVec3.y )^2 + + ( DetectionDetectedObjectPositionVec3.z - DetectionGroupPositionVec3.z )^2 + ) ^ 0.5 / 1000 + + self:T2( { DetectionGroupName, DetectionDetectedObjectName, Distance } ) + + if Distance <= self.DetectionRange then + + if not self.DetectedObjects[DetectionDetectedObjectName] then + self.DetectedObjects[DetectionDetectedObjectName] = {} + end + self.DetectedObjects[DetectionDetectedObjectName].Name = DetectionDetectedObjectName + self.DetectedObjects[DetectionDetectedObjectName].Visible = DetectionDetectedTarget.visible + self.DetectedObjects[DetectionDetectedObjectName].Type = DetectionDetectedTarget.type + self.DetectedObjects[DetectionDetectedObjectName].Distance = DetectionDetectedTarget.distance + else + -- if beyond the DetectionRange then nullify... + if self.DetectedObjects[DetectionDetectedObjectName] then + self.DetectedObjects[DetectionDetectedObjectName] = nil + end end end end + + self:T2( self.DetectedObjects ) + + -- okay, now we have a list of detected object names ... + -- Sort the table based on distance ... + table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) end - - self:T2( self.DetectedObjects ) - - -- okay, now we have a list of detected object names ... - -- Sort the table based on distance ... - self:T( { "Sorting DetectedObjects table:", self.DetectedObjects } ) - table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) - self:T( { "Sorted Targets Table:", self.DetectedObjects } ) - - -- Now group the DetectedObjects table into SET_BASEs, evaluating the DetectionZoneRange. - - if self.DetectedObjects then - self:CreateDetectionSets() - end - - end + + if self.DetectedObjects then + self:CreateDetectionSets() + end + + return true end ---- @type DETECTION_UNITGROUPS.DetectedSets --- @list --- - - ---- @type DETECTION_UNITGROUPS.DetectedZones --- @list --- ---- DETECTION_UNITGROUPS class --- @type DETECTION_UNITGROUPS --- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @field #DETECTION_UNITGROUPS.DetectedSets DetectedSets A list of @{Set#SET_UNIT}s containing the units in each set that were detected within a DetectionZoneRange. --- @field #DETECTION_UNITGROUPS.DetectedZones DetectedZones A list of @{Zone#ZONE_UNIT}s containing the zones of the reference detected units. +--- DETECTION_AREAS class +-- @type DETECTION_AREAS +-- @field DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @field #DETECTION_AREAS.DetectedAreas DetectedAreas A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. -- @extends Detection#DETECTION_BASE -DETECTION_UNITGROUPS = { - ClassName = "DETECTION_UNITGROUPS", - DetectedZones = {}, +DETECTION_AREAS = { + ClassName = "DETECTION_AREAS", + DetectedAreas = { n = 0 }, + DetectionZoneRange = nil, } +--- @type DETECTION_AREAS.DetectedAreas +-- @list <#DETECTION_AREAS.DetectedArea> + +--- @type DETECTION_AREAS.DetectedArea +-- @field Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @field Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @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 #number AreaID -- The identifier of the detected area. +-- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. ---- DETECTION_UNITGROUPS constructor. --- @param Detection#DETECTION_UNITGROUPS self --- @param Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +--- DETECTION_AREAS constructor. +-- @param Detection#DETECTION_AREAS self +-- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Detection#DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:New( FACGroup, DetectionRange, DetectionZoneRange ) +-- @return Detection#DETECTION_AREAS self +function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( FACGroup, DetectionRange ) ) + local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) ) + self.DetectionZoneRange = DetectionZoneRange - self:Schedule( 10, 30 ) + self._SmokeDetectedUnits = false + self._FlareDetectedUnits = false + self._SmokeDetectedZones = false + self._FlareDetectedZones = false + + self:Schedule( 0, 15 ) return self end ---- Get the detected @{Zone#ZONE_UNIT}s. --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS.DetectedZones DetectedZones -function DETECTION_UNITGROUPS:GetDetectedZones() - - local DetectedZones = self.DetectedZones - return DetectedZones +--- Add a detected @{#DETECTION_AREAS.DetectedArea}. +-- @param Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @param Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @return #DETECTION_AREAS.DetectedArea DetectedArea +function DETECTION_AREAS:AddDetectedArea( Set, Zone ) + local DetectedAreas = self:GetDetectedAreas() + DetectedAreas.n = self:GetDetectedAreaCount() + 1 + DetectedAreas[DetectedAreas.n] = {} + local DetectedArea = DetectedAreas[DetectedAreas.n] + DetectedArea.Set = Set + DetectedArea.Zone = Zone + DetectedArea.Removed = false + DetectedArea.AreaID = DetectedAreas.n + + return DetectedArea end ---- Get the amount of @{Zone#ZONE_UNIT}s with detected units. --- @param #DETECTION_UNITGROUPS self --- @return #number Count -function DETECTION_UNITGROUPS:GetDetectedZoneCount() - - local DetectedZoneCount = #self.DetectedZones - return DetectedZoneCount +--- Remove a detected @{#DETECTION_AREAS.DetectedArea} with a given Index. +-- @param #DETECTION_AREAS self +-- @param #number Index The Index of the detection are to be removed. +-- @return #nil +function DETECTION_AREAS:RemoveDetectedArea( Index ) + local DetectedAreas = self:GetDetectedAreas() + local DetectedAreaCount = self:GetDetectedAreaCount() + local DetectedArea = DetectedAreas[Index] + local DetectedAreaSet = DetectedArea.Set + DetectedArea[Index] = nil + return nil end ---- Get a SET of detected objects using a given numeric index. --- @param #DETECTION_UNITGROUPS self + +--- Get the detected @{#DETECTION_AREAS.DetectedAreas}. +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS.DetectedAreas DetectedAreas +function DETECTION_AREAS:GetDetectedAreas() + + local DetectedAreas = self.DetectedAreas + return DetectedAreas +end + +--- Get the amount of @{#DETECTION_AREAS.DetectedAreas}. +-- @param #DETECTION_AREAS self +-- @return #number DetectedAreaCount +function DETECTION_AREAS:GetDetectedAreaCount() + + local DetectedAreaCount = self.DetectedAreas.n + return DetectedAreaCount +end + +--- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. +-- @param #DETECTION_AREAS self -- @param #number Index --- @return Zone#ZONE_UNIT -function DETECTION_UNITGROUPS:GetDetectedZone( Index ) +-- @return Set#SET_UNIT DetectedSet +function DETECTION_AREAS:GetDetectedSet( Index ) - local DetectedZone = self.DetectedZones[Index] + local DetectedSetUnit = self.DetectedAreas[Index].Set + if DetectedSetUnit then + return DetectedSetUnit + end + + return nil +end + +--- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. +-- @param #DETECTION_AREAS self +-- @param #number Index +-- @return Zone#ZONE_UNIT DetectedZone +function DETECTION_AREAS:GetDetectedZone( Index ) + + local DetectedZone = self.DetectedAreas[Index].Zone if DetectedZone then return DetectedZone end @@ -387,10 +509,107 @@ function DETECTION_UNITGROUPS:GetDetectedZone( Index ) return nil end +--- Background worker function to determine if there are friendlies nearby ... +-- @param #DETECTION_AREAS self +-- @param Unit#UNIT ReportUnit +function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) + self:F2() + + local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT + + DetectedArea.FriendliesNearBy = false + + local SphereSearch = { + id = world.VolumeType.SPHERE, + params = { + point = DetectedZoneUnit:GetPointVec3(), + radius = 6000, + } + + } + + --- @param DCSUnit#Unit FoundDCSUnit + -- @param Group#GROUP ReportGroup + -- @param Set#SET_GROUP ReportSetGroup + local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) + + local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Unit#UNIT + local ReportSetGroup = ReportGroupData.ReportSetGroup + + local EnemyCoalition = DetectedZoneUnit:GetCoalition() + + local FoundUnitCoalition = FoundDCSUnit:getCoalition() + local FoundUnitName = FoundDCSUnit:getName() + local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() + local EnemyUnitName = DetectedZoneUnit:GetName() + local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil + + self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + + if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then + DetectedArea.FriendliesNearBy = true + return false + end + + return true + end + + world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) + +end + + + +--- Returns if there are friendlies nearby the FAC units ... +-- @param #DETECTION_AREAS self +-- @return #boolean trhe if there are friendlies nearby +function DETECTION_AREAS:IsFriendliesNearBy( DetectedArea ) + + self:T3( DetectedArea.FriendliesNearBy ) + return DetectedArea.FriendliesNearBy or false +end + +--- Calculate the maxium A2G threat level of the DetectedArea. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) + + local MaxThreatLevelA2G = 0 + for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do + local ThreatUnit = UnitData -- Unit#UNIT + local ThreatLevelA2G = ThreatUnit:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + end + end + + self:T3( MaxThreatLevelA2G ) + DetectedArea.MaxThreatLevelA2G = MaxThreatLevelA2G + +end + +--- Returns the A2G threat level of the units in the DetectedArea +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #number a scale from 0 to 10. +function DETECTION_AREAS:GetTreatLevelA2G( DetectedArea ) + + self:T3( DetectedArea.MaxThreatLevelA2G ) + return DetectedArea.MaxThreatLevelA2G +end + + + --- Smoke the detected units --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:SmokeDetectedUnits() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:SmokeDetectedUnits() self:F2() self._SmokeDetectedUnits = true @@ -398,9 +617,9 @@ function DETECTION_UNITGROUPS:SmokeDetectedUnits() end --- Flare the detected units --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:FlareDetectedUnits() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:FlareDetectedUnits() self:F2() self._FlareDetectedUnits = true @@ -408,9 +627,9 @@ function DETECTION_UNITGROUPS:FlareDetectedUnits() end --- Smoke the detected zones --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:SmokeDetectedZones() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:SmokeDetectedZones() self:F2() self._SmokeDetectedZones = true @@ -418,77 +637,310 @@ function DETECTION_UNITGROUPS:SmokeDetectedZones() end --- Flare the detected zones --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:FlareDetectedZones() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:FlareDetectedZones() self:F2() self._FlareDetectedZones = true return self end +--- Add a change to the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @param #string ChangeCode +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AddChangeArea( DetectedArea, ChangeCode, AreaUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode].AreaID = AreaID + DetectedArea.Changes[ChangeCode].AreaUnitType = AreaUnitType + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, AreaUnitType } ) + + return self +end + + +--- Add a change to the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @param #string ChangeCode +-- @param #string ChangeUnitType +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AddChangeUnit( DetectedArea, ChangeCode, ChangeUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] or 0 + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] + 1 + DetectedArea.Changes[ChangeCode].AreaID = AreaID + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, ChangeUnitType } ) + + return self +end + +--- Make text documenting the changes of the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #string The Changes text +function DETECTION_AREAS:GetChangeText( DetectedArea ) + self:F( DetectedArea ) + + local MT = {} + + for ChangeCode, ChangeData in pairs( DetectedArea.Changes ) do + + if ChangeCode == "AA" then + MT[#MT+1] = "Detected new area " .. ChangeData.AreaID .. ". The center target is a " .. ChangeData.AreaUnitType .. "." + end + + if ChangeCode == "RAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". Removed the center target " .. ChangeData.AreaUnitType "." + end + + if ChangeCode == "AAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". The new center target is a " .. ChangeData.AreaUnitType "." + end + + if ChangeCode == "RA" then + MT[#MT+1] = "Removed old area " .. ChangeData.AreaID .. ". No more targets in this area." + end + + if ChangeCode == "AU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Detected for area " .. ChangeData.AreaID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + if ChangeCode == "RU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Removed for area " .. ChangeData.AreaID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + end + + return table.concat( MT, "\n" ) + +end + + +--- Accepts changes from the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AcceptChanges( DetectedArea ) + + DetectedArea.Changed = false + DetectedArea.Changes = {} + + return self +end + --- Make a DetectionSet table. This function will be overridden in the derived clsses. --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:CreateDetectionSets() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:CreateDetectionSets() self:F2() - for DetectedUnitName, DetectedUnitData in pairs( self.DetectedObjects ) do - self:T( DetectedUnitData.Name ) - local DetectedUnit = UNIT:FindByName( DetectedUnitData.Name ) -- Unit#UNIT - if DetectedUnit and DetectedUnit:IsAlive() then - self:T( DetectedUnit:GetName() ) - if #self.DetectedSets == 0 then - self:T( { "Adding Unit Set #", 1 } ) - self.DetectedZones[1] = ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - self.DetectedSets[1] = SET_UNIT:New() - self.DetectedSets[1]:AddUnit( DetectedUnit ) + -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. + -- Regroup when needed, split groups when needed. + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + + local DetectedSet = DetectedArea.Set + + local AreaExists = false -- This flag will determine of the detected area is still existing. + + -- First test if the center unit is detected in the detection area. + self:T3( DetectedArea.Zone.ZoneUNIT.UnitName ) + local DetectedZoneObject = self:GetDetectedObject( DetectedArea.Zone.ZoneUNIT.UnitName ) + self:T3( { "Detecting Zone Object", DetectedArea.AreaID, DetectedArea.Zone, DetectedZoneObject } ) + + if DetectedZoneObject then + + --self:IdentifyDetectedObject( DetectedZoneObject ) + AreaExists = true + + + else - local AddedToSet = false - for DetectedZoneIndex = 1, #self.DetectedZones do - self:T( "Detected Unit Set #" .. DetectedZoneIndex ) - local DetectedUnitSet = self.DetectedSets[DetectedZoneIndex] -- Set#SET_BASE - local DetectedZone = self.DetectedZones[DetectedZoneIndex] -- Zone#ZONE_UNIT - if DetectedUnit:IsInZone( DetectedZone ) then - self:T( "Adding to Unit Set #" .. DetectedZoneIndex ) - DetectedUnitSet:AddUnit( DetectedUnit ) - AddedToSet = true + -- The center object of the detected area has not been detected. Find an other unit of the set to become the center of the area. + -- First remove the center unit from the set. + DetectedSet:RemoveUnitsByName( DetectedArea.Zone.ZoneUNIT.UnitName ) + + self:AddChangeArea( DetectedArea, 'RAU', DetectedArea.Zone.ZoneUNIT:GetTypeName() ) + + -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. + for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do + + local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) + + -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. + -- If the DetectedUnit was already identified, DetectedObject will be nil. + if DetectedObject then + self:IdentifyDetectedObject( DetectedObject ) + AreaExists = true + + -- Assign the Unit as the new center unit of the detected area. + DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) + + self:AddChangeArea( DetectedArea, "AAU", DetectedArea.Zone.ZoneUNIT:GetTypeName() ) + + -- We don't need to add the DetectedObject to the area set, because it is already there ... + break end end - if AddedToSet == false then - local DetectedZoneIndex = #self.DetectedZones + 1 - self:T( "Adding new zone #" .. DetectedZoneIndex ) - self.DetectedZones[DetectedZoneIndex] = ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - self.DetectedSets[DetectedZoneIndex] = SET_UNIT:New() - self.DetectedSets[DetectedZoneIndex]:AddUnit( DetectedUnit ) - end + end + + -- Now we've determined the center unit of the area, now we can iterate the units in the detected area. + -- Note that the position of the area may have moved due to the center unit repositioning. + -- If no center unit was identified, then the detected area does not exist anymore and should be deleted, as there are no valid units that can be the center unit. + if AreaExists then + + -- ok, we found the center unit of the area, now iterate through the detected area set and see which units are still within the center unit zone ... + -- Those units within the zone are flagged as Identified. + -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. + for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do + + local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedObject = nil + if DetectedUnit:IsAlive() then + --self:E(DetectedUnit:GetName()) + DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) + end + if DetectedObject then + + -- Check if the DetectedUnit is within the DetectedArea.Zone + if DetectedUnit:IsInZone( DetectedArea.Zone ) then + + -- Yes, the DetectedUnit is within the DetectedArea.Zone, no changes, DetectedUnit can be kept within the Set. + self:IdentifyDetectedObject( DetectedObject ) + + else + -- No, the DetectedUnit is not within the DetectedArea.Zone, remove DetectedUnit from the Set. + DetectedSet:Remove( DetectedUnitName ) + self:AddChangeUnit( DetectedArea, "RU", DetectedUnit:GetTypeName() ) + end + + else + -- There was no DetectedObject, remove DetectedUnit from the Set. + self:AddChangeUnit( DetectedArea, "RU", "destroyed target" ) + DetectedSet:Remove( DetectedUnitName ) + + -- The DetectedObject has been identified, because it does not exist ... + -- self:IdentifyDetectedObject( DetectedObject ) + end + end + else + self:RemoveDetectedArea( DetectedAreaID ) + self:AddChangeArea( DetectedArea, "RA" ) end end end - -- Now all the tests should have been build, now make some smoke and flares... + -- We iterated through the existing detection areas and: + -- - We checked which units are still detected in each detection area. Those units were flagged as Identified. + -- - We recentered the detection area to new center units where it was needed. + -- + -- Now we need to loop through the unidentified detected units and see where they belong: + -- - They can be added to a new detection area and become the new center unit. + -- - They can be added to a new detection area. + for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do + + local DetectedObject = self:GetDetectedObject( DetectedUnitName ) + + if DetectedObject then + + -- We found an unidentified unit outside of any existing detection area. + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Unit#UNIT + + local AddedToDetectionArea = false + + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + self:T( "Detection Area #" .. DetectedArea.AreaID ) + local DetectedSet = DetectedArea.Set + if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedArea.Zone ) then + self:IdentifyDetectedObject( DetectedObject ) + DetectedSet:AddUnit( DetectedUnit ) + AddedToDetectionArea = true + self:AddChangeUnit( DetectedArea, "AU", DetectedUnit:GetTypeName() ) + end + end + end + + if AddedToDetectionArea == false then + + -- New detection area + local DetectedArea = self:AddDetectedArea( + SET_UNIT:New(), + ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) + ) + --self:E( DetectedArea.Zone.ZoneUNIT.UnitName ) + DetectedArea.Set:AddUnit( DetectedUnit ) + self:AddChangeArea( DetectedArea, "AA", DetectedUnit:GetTypeName() ) + end + end + end - for DetectedZoneIndex = 1, #self.DetectedZones do - local DetectedUnitSet = self.DetectedSets[DetectedZoneIndex] -- Set#SET_BASE - local DetectedZone = self.DetectedZones[DetectedZoneIndex] -- Zone#ZONE_UNIT - self:T( "Detected Set #" .. DetectedZoneIndex ) - DetectedUnitSet:ForEachUnit( + -- Now all the tests should have been build, now make some smoke and flares... + -- We also report here the friendlies within the detected areas. + + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + self:ReportFriendliesNearBy( { DetectedArea = DetectedArea, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table + self:CalculateThreatLevelA2G( DetectedArea ) -- Calculate A2G threat level + + if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then + DetectedZone.ZoneUNIT:SmokeRed() + end + DetectedSet:ForEachUnit( --- @param Unit#UNIT DetectedUnit function( DetectedUnit ) - self:T( DetectedUnit:GetName() ) - if self._FlareDetectedUnits then - DetectedUnit:FlareRed() - end - if self._SmokeDetectedUnits then - DetectedUnit:SmokeRed() + if DetectedUnit:IsAlive() then + self:T( "Detected Set #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) + if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then + DetectedUnit:FlareGreen() + end + if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then + DetectedUnit:SmokeGreen() + end end end ) - if self._FlareDetectedZones then + if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then DetectedZone:FlareZone( POINT_VEC3.SmokeColor.White, 30, math.random( 0,90 ) ) end - if self._SmokeDetectedZones then + if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then DetectedZone:SmokeZone( POINT_VEC3.SmokeColor.White, 30 ) end end diff --git a/Moose Development/Moose/DetectionManager.lua b/Moose Development/Moose/DetectionManager.lua new file mode 100644 index 000000000..ec8a868bf --- /dev/null +++ b/Moose Development/Moose/DetectionManager.lua @@ -0,0 +1,504 @@ +--- This module contains the DETECTION_MANAGER class and derived classes. +-- +-- === +-- +-- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} +-- ==================================================================== +-- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. +-- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. +-- +-- 1.1) DETECTION_MANAGER constructor: +-- ----------------------------------- +-- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. +-- +-- 1.2) DETECTION_MANAGER reporting: +-- --------------------------------- +-- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. +-- +-- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). +-- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). +-- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. +-- +-- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. +-- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). +-- +-- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. +-- +-- === +-- +-- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} +-- ========================================================================================= +-- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. +-- +-- 2.1) DETECTION_REPORTING constructor: +-- ------------------------------- +-- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. +-- +-- === +-- +-- 3) @{#DETECTION_DISPATCHER} class, extends @{#DETECTION_MANAGER} +-- ================================================================ +-- The @{#DETECTION_DISPATCHER} class implements the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of FAC (groups). +-- The FAC will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. +-- Find a summary below describing for which situation a task type is created: +-- +-- * **CAS Task**: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter. +-- * **BAI Task**: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter. +-- * **SEAD Task**: Is created when there are enemy ground units wihtin range of the FAC, with air search radars. +-- +-- Other task types will follow... +-- +-- 3.1) DETECTION_DISPATCHER constructor: +-- -------------------------------------- +-- The @{#DETECTION_DISPATCHER.New}() method creates a new DETECTION_DISPATCHER instance. +-- +-- === +-- +-- ### Contributions: Mechanic, Prof_Hilactic, FlightControl - Concept & Testing +-- ### Author: FlightControl - Framework Design & Programming +-- +-- @module DetectionManager + +do -- DETECTION MANAGER + + --- DETECTION_MANAGER class. + -- @type DETECTION_MANAGER + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @extends Base#BASE + DETECTION_MANAGER = { + ClassName = "DETECTION_MANAGER", + SetGroup = nil, + Detection = nil, + } + + --- FAC constructor. + -- @param #DETECTION_MANAGER self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:New( SetGroup, Detection ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) -- Detection#DETECTION_MANAGER + + self.SetGroup = SetGroup + self.Detection = Detection + + self:SetReportInterval( 30 ) + self:SetReportDisplayTime( 25 ) + + return self + end + + --- Set the reporting time interval. + -- @param #DETECTION_MANAGER self + -- @param #number ReportInterval The interval in seconds when a report needs to be done. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:SetReportInterval( ReportInterval ) + self:F2() + + self._ReportInterval = ReportInterval + end + + + --- Set the reporting message display time. + -- @param #DETECTION_MANAGER self + -- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime ) + self:F2() + + self._ReportDisplayTime = ReportDisplayTime + end + + --- Get the reporting message display time. + -- @param #DETECTION_MANAGER self + -- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. + function DETECTION_MANAGER:GetReportDisplayTime() + self:F2() + + return self._ReportDisplayTime + end + + + + --- Reports the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_MANAGER self + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:ReportDetected( Detection ) + self:F2() + + end + + --- Schedule the FAC reporting. + -- @param #DETECTION_MANAGER self + -- @param #number DelayTime The delay in seconds to wait the reporting. + -- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:Schedule( DelayTime, ReportInterval ) + self:F2() + + self._ScheduleDelayTime = DelayTime + + self:SetReportInterval( ReportInterval ) + + self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "DetectionManager" }, self._ScheduleDelayTime, self._ReportInterval ) + return self + end + + --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. + -- @param #DETECTION_MANAGER self + function DETECTION_MANAGER:_FacScheduler( SchedulerName ) + self:F2( { SchedulerName } ) + + return self:ProcessDetected( self.Detection ) + +-- self.SetGroup:ForEachGroup( +-- --- @param Group#GROUP Group +-- function( Group ) +-- if Group:IsAlive() then +-- return self:ProcessDetected( self.Detection ) +-- end +-- end +-- ) + +-- return true + end + +end + + +do -- DETECTION_REPORTING + + --- DETECTION_REPORTING class. + -- @type DETECTION_REPORTING + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @extends #DETECTION_MANAGER + DETECTION_REPORTING = { + ClassName = "DETECTION_REPORTING", + } + + + --- DETECTION_REPORTING constructor. + -- @param #DETECTION_REPORTING self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_AREAS Detection + -- @return #DETECTION_REPORTING self + function DETECTION_REPORTING:New( SetGroup, Detection ) + + -- Inherits from DETECTION_MANAGER + local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING + + self:Schedule( 1, 30 ) + return self + end + + --- Creates a string of the detected items in a @{Detection}. + -- @param #DETECTION_MANAGER self + -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. + -- @return #DETECTION_MANAGER self + function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) + self:F2() + + local MT = {} -- Message Text + local UnitTypes = {} + + for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do + local DetectedUnit = DetectedUnitData -- Unit#UNIT + if DetectedUnit:IsAlive() then + local UnitType = DetectedUnit:GetTypeName() + + if not UnitTypes[UnitType] then + UnitTypes[UnitType] = 1 + else + UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + end + end + end + + for UnitTypeID, UnitType in pairs( UnitTypes ) do + MT[#MT+1] = UnitType .. " of " .. UnitTypeID + end + + return table.concat( MT, ", " ) + end + + + + --- Reports the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_REPORTING self + -- @param Group#GROUP Group The @{Group} object to where the report needs to go. + -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. + -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. + function DETECTION_REPORTING:ProcessDetected( Group, Detection ) + self:F2( Group ) + + self:E( Group ) + local DetectedMsg = {} + for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do + local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) + end + local FACGroup = Detection:GetDetectionGroups() + FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group ) + + return true + end + +end + +do -- DETECTION_DISPATCHER + + --- DETECTION_DISPATCHER class. + -- @type DETECTION_DISPATCHER + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Mission#MISSION Mission + -- @field Group#GROUP CommandCenter + -- @extends DetectionManager#DETECTION_MANAGER + DETECTION_DISPATCHER = { + ClassName = "DETECTION_DISPATCHER", + Mission = nil, + CommandCenter = nil, + Detection = nil, + } + + + --- DETECTION_DISPATCHER constructor. + -- @param #DETECTION_DISPATCHER self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_DISPATCHER self + function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) + + -- Inherits from DETECTION_MANAGER + local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_DISPATCHER + + self.Detection = Detection + self.CommandCenter = CommandCenter + self.Mission = Mission + + self:Schedule( 30 ) + return self + end + + + --- Creates a SEAD task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Set#SET_UNIT TargetSetUnit: The target set of units. + -- @return #nil If there are no targets to be set. + function DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local RadarCount = DetectedSet:HasSEAD() + + if RadarCount > 0 then + + -- Here we're doing something advanced... We're copying the DetectedSet, but making a new Set only with SEADable Radar units in it. + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterHasSEAD() + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Creates a CAS task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local GroundUnitCount = DetectedSet:HasGroundUnits() + local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) + + if GroundUnitCount > 0 and FriendliesNearBy == true then + + -- Copy the Set + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Creates a BAI task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local GroundUnitCount = DetectedSet:HasGroundUnits() + local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) + + if GroundUnitCount > 0 and FriendliesNearBy == false then + + -- Copy the Set + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Evaluates the removal of the Task from the Mission. + -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". + -- @param #DETECTION_DISPATCHER self + -- @param Mission#MISSION Mission + -- @param Task#TASK_BASE Task + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) + + if Task then + if Task:IsStatePlanned() and DetectedArea.Changed == true then + Mission:RemoveTaskMenu( Task ) + Task = Mission:RemoveTask( Task ) + end + end + + return Task + end + + + --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. + -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. + function DETECTION_DISPATCHER:ProcessDetected( Detection ) + self:F2() + + local AreaMsg = {} + local TaskMsg = {} + local ChangeMsg = {} + + local Mission = self.Mission + + --- First we need to the detected targets. + for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do + + local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) + DetectedSet:Flush() + + local AreaID = DetectedArea.AreaID + + -- Evaluate SEAD Tasking + local SEADTask = Mission:GetTask( "SEAD." .. AreaID ) + SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedArea ) + if not SEADTask then + local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if SEADTask and SEADTask:IsStatePlanned() then + SEADTask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() + end + + -- Evaluate CAS Tasking + local CASTask = Mission:GetTask( "CAS." .. AreaID ) + CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedArea ) + if not CASTask then + local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + CASTask = Mission:AddTask( TASK_CAS:New( Mission, self.SetGroup, "CAS." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if CASTask and CASTask:IsStatePlanned() then + CASTask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() + end + + -- Evaluate BAI Tasking + local BAITask = Mission:GetTask( "BAI." .. AreaID ) + BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedArea ) + if not BAITask then + local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + BAITask = Mission:AddTask( TASK_BAI:New( Mission, self.SetGroup, "BAI." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if BAITask and BAITask:IsStatePlanned() then + BAITask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() + end + + if #TaskMsg > 0 then + + local ThreatLevel = Detection:GetTreatLevelA2G( DetectedArea ) + + local DetectedAreaVec3 = DetectedZone:GetPointVec3() + local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) + local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) + AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", + DetectedAreaID, + DetectedAreaPointLL, + string.rep( "â– ", ThreatLevel ), + ThreatLevel + ) + + -- Loop through the changes ... + local ChangeText = Detection:GetChangeText( DetectedArea ) + + if ChangeText ~= "" then + ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) + end + end + + -- OK, so the tasking has been done, now delete the changes reported for the area. + Detection:AcceptChanges( DetectedArea ) + + end + + if #AreaMsg > 0 then + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not TaskGroup:GetState( TaskGroup, "Assigned" ) then + self.CommandCenter:MessageToGroup( + string.format( "HQ Reporting - Target areas for mission '%s':\nAreas:\n%s\n\nTasks:\n%s\n\nChanges:\n%s ", + self.Mission:GetName(), + table.concat( AreaMsg, "\n" ), + table.concat( TaskMsg, "\n" ), + table.concat( ChangeMsg, "\n" ) + ), self:GetReportDisplayTime(), TaskGroup + ) + end + end + end + + return true + end + +end \ No newline at end of file diff --git a/Moose Development/Moose/Escort.lua b/Moose Development/Moose/Escort.lua index e03c871c9..4143efd82 100644 --- a/Moose Development/Moose/Escort.lua +++ b/Moose Development/Moose/Escort.lua @@ -674,7 +674,7 @@ function ESCORT._HoldPosition( MenuParam ) PointFrom.alt = GroupPoint.y PointFrom.alt_type = AI.Task.AltitudeType.BARO - local OrbitPoint = OrbitUnit:GetPointVec2() + local OrbitPoint = OrbitUnit:GetVec2() local PointTo = {} PointTo.x = OrbitPoint.x PointTo.y = OrbitPoint.y @@ -867,7 +867,7 @@ function ESCORT._AttackTarget( MenuParam ) SCHEDULER:New( EscortGroup, EscortGroup.PushTask, { EscortGroup:TaskCombo( - { EscortGroup:TaskFireAtPoint( AttackUnit:GetPointVec2(), 50 ) + { EscortGroup:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) } ) }, 10 @@ -907,7 +907,7 @@ function ESCORT._AssistTarget( MenuParam ) SCHEDULER:New( EscortGroupAttack, EscortGroupAttack.PushTask, { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetPointVec2(), 50 ) + { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) } ) }, 10 diff --git a/Moose Development/Moose/Event.lua b/Moose Development/Moose/Event.lua index 7f36aa946..fe1e80059 100644 --- a/Moose Development/Moose/Event.lua +++ b/Moose Development/Moose/Event.lua @@ -94,6 +94,18 @@ function EVENT:Init( EventID, EventClass ) return self.Events[EventID][EventClass] end +--- Removes an Events entry +-- @param #EVENT self +-- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param DCSWorld#world.event EventID +-- @return #EVENT.Events +function EVENT:Remove( EventSelf, EventID ) + self:F3( { EventSelf, _EVENTCODES[EventID] } ) + + local EventClass = EventSelf:GetClassNameAndID() + self.Events[EventID][EventClass] = nil +end + --- Create an OnDead event handler for a group -- @param #EVENT self @@ -147,328 +159,533 @@ function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, Event return self end +do -- OnBirth ---- Create an OnBirth event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + --- Create an OnBirth event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnBirth( EventFunction, EventSelf ) - self:F2() + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + + return self + end - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirth( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end - return self -end + --- Set a new listener for an S_EVENT_BIRTH event. + -- @param #EVENT self + -- @param #string EventDCSUnitName The id of the unit for the event to be handled. + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end ---- Set a new listener for an S_EVENT_BIRTH event. --- @param #EVENT self --- @param #string EventDCSUnitName The id of the unit for the event to be handled. --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) + --- Stop listening to S_EVENT_BIRTH event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirthRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + +end + +do -- OnCrash + + --- Create an OnCrash event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Create an OnCrash event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) - - return self -end - ---- Set a new listener for an S_EVENT_CRASH event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnCrash( EventFunction, EventSelf ) - self:F2() + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + return self + end - return self -end - ---- Set a new listener for an S_EVENT_CRASH event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) + --- Set a new listener for an S_EVENT_CRASH event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnCrash( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + + return self + end - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) + --- Set a new listener for an S_EVENT_CRASH event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) + return self + end + + --- Stop listening to S_EVENT_CRASH event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnCrashRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_CRASH ) + + return self + end - return self end ---- Set a new listener for an S_EVENT_DEAD event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnDead( EventFunction, EventSelf ) - self:F2() +do -- OnDead + + --- Create an OnDead event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + return self + end - return self -end - - ---- Set a new listener for an S_EVENT_DEAD event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + --- Set a new listener for an S_EVENT_DEAD event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnDead( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_PILOT_DEAD event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) - return self -end - ---- Set a new listener for an S_EVENT_LAND event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) - - return self -end - ---- Set a new listener for an S_EVENT_TAKEOFF event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + --- Set a new listener for an S_EVENT_DEAD event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self -end - ---- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_ENGINE_STARTUP event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + --- Stop listening to S_EVENT_DEAD event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnDeadRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self + end ---- Set a new listener for an S_EVENT_SHOT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnShot( EventFunction, EventSelf ) - self:F2() +do -- OnPilotDead - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + --- Set a new listener for an S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPilotDead( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_SHOT event for a unit. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + --- Set a new listener for an S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self -end - ---- Set a new listener for an S_EVENT_HIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnHit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) - return self + return self + end + + --- Stop listening to S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPilotDeadRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PILOT_DEAD ) + + return self + end + end ---- Set a new listener for an S_EVENT_HIT event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) +do -- OnLand + --- Create an OnLand event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + --- Set a new listener for an S_EVENT_LAND event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) + + return self + end + + --- Stop listening to S_EVENT_LAND event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnLandRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_LAND ) + + return self + end + + end +do -- OnTakeOff + --- Create an OnTakeOff event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) + + return self + end + + --- Set a new listener for an S_EVENT_TAKEOFF event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) + + return self + end + + --- Stop listening to S_EVENT_TAKEOFF event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnTakeOffRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_TAKEOFF ) + + return self + end + + +end + +do -- OnEngineShutDown + + --- Create an OnDead event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + + return self + end + + --- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self + end + + --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnEngineShutDownRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self + end + +end + +do -- OnEngineStartUp + + --- Set a new listener for an S_EVENT_ENGINE_STARTUP event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + + return self + end + + --- Stop listening to S_EVENT_ENGINE_STARTUP event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnEngineStartUpRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + + return self + end + +end + +do -- OnShot + --- Set a new listener for an S_EVENT_SHOT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnShot( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + --- Set a new listener for an S_EVENT_SHOT event for a unit. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + --- Stop listening to S_EVENT_SHOT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnShotRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + +end + +do -- OnHit + + --- Set a new listener for an S_EVENT_HIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnHit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + + return self + end + + --- Set a new listener for an S_EVENT_HIT event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) + + return self + end + + --- Stop listening to S_EVENT_HIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnHitRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_HIT ) + + return self + end + +end + +do -- OnPlayerEnterUnit + + --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self + end + + --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPlayerEnterRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self + end + +end + +do -- OnPlayerLeaveUnit + --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self + end + + --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPlayerLeaveRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self + end + +end + + --- @param #EVENT self -- @param #EVENTDATA Event function EVENT:onEvent( Event ) - self:F2( { _EVENTCODES[Event.id], Event } ) if self and self.Events and self.Events[Event.id] then if Event.initiator and Event.initiator:getCategory() == Object.Category.UNIT then @@ -502,16 +719,21 @@ function EVENT:onEvent( Event ) end self:E( { _EVENTCODES[Event.id], Event.IniUnitName, Event.TgtUnitName, Event.WeaponName } ) for ClassName, EventData in pairs( self.Events[Event.id] ) do + self:T( { "Evaluating class ", { EventData.EventSelf:GetClassNameAndID(), ClassName } } ) if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then - self:E( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) + self:T( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventSelf, Event ) else if Event.IniDCSUnit and not EventData.IniUnit then - self:E( { "Calling event function for class ", ClassName } ) - EventData.EventFunction( EventData.EventSelf, Event ) + if ClassName == EventData.EventSelf:GetClassNameAndID() then + self:T( { "Calling event function for class ", ClassName } ) + EventData.EventFunction( EventData.EventSelf, Event ) + end end end end + else + self:E( { _EVENTCODES[Event.id], Event } ) end end diff --git a/Moose Development/Moose/FAC.lua b/Moose Development/Moose/FAC.lua deleted file mode 100644 index 554888549..000000000 --- a/Moose Development/Moose/FAC.lua +++ /dev/null @@ -1,210 +0,0 @@ ---- This module contains the FAC classes. --- --- === --- --- 1) @{Fac#FAC_BASE} class, extends @{Base#BASE} --- ============================================== --- The @{Fac#FAC_BASE} class defines the core functions to report detected objects to clients. --- Reportings can be done in several manners, and it is up to the derived classes if FAC_BASE to model the reporting behaviour. --- --- 1.1) FAC_BASE constructor: --- ---------------------------- --- * @{Fac#FAC_BASE.New}(): Create a new FAC_BASE instance. --- --- 1.2) FAC_BASE reporting: --- ------------------------ --- Derived FAC_BASE classes will reports detected units using the method @{Fac#FAC_BASE.ReportDetected}(). This method implements polymorphic behaviour. --- --- The time interval in seconds of the reporting can be changed using the methods @{Fac#FAC_BASE.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{Fac#FAC_BASE.SetReportDisplayTime}(). --- Derived classes need to implement the method @{Fac#FAC_BASE.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. --- --- Reporting can be started and stopped using the methods @{Fac#FAC_BASE.StartReporting}() and @{Fac#FAC_BASE.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{Fac#FAC_BASE#ReportNow}(). --- --- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. --- --- === --- --- 2) @{Fac#FAC_REPORTING} class, extends @{Fac#FAC_BASE} --- ====================================================== --- The @{Fac#FAC_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Fac#FAC_BASE} class. --- --- 2.1) FAC_REPORTING constructor: --- ------------------------------- --- The @{Fac#FAC_REPORTING.New}() method creates a new FAC_REPORTING instance. --- --- === --- --- @module Fac --- @author Mechanic, Prof_Hilactic, FlightControl : Concept & Testing --- @author FlightControl : Design & Programming - - - ---- FAC_BASE class. --- @type FAC_BASE --- @field Set#SET_CLIENT ClientSet The clients to which the FAC will report to. --- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. --- @extends Base#BASE -FAC_BASE = { - ClassName = "FAC_BASE", - ClientSet = nil, - Detection = nil, -} - ---- FAC constructor. --- @param #FAC_BASE self --- @param Set#SET_CLIENT ClientSet --- @param Detection#DETECTION_BASE Detection --- @return #FAC_BASE self -function FAC_BASE:New( ClientSet, Detection ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Fac#FAC_BASE - - self.ClientSet = ClientSet - self.Detection = Detection - - self:SetReportInterval( 60 ) - self:SetReportDisplayTime( 15 ) - - return self -end - ---- Set the reporting time interval. --- @param #FAC_BASE self --- @param #number ReportInterval The interval in seconds when a report needs to be done. --- @return #FAC_BASE self -function FAC_BASE:SetReportInterval( ReportInterval ) - self:F2() - - self._ReportInterval = ReportInterval -end - - ---- Set the reporting message display time. --- @param #FAC_BASE self --- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. --- @return #FAC_BASE self -function FAC_BASE:SetReportDisplayTime( ReportDisplayTime ) - self:F2() - - self._ReportDisplayTime = ReportDisplayTime -end - ---- Get the reporting message display time. --- @param #FAC_BASE self --- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. -function FAC_BASE:GetReportDisplayTime() - self:F2() - - return self._ReportDisplayTime -end - ---- Reports the detected items to the @{Set#SET_CLIENT}. --- @param #FAC_BASE self --- @param Set#SET_BASE DetectedSets The detected Sets created by the @{Detection#DETECTION_BASE} object. --- @return #FAC_BASE self -function FAC_BASE:ReportDetected( DetectedSets ) - self:F2() - - - -end - ---- Schedule the FAC reporting. --- @param #FAC_BASE self --- @param #number DelayTime The delay in seconds to wait the reporting. --- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. --- @return #FAC_BASE self -function FAC_BASE:Schedule( DelayTime, ReportInterval ) - self:F2() - - self._ScheduleDelayTime = DelayTime - - self:SetReportInterval( ReportInterval ) - - self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "Fac" }, self._ScheduleDelayTime, self._ReportInterval ) - return self -end - ---- Report the detected @{Unit#UNIT}s detected within the @{DetectION#DETECTION_BASE} object to the @{Set#SET_CLIENT}s. --- @param #FAC_BASE self -function FAC_BASE:_FacScheduler( SchedulerName ) - self:F2( { SchedulerName } ) - - self.ClientSet:ForEachClient( - --- @param Client#CLIENT Client - function( Client ) - if Client:IsAlive() then - local DetectedSets = self.Detection:GetDetectedSets() - return self:ReportDetected( Client, DetectedSets ) - end - end - ) - - return true -end - --- FAC_REPORTING - ---- FAC_REPORTING class. --- @type FAC_REPORTING --- @field Set#SET_CLIENT ClientSet The clients to which the FAC will report to. --- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. --- @extends #FAC_BASE -FAC_REPORTING = { - ClassName = "FAC_REPORTING", -} - - ---- FAC_REPORTING constructor. --- @param #FAC_REPORTING self --- @param Set#SET_CLIENT ClientSet --- @param Detection#DETECTION_BASE Detection --- @return #FAC_REPORTING self -function FAC_REPORTING:New( ClientSet, Detection ) - - -- Inherits from FAC_BASE - local self = BASE:Inherit( self, FAC_BASE:New( ClientSet, Detection ) ) -- #FAC_REPORTING - - self:Schedule( 5, 60 ) - return self -end - - ---- Reports the detected items to the @{Set#SET_CLIENT}. --- @param #FAC_REPORTING self --- @param Client#CLIENT Client The @{Client} object to where the report needs to go. --- @param Set#SET_BASE DetectedSets The detected Sets created by the @{Detection#DETECTION_BASE} object. --- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. -function FAC_REPORTING:ReportDetected( Client, DetectedSets ) - self:F2( Client ) - - local DetectedMsg = {} - for DetectedUnitSetID, DetectedUnitSet in pairs( DetectedSets ) do - local UnitSet = DetectedUnitSet -- Set#SET_UNIT - local MT = {} -- Message Text - local UnitTypes = {} - for DetectedUnitID, DetectedUnitData in pairs( UnitSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT - local UnitType = DetectedUnit:GetTypeName() - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - local MessageText = table.concat( MT, ", " ) - DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedUnitSetID .. ": " .. MessageText - end - local FACGroup = self.Detection:GetFACGroup() - FACGroup:MessageToClient( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Client ) - - return true -end - diff --git a/Moose Development/Moose/Group.lua b/Moose Development/Moose/Group.lua index 791306273..cc10cf621 100644 --- a/Moose Development/Moose/Group.lua +++ b/Moose Development/Moose/Group.lua @@ -476,12 +476,12 @@ end --- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. -- @param #GROUP self -- @return DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. -function GROUP:GetPointVec2() +function GROUP:GetVec2() self:F2( self.GroupName ) local UnitPoint = self:GetUnit(1) - UnitPoint:GetPointVec2() - local GroupPointVec2 = UnitPoint:GetPointVec2() + UnitPoint:GetVec2() + local GroupPointVec2 = UnitPoint:GetVec2() self:T3( GroupPointVec2 ) return GroupPointVec2 end @@ -873,7 +873,7 @@ end -- @param #string Message The message text -- @param DCSTypes#Duration Duration The duration of the message. -- @return Message#MESSAGE -function GROUP:Message( Message, Duration ) +function GROUP:GetMessage( Message, Duration ) self:F2( { Message, Duration } ) local DCSGroup = self:GetDCSObject() @@ -894,7 +894,7 @@ function GROUP:MessageToAll( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToAll() + self:GetMessage( Message, Duration ):ToAll() end return nil @@ -910,7 +910,7 @@ function GROUP:MessageToRed( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToRed() + self:GetMessage( Message, Duration ):ToRed() end return nil @@ -926,7 +926,7 @@ function GROUP:MessageToBlue( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToBlue() + self:GetMessage( Message, Duration ):ToBlue() end return nil @@ -943,7 +943,42 @@ function GROUP:MessageToClient( Message, Duration, Client ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToClient( Client ) + self:GetMessage( Message, Duration ):ToClient( Client ) + end + + return nil +end + +--- Send a message to a @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #GROUP self +-- @param #string Message The message text +-- @param DCSTypes#Duration Duration The duration of the message. +-- @param Group#GROUP MessageGroup The GROUP object receiving the message. +function GROUP:MessageToGroup( Message, Duration, MsgGroup ) + self:F2( { Message, Duration } ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + if DCSGroup:isExist() then + self:GetMessage( Message, Duration ):ToGroup( MsgGroup ) + end + end + + return nil +end + +--- Send a message to the players in the @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #GROUP self +-- @param #string Message The message text +-- @param DCSTypes#Duration Duration The duration of the message. +function GROUP:Message( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + self:GetMessage( Message, Duration ):ToGroup( self ) end return nil diff --git a/Moose Development/Moose/Menu.lua b/Moose Development/Moose/Menu.lua index cdad61304..7f6c81694 100644 --- a/Moose Development/Moose/Menu.lua +++ b/Moose Development/Moose/Menu.lua @@ -70,180 +70,365 @@ function SUBMENU:New( MenuText, ParentMenu ) return Child end --- This local variable is used to cache the menus registered under clients. --- Menus don't dissapear when clients are destroyed and restarted. --- So every menu for a client created must be tracked so that program logic accidentally does not create --- the same menus twice during initialization logic. --- These menu classes are handling this logic with this variable. -local _MENUCLIENTS = {} +do ---- The MENU_CLIENT class --- @type MENU_CLIENT --- @extends Menu#MENU -MENU_CLIENT = { - ClassName = "MENU_CLIENT" -} - ---- Creates a new menu item for a group --- @param self --- @param Client#CLIENT MenuClient The Client owning the menu. --- @param #string MenuText The text for the menu. --- @param #table ParentMenu The parent menu. --- @return #MENU_CLIENT self -function MENU_CLIENT:New( MenuClient, MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { MenuClient, MenuText, ParentMenu } ) - - self.MenuClient = MenuClient - self.MenuClientGroupID = MenuClient:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu + -- This local variable is used to cache the menus registered under clients. + -- Menus don't dissapear when clients are destroyed and restarted. + -- So every menu for a client created must be tracked so that program logic accidentally does not create + -- the same menus twice during initialization logic. + -- These menu classes are handling this logic with this variable. + local _MENUCLIENTS = {} - self.Menus = {} - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} + --- The MENU_CLIENT class + -- @type MENU_CLIENT + -- @extends Menu#MENU + MENU_CLIENT = { + ClassName = "MENU_CLIENT" + } + + --- Creates a new menu item for a group + -- @param self + -- @param Client#CLIENT MenuClient The Client owning the menu. + -- @param #string MenuText The text for the menu. + -- @param #table ParentMenu The parent menu. + -- @return #MENU_CLIENT self + function MENU_CLIENT:New( MenuClient, MenuText, ParentMenu ) + + -- Arrange meta tables + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + self:F( { MenuClient, MenuText, ParentMenu } ) + + self.MenuClient = MenuClient + self.MenuClientGroupID = MenuClient:GetClientGroupID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self.Menus = {} + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + end + + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) + MenuPath[MenuPathID] = self.MenuPath + + self:T( { MenuClient:GetClientGroupName(), self.MenuPath } ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end + return self end - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + --- Removes the sub menus recursively of this MENU_CLIENT. + -- @param #MENU_CLIENT self + -- @return #MENU_CLIENT self + function MENU_CLIENT:RemoveSubMenus() + self:F( self.MenuPath ) + + for MenuID, Menu in pairs( self.Menus ) do + Menu:Remove() + end + end + + --- Removes the sub menus recursively of this MENU_CLIENT. + -- @param #MENU_CLIENT self + -- @return #MENU_CLIENT self + function MENU_CLIENT:Remove() + self:F( self.MenuPath ) + + self:RemoveSubMenus() + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil + end + + + --- The MENU_CLIENT_COMMAND class + -- @type MENU_CLIENT_COMMAND + -- @extends Menu#MENU + MENU_CLIENT_COMMAND = { + ClassName = "MENU_CLIENT_COMMAND" + } + + --- Creates a new radio command item for a group + -- @param self + -- @param Client#CLIENT MenuClient The Client owning the menu. + -- @param MenuText The text for the menu. + -- @param ParentMenu The parent menu. + -- @param CommandMenuFunction A function that is called when the menu key is pressed. + -- @param CommandMenuArgument An argument for the function. + -- @return Menu#MENU_CLIENT_COMMAND self + function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) + + -- Arrange meta tables + + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + + self.MenuClient = MenuClient + self.MenuClientGroupID = MenuClient:GetClientGroupID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + end + + self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) + MenuPath[MenuPathID] = self.MenuPath + + self.CommandMenuFunction = CommandMenuFunction + self.CommandMenuArgument = CommandMenuArgument + + ParentMenu.Menus[self.MenuPath] = self + + return self + end + + function MENU_CLIENT_COMMAND:Remove() + self:F( self.MenuPath ) + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil + end +end - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath +--- MENU_GROUP - self:T( { MenuClient:GetClientGroupName(), self.MenuPath } ) +do + -- This local variable is used to cache the menus registered under clients. + -- Menus don't dissapear when clients are destroyed and restarted. + -- So every menu for a client created must be tracked so that program logic accidentally does not create + -- the same menus twice during initialization logic. + -- These menu classes are handling this logic with this variable. + local _MENUGROUPS = {} - if ParentMenu and ParentMenu.Menus then + --- The MENU_GROUP class + -- @type MENU_GROUP + -- @extends Menu#MENU + MENU_GROUP = { + ClassName = "MENU_GROUP" + } + + --- Creates a new menu item for a group + -- @param self + -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param #string MenuText The text for the menu. + -- @param #table ParentMenu The parent menu. + -- @return #MENU_GROUP self + function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) + + -- Arrange meta tables + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + self:F( { MenuGroup, MenuText, ParentMenu } ) + + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self.Menus = {} + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) + end + + self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, MenuParentPath ) + MenuPath[MenuPathID] = self.MenuPath + + self:T( { self.MenuGroupID, self.MenuPath } ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end + return self + end + + --- Removes the sub menus recursively of this MENU_GROUP. + -- @param #MENU_GROUP self + -- @return #MENU_GROUP self + function MENU_GROUP:RemoveSubMenus() + self:F( self.MenuPath ) + + for MenuID, Menu in pairs( self.Menus ) do + Menu:Remove() + end + + end + + --- Removes the sub menus recursively of this MENU_GROUP. + -- @param #MENU_GROUP self + -- @return #MENU_GROUP self + function MENU_GROUP:Remove() + self:F( self.MenuPath ) + + self:RemoveSubMenus() + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + return nil + end + + + --- The MENU_GROUP_COMMAND class + -- @type MENU_GROUP_COMMAND + -- @extends Menu#MENU + MENU_GROUP_COMMAND = { + ClassName = "MENU_GROUP_COMMAND" + } + + --- Creates a new radio command item for a group + -- @param #MENU_GROUP_COMMAND self + -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param MenuText The text for the menu. + -- @param ParentMenu The parent menu. + -- @param CommandMenuFunction A function that is called when the menu key is pressed. + -- @param CommandMenuArgument An argument for the function. + -- @return Menu#MENU_GROUP_COMMAND self + function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) + + -- Arrange meta tables + + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) + end + + self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) + self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) + MenuPath[MenuPathID] = self.MenuPath + + self.CommandMenuFunction = CommandMenuFunction + self.CommandMenuArgument = CommandMenuArgument + ParentMenu.Menus[self.MenuPath] = self + + return self end - return self -end + + function MENU_GROUP_COMMAND:Remove() + self:F( self.MenuPath ) + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] ---- Removes the sub menus recursively of this MENU_CLIENT. --- @param #MENU_CLIENT self --- @return #MENU_CLIENT self -function MENU_CLIENT:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil end end ---- Removes the sub menus recursively of this MENU_CLIENT. --- @param #MENU_CLIENT self --- @return #MENU_CLIENT self -function MENU_CLIENT:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil -end - - ---- The MENU_CLIENT_COMMAND class --- @type MENU_CLIENT_COMMAND --- @extends Menu#MENU -MENU_CLIENT_COMMAND = { - ClassName = "MENU_CLIENT_COMMAND" -} - ---- Creates a new radio command item for a group --- @param self --- @param Client#CLIENT MenuClient The Client owning the menu. --- @param MenuText The text for the menu. --- @param ParentMenu The parent menu. --- @param CommandMenuFunction A function that is called when the menu key is pressed. --- @param CommandMenuArgument An argument for the function. --- @return Menu#MENU_CLIENT_COMMAND self -function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - - self.MenuClient = MenuClient - self.MenuClientGroupID = MenuClient:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) - MenuPath[MenuPathID] = self.MenuPath - - self.CommandMenuFunction = CommandMenuFunction - self.CommandMenuArgument = CommandMenuArgument - - ParentMenu.Menus[self.MenuPath] = self - - return self -end - -function MENU_CLIENT_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil -end - - --- The MENU_COALITION class -- @type MENU_COALITION -- @extends Menu#MENU @@ -253,11 +438,11 @@ MENU_COALITION = { --- Creates a new coalition menu item -- @param #MENU_COALITION self --- @param DCSCoalition#coalition.side MenuCoalition The coalition owning the menu. +-- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_COALITION self -function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) +function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) -- Arrange meta tables local MenuParentPath = {} @@ -266,9 +451,9 @@ function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) end local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { MenuCoalition, MenuText, ParentMenu } ) + self:F( { Coalition, MenuText, ParentMenu } ) - self.MenuCoalition = MenuCoalition + self.Coalition = Coalition self.MenuParentPath = MenuParentPath self.MenuText = MenuText self.ParentMenu = ParentMenu @@ -277,7 +462,7 @@ function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) self:T( { MenuParentPath, MenuText } ) - self.MenuPath = missionCommands.addSubMenuForCoalition( self.MenuCoalition, MenuText, MenuParentPath ) + self.MenuPath = missionCommands.addSubMenuForCoalition( self.Coalition, MenuText, MenuParentPath ) self:T( { self.MenuPath } ) diff --git a/Moose Development/Moose/Message.lua b/Moose Development/Moose/Message.lua index b55a48c6f..bc44263bc 100644 --- a/Moose Development/Moose/Message.lua +++ b/Moose Development/Moose/Message.lua @@ -59,7 +59,7 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) self.MessageCategory = "" end - self.MessageDuration = MessageDuration + self.MessageDuration = MessageDuration or 5 self.MessageTime = timer.getTime() self.MessageText = MessageText @@ -102,6 +102,21 @@ function MESSAGE:ToClient( Client ) return self end +--- Sends a MESSAGE to a Group. +-- @param #MESSAGE self +-- @param Group#GROUP Group is the Group. +-- @return #MESSAGE +function MESSAGE:ToGroup( Group ) + self:F( Group.GroupName ) + + if Group then + + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end --- Sends a MESSAGE to the Blue coalition. -- @param #MESSAGE self -- @return #MESSAGE diff --git a/Moose Development/Moose/Mission.lua b/Moose Development/Moose/Mission.lua index 37778a5f5..a5d59800e 100644 --- a/Moose Development/Moose/Mission.lua +++ b/Moose Development/Moose/Mission.lua @@ -6,13 +6,17 @@ -- @type MISSION -- @extends Base#BASE -- @field #MISSION.Clients _Clients +-- @field Menu#MENU_COALITION MissionMenu -- @field #string MissionBriefing MISSION = { ClassName = "MISSION", Name = "", MissionStatus = "PENDING", _Clients = {}, - _Tasks = {}, + Tasks = {}, + TaskMenus = {}, + TaskCategoryMenus = {}, + TaskTypeMenus = {}, _ActiveTasks = {}, GoalFunction = nil, MissionReportTrigger = 0, @@ -33,49 +37,177 @@ MISSION = { function MISSION:Meta() local self = BASE:Inherit( self, BASE:New() ) - self:F() return self end --- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. --- @param string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param string MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... --- @return MISSION --- @usage --- -- Declare a few missions. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Patriots', 'Primary', 'Our intelligence reports that 3 Patriot SAM defense batteries are located near Ruisi, Kvarhiti and Gori.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Package Delivery', 'Operational', 'In order to be in full control of the situation, we need you to deliver a very important package at a secret location. Fly undetected through the NATO defenses and deliver the secret package. The secret agent is located at waypoint 4.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Rescue General', 'Tactical', 'Our intelligence has received a remote signal behind Gori. We believe it is a very important Russian General that was captured by Georgia. Go out there and rescue him! Ensure you stay out of the battle zone, keep south. Waypoint 4 is the location of our Russian General.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'SA-6 SAMs', 'Primary', 'Our intelligence reports that 3 SA-6 SAM defense batteries are located near Didmukha, Khetagurov and Berula. Eliminate the Russian SAMs.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Sling Load', 'Operational', 'Fly to the cargo pickup zone at Dzegvi or Kaspi, and sling the cargo to Soganlug airbase.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Rescue secret agent', 'Tactical', 'In order to be in full control of the situation, we need you to rescue a secret agent from the woods behind enemy lines. Avoid the Russian defenses and rescue the agent. Keep south until Khasuri, and keep your eyes open for any SAM presence. The agent is located at waypoint 4 on your kneeboard.', 'NATO' ) +-- @param #MISSION self +-- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. +-- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. +-- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. +-- @param DCSCoalitionObject#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... +-- @return #MISSION self function MISSION:New( MissionName, MissionPriority, MissionBriefing, MissionCoalition ) self = MISSION:Meta() - self:T({ MissionName, MissionPriority, MissionBriefing, MissionCoalition }) + self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - local Valid = true - - Valid = routines.ValidateString( MissionName, "MissionName", Valid ) - Valid = routines.ValidateString( MissionPriority, "MissionPriority", Valid ) - Valid = routines.ValidateString( MissionBriefing, "MissionBriefing", Valid ) - Valid = routines.ValidateString( MissionCoalition, "MissionCoalition", Valid ) - - if Valid then - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - end + self.Name = MissionName + self.MissionPriority = MissionPriority + self.MissionBriefing = MissionBriefing + self.MissionCoalition = MissionCoalition return self end +--- Gets the mission name. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:GetName() + return self.Name +end + +--- Add a scoring to the mission. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:AddScoring( Scoring ) + self.Scoring = Scoring + return self +end + +--- Get the scoring object of a mission. +-- @param #MISSION self +-- @return #SCORING Scoring +function MISSION:GetScoring() + return self.Scoring +end + + +--- Sets the Planned Task menu. +-- @param #MISSION self +function MISSION:SetPlannedMenu() + + for _, Task in pairs( self.Tasks ) do + local Task = Task -- Task#TASK_BASE + Task:RemoveMenu() + Task:SetPlannedMenu() + end + +end + +--- Sets the Assigned Task menu. +-- @param #MISSION self +-- @param Task#TASK_BASE Task +-- @param #string MenuText The menu text. +-- @return #MISSION self +function MISSION:SetAssignedMenu( Task ) + + for _, Task in pairs( self.Tasks ) do + local Task = Task -- Task#TASK_BASE + Task:RemoveMenu() + Task:SetAssignedMenu() + end + +end + +--- Removes a Task menu. +-- @param #MISSION self +-- @param Task#TASK_BASE Task +-- @return #MISSION self +function MISSION:RemoveTaskMenu( Task ) + + Task:RemoveMenu() +end + + +--- Gets the mission menu for the coalition. +-- @param #MISSION self +-- @param Group#GROUP TaskGroup +-- @return Menu#MENU_COALITION self +function MISSION:GetMissionMenu( TaskGroup ) + local TaskGroupName = TaskGroup:GetName() + return self.MenuMission[TaskGroupName] +end + + +--- Clears the mission menu for the coalition. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:ClearMissionMenu() + self.MissionMenu:Remove() + self.MissionMenu = nil +end + +--- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. +-- @param #string TaskIndex is the Index of the @{Task} within the @{Mission}. +-- @param #number TaskID is the ID of the @{Task} within the @{Mission}. +-- @return Task#TASK_BASE The Task +-- @return #nil Returns nil if no task was found. +function MISSION:GetTask( TaskName ) + self:F( { TaskName } ) + + return self.Tasks[TaskName] +end + + +--- Register a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return Task#TASK_BASE The task added. +function MISSION:AddTask( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName] = Task + + return Task +end + +--- Removes a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return #nil The cleaned Task reference. +function MISSION:RemoveTask( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + Task:CleanUp() -- Cleans all events and sets task to nil to get Garbage Collected + + -- Ensure everything gets garbarge collected. + self.Tasks[TaskName] = nil + Task = nil + + return nil +end + +--- Return the next @{Task} ID to be completed within the @{Mission}. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return Task#TASK_BASE The task added. +function MISSION:GetNextTaskID( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 + + return self.Tasks[TaskName].n +end + + + +--- old stuff + --- Returns if a Mission has completed. -- @return bool function MISSION:IsCompleted() @@ -264,66 +396,6 @@ function MISSION:FindClient( ClientName ) end ---- Register a @{TASK} to be completed within the @{MISSION}. Note that there can be multiple @{TASK}s registered to be completed. Each TASK can be set a certain Goal. The MISSION will not be completed until all Goals are reached. --- @param TASK Task is the @{TASK} object. The object must have been instantiated with @{TASK:New} or any of its inherited @{TASK}s. --- @param number TaskNumber is the sequence number of the TASK within the MISSION. This number does have to be chronological. --- @return TASK --- @usage --- -- Define a few tasks for the Mission. --- PickupZones = { "NATO Gold Pickup Zone", "NATO Titan Pickup Zone" } --- PickupSignalUnits = { "NATO Gold Coordination Center", "NATO Titan Coordination Center" } --- --- -- Assign the Pickup Task --- local PickupTask = PICKUPTASK:New( PickupZones, CARGO_TYPE.ENGINEERS, CLIENT.ONBOARDSIDE.LEFT ) --- PickupTask:AddSmokeBlue( PickupSignalUnits ) --- PickupTask:SetGoalTotal( 3 ) --- Mission:AddTask( PickupTask, 1 ) --- --- -- Assign the Deploy Task --- local PatriotActivationZones = { "US Patriot Battery 1 Activation", "US Patriot Battery 2 Activation", "US Patriot Battery 3 Activation" } --- local PatriotActivationZonesSmokeUnits = { "US SAM Patriot - Battery 1 Control", "US SAM Patriot - Battery 2 Control", "US SAM Patriot - Battery 3 Control" } --- local DeployTask = DEPLOYTASK:New( PatriotActivationZones, CARGO_TYPE.ENGINEERS ) --- --DeployTask:SetCargoTargetZoneName( 'US Troops Attack ' .. math.random(2) ) --- DeployTask:AddSmokeBlue( PatriotActivationZonesSmokeUnits ) --- DeployTask:SetGoalTotal( 3 ) --- DeployTask:SetGoalTotal( 3, "Patriots activated" ) --- Mission:AddTask( DeployTask, 2 ) - -function MISSION:AddTask( Task, TaskNumber ) - self:F() - - self._Tasks[TaskNumber] = Task - self._Tasks[TaskNumber]:EnableEvents() - self._Tasks[TaskNumber].ID = TaskNumber - - return Task - end - ---- Get the TASK idenified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param number TaskNumber is the number of the @{TASK} within the @{MISSION}. --- @return TASK --- @usage --- -- Get Task 2 from the Mission. --- Task2 = Mission:GetTask( 2 ) - -function MISSION:GetTask( TaskNumber ) - self:F() - - local Valid = true - - local Task = nil - - if type(TaskNumber) ~= "number" then - Valid = false - end - - if Valid then - Task = self._Tasks[TaskNumber] - end - - return Task -end - --- Get all the TASKs from the Mission. This function is useful in GoalFunctions. -- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. -- @usage diff --git a/Moose Development/Moose/Moose.lua b/Moose Development/Moose/Moose.lua index 1c2f3ffe6..67d96af87 100644 --- a/Moose Development/Moose/Moose.lua +++ b/Moose Development/Moose/Moose.lua @@ -1,6 +1,7 @@ --- The main include file for the MOOSE system. Include.File( "Routines" ) +Include.File( "Utils" ) Include.File( "Base" ) Include.File( "Object" ) Include.File( "Identifiable" ) @@ -43,13 +44,27 @@ Include.File( "MissileTrainer" ) Include.File( "PatrolZone" ) Include.File( "AIBalancer" ) Include.File( "AirbasePolice" ) + Include.File( "Detection" ) -Include.File( "FAC" ) +Include.File( "DetectionManager" ) + +Include.File( "StateMachine" ) + +Include.File( "Process" ) +Include.File( "Process_Assign" ) +Include.File( "Process_Route" ) +Include.File( "Process_Smoke" ) +Include.File( "Process_Destroy" ) + +Include.File( "Task" ) +Include.File( "Task_SEAD" ) +Include.File( "Task_CAS" ) +Include.File( "Task_BAI" ) -- The order of the declarations is important here. Don't touch it. --- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- #EVENT +_EVENTDISPATCHER = EVENT:New() -- Event#EVENT --- Declare the main database object, which is used internally by the MOOSE classes. _DATABASE = DATABASE:New() -- Database#DATABASE diff --git a/Moose Development/Moose/PatrolZone.lua b/Moose Development/Moose/PatrolZone.lua index f4bc61b6f..ffa4a3a6d 100644 --- a/Moose Development/Moose/PatrolZone.lua +++ b/Moose Development/Moose/PatrolZone.lua @@ -138,7 +138,7 @@ function PATROLZONE:NewPatrolRoute() -- If not, make a waypoint within the to that the PatrolGroup will fly at maximum speed to that point. -- --- Calculate the current route point. --- local CurrentVec2 = self.PatrolGroup:GetPointVec2() +-- local CurrentVec2 = self.PatrolGroup:GetVec2() -- local CurrentAltitude = self.PatrolGroup:GetUnit(1):GetAltitude() -- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) -- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( diff --git a/Moose Development/Moose/Point.lua b/Moose Development/Moose/Point.lua index 7fdd395a7..216933c30 100644 --- a/Moose Development/Moose/Point.lua +++ b/Moose Development/Moose/Point.lua @@ -4,6 +4,9 @@ -- =============================================== -- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. -- +-- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. +-- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. +-- -- 1.1) POINT_VEC3 constructor -- --------------------------- -- @@ -28,6 +31,7 @@ --- The POINT_VEC3 class -- @type POINT_VEC3 -- @extends Base#BASE +-- @field DCSTypes#Vec3 PointVec3 -- @field #POINT_VEC3.SmokeColor SmokeColor -- @field #POINT_VEC3.FlareColor FlareColor -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType @@ -48,6 +52,7 @@ POINT_VEC3 = { White = trigger.flareColor.White, Yellow = trigger.flareColor.Yellow }, + Metric = true, RoutePointAltType = { BARO = "BARO", }, @@ -114,6 +119,160 @@ function POINT_VEC3:New( x, y, z ) end +--- Return the coordinates of the POINT_VEC3 in Vec3 format. +-- @param #POINT_VEC3 self +-- @return DCSTypes#Vec3 The Vec3 coodinate. +function POINT_VEC3:GetVec3() + return self.PointVec3 +end + + +--- Return the x coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The x coodinate. +function POINT_VEC3:GetX() + return self.PointVec3.x +end + +--- Return the y coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The y coodinate. +function POINT_VEC3:GetY() + return self.PointVec3.y +end + +--- Return the z coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The z coodinate. +function POINT_VEC3:GetZ() + return self.PointVec3.z +end + + +--- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) + return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } +end + +--- Get a correction in radians of the real magnetic north of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number CorrectionRadians The correction in radians. +function POINT_VEC3:GetNorthCorrectionRadians() + local TargetVec3 = self:GetVec3() + local lat, lon = coord.LOtoLL(TargetVec3) + local north_posit = coord.LLtoLO(lat + 1, lon) + return math.atan2( north_posit.z - TargetVec3.z, north_posit.x - TargetVec3.x ) +end + + +--- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. +-- @param #POINT_VEC3 self +-- @param DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @return #number DirectionRadians The direction in radians. +function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) + local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) + --DirectionRadians = DirectionRadians + self:GetNorthCorrectionRadians() + if DirectionRadians < 0 then + DirectionRadians = DirectionRadians + 2 * math.pi -- put dir in range of 0 to 2*pi ( the full circle ) + end + return DirectionRadians +end + +--- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Distance Distance The distance in meters. +function POINT_VEC3:Get2DDistance( TargetPointVec3 ) + local TargetVec3 = TargetPointVec3:GetVec3() + local SourceVec3 = self:GetVec3() + return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 +end + +--- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Distance Distance The distance in meters. +function POINT_VEC3:Get3DDistance( TargetPointVec3 ) + local TargetVec3 = TargetPointVec3:GetVec3() + local SourceVec3 = self:GetVec3() + return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 +end + +--- Provides a Bearing / Range string +-- @param #POINT_VEC3 self +-- @param #number AngleRadians The angle in randians +-- @param #number Distance The distance +-- @return #string The BR Text +function POINT_VEC3:ToStringBR( AngleRadians, Distance ) + + AngleRadians = UTILS.Round( UTILS.ToDegree( AngleRadians ), 0 ) + if self:IsMetric() then + Distance = UTILS.Round( Distance / 1000, 2 ) + else + Distance = UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) + end + + local s = string.format( '%03d', AngleRadians ) .. ' for ' .. Distance + + s = s .. self:GetAltitudeText() -- When the POINT is a VEC2, there will be no altitude shown. + + return s +end + +--- Provides a Bearing / Range string +-- @param #POINT_VEC3 self +-- @param #number AngleRadians The angle in randians +-- @param #number Distance The distance +-- @return #string The BR Text +function POINT_VEC3:ToStringLL( acc, DMS ) + + acc = acc or 3 + local lat, lon = coord.LOtoLL( self.PointVec3 ) + return UTILS.tostringLL(lat, lon, acc, DMS) +end + +--- Return the altitude text of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #string Altitude text. +function POINT_VEC3:GetAltitudeText() + if self:IsMetric() then + return ' at ' .. UTILS.Round( self:GetY(), 0 ) + else + return ' at ' .. UTILS.Round( UTILS.MetersToFeet( self:GetY() ), 0 ) + end +end + +--- Return a BR string from a POINT_VEC3 to the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return #string The BR text. +function POINT_VEC3:GetBRText( TargetPointVec3 ) + local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 ) + local AngleRadians = self:GetDirectionRadians( DirectionVec3 ) + local Distance = self:Get2DDistance( TargetPointVec3 ) + return self:ToStringBR( AngleRadians, Distance ) +end + +--- Sets the POINT_VEC3 metric or NM. +-- @param #POINT_VEC3 self +-- @param #boolean Metric true means metric, false means NM. +function POINT_VEC3:SetMetric( Metric ) + self.Metric = Metric +end + +--- Gets if the POINT_VEC3 is metric or NM. +-- @param #POINT_VEC3 self +-- @return #boolean Metric true means metric, false means NM. +function POINT_VEC3:IsMetric() + return self.Metric +end + + + + --- Build an air type route point. -- @param #POINT_VEC3 self -- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. @@ -299,3 +458,10 @@ function POINT_VEC2:DistanceFromVec2( Vec2Reference ) end +--- Return no text for the altitude of the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #string Empty string. +function POINT_VEC2:GetAltitudeText() + return '' +end + diff --git a/Moose Development/Moose/Positionable.lua b/Moose Development/Moose/Positionable.lua index 82c824957..a8e54b9ba 100644 --- a/Moose Development/Moose/Positionable.lua +++ b/Moose Development/Moose/Positionable.lua @@ -71,7 +71,7 @@ end -- @param Positionable#POSITIONABLE self -- @return DCSTypes#Vec2 The 2D point vector of the DCS Positionable. -- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetPointVec2() +function POSITIONABLE:GetVec2() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() @@ -91,6 +91,30 @@ function POSITIONABLE:GetPointVec2() end +--- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the DCS Positionable within the mission. +-- @param Positionable#POSITIONABLE self +-- @return DCSTypes#Vec3 The 3D point vector of the DCS Positionable. +-- @return #nil The DCS Positionable is not existing or alive. +function POSITIONABLE:GetRandomPointVec3( Radius ) + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableRandomPointVec3 = {} + local angle = math.random() * math.pi*2; + PositionableRandomPointVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; + PositionableRandomPointVec3.y = PositionablePointVec3.y + PositionableRandomPointVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; + + self:T3( PositionableRandomPointVec3 ) + return PositionableRandomPointVec3 + end + + return nil +end + --- Returns the @{DCSTypes#Vec3} vector indicating the point in 3D of the DCS Positionable within the mission. -- @param Positionable#POSITIONABLE self -- @return DCSTypes#Vec3 The 3D point vector of the DCS Positionable. @@ -137,7 +161,7 @@ function POSITIONABLE:IsAboveRunway() if DCSPositionable then - local PointVec2 = self:GetPointVec2() + local PointVec2 = self:GetVec2() local SurfaceType = land.getSurfaceType( PointVec2 ) local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY @@ -209,5 +233,26 @@ function POSITIONABLE:GetVelocity() return nil end +--- Returns the @{Unit#UNIT} velocity in km/h. +-- @param Positionable#POSITIONABLE self +-- @return #number The velocity in km/h +-- @return #nil The DCS Positionable is not existing or alive. +function POSITIONABLE:GetVelocityKMH() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local VelocityVec3 = self:GetVelocity() + local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec + local Velocity = Velocity * 3.6 -- now it is in km/h. + self:T3( Velocity ) + return Velocity + end + + return nil +end + + diff --git a/Moose Development/Moose/Process.lua b/Moose Development/Moose/Process.lua new file mode 100644 index 000000000..bf5f9f122 --- /dev/null +++ b/Moose Development/Moose/Process.lua @@ -0,0 +1,93 @@ +--- @module Process + +--- The PROCESS class +-- @type PROCESS +-- @field Scheduler#SCHEDULER ProcessScheduler +-- @field Unit#UNIT ProcessUnit +-- @field Task#TASK Task +-- @field StateMachine#STATEMACHINE_TASK Fsm +-- @field #string ProcessName +-- @extends Base#BASE +PROCESS = { + ClassName = "TASK", + ProcessScheduler = nil, + NextEvent = nil, + Scores = {}, +} + +--- Instantiates a new TASK Base. Should never be used. Interface Class. +-- @param #PROCESS self +-- @param #string ProcessName +-- @param Task#TASK_BASE Task +-- @param Unit#UNIT ProcessUnit +-- @return #PROCESS self +function PROCESS:New( ProcessName, Task, ProcessUnit ) + local self = BASE:Inherit( self, BASE:New() ) + self:F() + + self.ProcessUnit = ProcessUnit + self.Task = Task + self.ProcessName = ProcessName + + self.AllowEvents = true + + return self +end + +--- @param #PROCESS self +function PROCESS:NextEvent( NextEvent, ... ) + self:F2( arg ) + if self.AllowEvents == true then + self.ProcessScheduler = SCHEDULER:New( self.Fsm, NextEvent, { self, self.ProcessUnit, unpack( arg ) }, 1 ) + end +end + +--- @param #PROCESS self +function PROCESS:StopEvents( ) + self:F2() + if self.ProcessScheduler then + self:E( "Stop" ) + self.ProcessScheduler:Stop() + self.ProcessScheduler = nil + self.AllowEvents = false + end +end + +--- Adds a score for the PROCESS to be achieved. +-- @param #PROCESS self +-- @param #string ProcessStatus is the status of the PROCESS when the score needs to be given. +-- @param #string ScoreText is a text describing the score that is given according the status. +-- @param #number Score is a number providing the score of the status. +-- @return #PROCESS self +function PROCESS:AddScore( ProcessStatus, ScoreText, Score ) + self:F2( { ProcessStatus, ScoreText, Score } ) + + self.Scores[ProcessStatus] = self.Scores[ProcessStatus] or {} + self.Scores[ProcessStatus].ScoreText = ScoreText + self.Scores[ProcessStatus].Score = Score + return self +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS:OnStateChange( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName } ) + + if self:IsTrace() then + MESSAGE:New( "Process " .. self.ProcessName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() + end + + if self.Scores[To] then + + local Scoring = self.Task:GetScoring() + if Scoring then + Scoring:_AddMissionTaskScore( self.Task.Mission, self.ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end +end + + diff --git a/Moose Development/Moose/Process_Assign.lua b/Moose Development/Moose/Process_Assign.lua new file mode 100644 index 000000000..28dcc32f4 --- /dev/null +++ b/Moose Development/Moose/Process_Assign.lua @@ -0,0 +1,185 @@ +--- This module contains the TASK_ASSIGN classes. +-- +-- === +-- +-- 1) @{Task_Assign#TASK_ASSIGN_ACCEPT} class, extends @{Task#TASK_BASE} +-- ===================================================================== +-- The @{Task_Assign#TASK_ASSIGN_ACCEPT} class accepts by default a task for a player. No player intervention is allowed to reject the task. +-- +-- 2) @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class, extends @{Task#TASK_BASE} +-- ========================================================================== +-- The @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class accepts a task when the player accepts the task through an added menu option. +-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. +-- The assignment type also allows to reject the task. +-- +-- +-- +-- +-- +-- +-- @module Task_Assign +-- + + +do -- PROCESS_ASSIGN_ACCEPT + + --- PROCESS_ASSIGN_ACCEPT class + -- @type PROCESS_ASSIGN_ACCEPT + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_ASSIGN_ACCEPT = { + ClassName = "PROCESS_ASSIGN_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #PROCESS_ASSIGN_ACCEPT self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_ASSIGN_ACCEPT self + function PROCESS_ASSIGN_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_ACCEPT + + self.TaskBriefing = TaskBriefing + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnAssigned', + events = { + { name = 'Start', from = 'UnAssigned', to = 'Assigned' }, + { name = 'Fail', from = 'UnAssigned', to = 'Failed' }, + }, + callbacks = { + onAssign = self.OnAssign, + }, + endstates = { + 'Assigned', 'Failed' + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_ACCEPT:OnAssigned( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + end + +end + + +do -- PROCESS_ASSIGN_MENU_ACCEPT + + --- PROCESS_ASSIGN_MENU_ACCEPT class + -- @type PROCESS_ASSIGN_MENU_ACCEPT + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_ASSIGN_MENU_ACCEPT = { + ClassName = "PROCESS_ASSIGN_MENU_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_MENU_ACCEPT + + self.TaskBriefing = TaskBriefing + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnAssigned', + events = { + { name = 'Start', from = 'UnAssigned', to = 'AwaitAccept' }, + { name = 'Assign', from = 'AwaitAccept', to = 'Assigned' }, + { name = 'Reject', from = 'AwaitAccept', to = 'Rejected' }, + { name = 'Fail', from = 'AwaitAccept', to = 'Rejected' }, + }, + callbacks = { + onStart = self.OnStart, + onAssign = self.OnAssign, + onReject = self.OnReject, + }, + endstates = { + 'Assigned', 'Rejected' + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnStart( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + MESSAGE:New( self.TaskBriefing .. "\nAccess the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", 30, "Assignment" ):ToGroup( self.ProcessUnit:GetGroup() ) + self.MenuText = self.Task.TaskName + + local ProcessGroup = self.ProcessUnit:GetGroup() + self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.MenuText .. " acceptance" ) + self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.MenuText, self.Menu, self.MenuAssign, self ) + self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.MenuText, self.Menu, self.MenuReject, self ) + end + + --- Menu function. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() + self:E( ) + + self:NextEvent( self.Fsm.Assign ) + end + + --- Menu function. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() + self:E( ) + + self:NextEvent( self.Fsm.Reject ) + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnAssign( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.Menu:Remove() + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnReject( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.Menu:Remove() + self.Task:UnAssignFromUnit( self.ProcessUnit ) + self.ProcessUnit:Destroy() + end +end diff --git a/Moose Development/Moose/Process_BAI.lua b/Moose Development/Moose/Process_BAI.lua new file mode 100644 index 000000000..0181e6a65 --- /dev/null +++ b/Moose Development/Moose/Process_BAI.lua @@ -0,0 +1,143 @@ +--- @module Process_BAI + +--- PROCESS_BAI class +-- @type PROCESS_BAI +-- @field Unit#UNIT ProcessUnit +-- @field Set#SET_UNIT TargetSetUnit +-- @extends Process#PROCESS +PROCESS_BAI = { + ClassName = "PROCESS_BAI", + Fsm = {}, + TargetSetUnit = nil, +} + + +--- Creates a new BAI task. +-- @param #PROCESS_BAI self +-- @param Task#TASK Task +-- @param Unit#UNIT ProcessUnit +-- @param Set#SET_UNIT TargetSetUnit +-- @return #PROCESS_BAI self +function PROCESS_BAI:New( Task, ProcessUnit, TargetSetUnit ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "BAI", Task, ProcessUnit ) ) -- #PROCESS_BAI + + self.TargetSetUnit = TargetSetUnit + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'Assigned', + events = { + { name = 'Start', from = 'Assigned', to = 'Waiting' }, + { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, + { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, + { name = 'Destroyed', from = 'Destroy', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Waiting', to = 'Failed' }, + { name = 'Fail', from = 'Destroy', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onHitTarget = self.OnHitTarget, + onMoreTargets = self.OnMoreTargets, + onDestroyed = self.OnDestroyed, + onKilled = self.OnKilled, + }, + endstates = { 'Success', 'Failed' } + } ) + + + _EVENTDISPATCHER:OnDead( self.EventDead, self ) + + return self +end + +--- Process Events + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_BAI self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_BAI:OnStart( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self:NextEvent( Fsm.Start ) +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_BAI self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function PROCESS_BAI:OnHitTarget( Fsm, Event, From, To, Event ) + + if self.TargetSetUnit:Count() > 0 then + self:NextEvent( Fsm.MoreTargets ) + else + self:NextEvent( Fsm.Destroyed ) + end +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_BAI self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_BAI:OnMoreTargets( Fsm, Event, From, To ) + + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_BAI self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA DCSEvent +function PROCESS_BAI:OnKilled( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Restart ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_BAI self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_BAI:OnRestart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Menu ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_BAI self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_BAI:OnDestroyed( Fsm, Event, From, To ) + +end + +--- DCS Events + +--- @param #PROCESS_BAI self +-- @param Event#EVENTDATA Event +function PROCESS_BAI:EventDead( Event ) + + if Event.IniDCSUnit then + self.TargetSetUnit:Remove( Event.IniDCSUnitName ) + self:NextEvent( self.Fsm.HitTarget, Event ) + end +end + + diff --git a/Moose Development/Moose/Process_CAS.lua b/Moose Development/Moose/Process_CAS.lua new file mode 100644 index 000000000..d8b4c4b8b --- /dev/null +++ b/Moose Development/Moose/Process_CAS.lua @@ -0,0 +1,143 @@ +--- @module Process_CAS + +--- PROCESS_CAS class +-- @type PROCESS_CAS +-- @field Unit#UNIT ProcessUnit +-- @field Set#SET_UNIT TargetSetUnit +-- @extends Process#PROCESS +PROCESS_CAS = { + ClassName = "PROCESS_CAS", + Fsm = {}, + TargetSetUnit = nil, +} + + +--- Creates a new CAS task. +-- @param #PROCESS_CAS self +-- @param Task#TASK Task +-- @param Unit#UNIT ProcessUnit +-- @param Set#SET_UNIT TargetSetUnit +-- @return #PROCESS_CAS self +function PROCESS_CAS:New( Task, ProcessUnit, TargetSetUnit ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "CAS", Task, ProcessUnit ) ) -- #PROCESS_CAS + + self.TargetSetUnit = TargetSetUnit + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'Assigned', + events = { + { name = 'Start', from = 'Assigned', to = 'Waiting' }, + { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, + { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, + { name = 'Destroyed', from = 'Destroy', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Waiting', to = 'Failed' }, + { name = 'Fail', from = 'Destroy', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onHitTarget = self.OnHitTarget, + onMoreTargets = self.OnMoreTargets, + onDestroyed = self.OnDestroyed, + onKilled = self.OnKilled, + }, + endstates = { 'Success', 'Failed' } + } ) + + + _EVENTDISPATCHER:OnDead( self.EventDead, self ) + + return self +end + +--- Process Events + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_CAS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_CAS:OnStart( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self:NextEvent( Fsm.Start ) +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_CAS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function PROCESS_CAS:OnHitTarget( Fsm, Event, From, To, Event ) + + if self.TargetSetUnit:Count() > 0 then + self:NextEvent( Fsm.MoreTargets ) + else + self:NextEvent( Fsm.Destroyed ) + end +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_CAS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_CAS:OnMoreTargets( Fsm, Event, From, To ) + + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_CAS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA DCSEvent +function PROCESS_CAS:OnKilled( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Restart ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_CAS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_CAS:OnRestart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Menu ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_CAS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_CAS:OnDestroyed( Fsm, Event, From, To ) + +end + +--- DCS Events + +--- @param #PROCESS_CAS self +-- @param Event#EVENTDATA Event +function PROCESS_CAS:EventDead( Event ) + + if Event.IniDCSUnit then + self.TargetSetUnit:Remove( Event.IniDCSUnitName ) + self:NextEvent( self.Fsm.HitTarget, Event ) + end +end + + diff --git a/Moose Development/Moose/Process_Destroy.lua b/Moose Development/Moose/Process_Destroy.lua new file mode 100644 index 000000000..a1e5a4300 --- /dev/null +++ b/Moose Development/Moose/Process_Destroy.lua @@ -0,0 +1,180 @@ +--- @module Process_Destroy + +--- PROCESS_DESTROY class +-- @type PROCESS_DESTROY +-- @field Unit#UNIT ProcessUnit +-- @field Set#SET_UNIT TargetSetUnit +-- @extends Process#PROCESS +PROCESS_DESTROY = { + ClassName = "PROCESS_DESTROY", + Fsm = {}, + TargetSetUnit = nil, +} + + +--- Creates a new DESTROY process. +-- @param #PROCESS_DESTROY self +-- @param Task#TASK Task +-- @param Unit#UNIT ProcessUnit +-- @param Set#SET_UNIT TargetSetUnit +-- @return #PROCESS_DESTROY self +function PROCESS_DESTROY:New( Task, ProcessName, ProcessUnit, TargetSetUnit ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_DESTROY + + self.TargetSetUnit = TargetSetUnit + + self.DisplayInterval = 60 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Targets is the default display category + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'Assigned', + events = { + { name = 'Start', from = 'Assigned', to = 'Waiting' }, + { name = 'Start', from = 'Waiting', to = 'Waiting' }, + { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, + { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, + { name = 'Destroyed', from = 'Destroy', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Waiting', to = 'Failed' }, + { name = 'Fail', from = 'Destroy', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onWaiting = self.OnWaiting, + onHitTarget = self.OnHitTarget, + onMoreTargets = self.OnMoreTargets, + onDestroyed = self.OnDestroyed, + onKilled = self.OnKilled, + }, + endstates = { 'Success', 'Failed' } + } ) + + + _EVENTDISPATCHER:OnDead( self.EventDead, self ) + + return self +end + +--- Process Events + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnStart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Start ) +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnWaiting( Fsm, Event, From, To ) + + local TaskGroup = self.ProcessUnit:GetGroup() + if self.DisplayCount >= self.DisplayInterval then + MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup ) + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + return true -- Process always the event. + +end + + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function PROCESS_DESTROY:OnHitTarget( Fsm, Event, From, To, Event ) + + self.TargetSetUnit:Flush() + + if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then + self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName ) + end + + local TaskGroup = self.ProcessUnit:GetGroup() + MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup ) + + if self.TargetSetUnit:Count() > 0 then + self:NextEvent( Fsm.MoreTargets ) + else + self:NextEvent( Fsm.Destroyed ) + end +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnMoreTargets( Fsm, Event, From, To ) + + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA DCSEvent +function PROCESS_DESTROY:OnKilled( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Restart ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnRestart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Menu ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnDestroyed( Fsm, Event, From, To ) + +end + +--- DCS Events + +--- @param #PROCESS_DESTROY self +-- @param Event#EVENTDATA Event +function PROCESS_DESTROY:EventDead( Event ) + + if Event.IniDCSUnit then + self.TargetSetUnit:Remove( Event.IniDCSUnitName ) + self:NextEvent( self.Fsm.HitTarget, Event ) + end +end + + diff --git a/Moose Development/Moose/Process_Route.lua b/Moose Development/Moose/Process_Route.lua new file mode 100644 index 000000000..a3bb9fd66 --- /dev/null +++ b/Moose Development/Moose/Process_Route.lua @@ -0,0 +1,86 @@ +--- @module Task_Route + +--- PROCESS_ROUTE class +-- @type PROCESS_ROUTE +-- @field Task#TASK TASK +-- @field Unit#UNIT ProcessUnit +-- @field Zone#ZONE_BASE TargetZone +-- @extends Task2#TASK2 +PROCESS_ROUTE = { + ClassName = "PROCESS_ROUTE", +} + + +--- Creates a new routing state machine. The task will route a CLIENT to a ZONE until the CLIENT is within that ZONE. +-- @param #PROCESS_ROUTE self +-- @param Task#TASK Task +-- @param Unit#UNIT Unit +-- @return #PROCESS_ROUTE self +function PROCESS_ROUTE:New( Task, ProcessUnit, TargetZone ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ROUTE", Task, ProcessUnit ) ) -- #PROCESS_ROUTE + + self.TargetZone = TargetZone + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Route is the default display category + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnArrived', + events = { + { name = 'Start', from = 'UnArrived', to = 'UnArrived' }, + { name = 'Fail', from = 'UnArrived', to = 'Failed' }, + }, + callbacks = { + onleaveUnArrived = self.OnLeaveUnArrived, + onFail = self.OnFail, + }, + endstates = { + 'Arrived', 'Failed' + }, + } ) + + return self +end + +--- Task Events + +--- StateMachine callback function for a TASK2 +-- @param #PROCESS_ROUTE self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_ROUTE:OnLeaveUnArrived( Fsm, Event, From, To ) + + if self.ProcessUnit:IsAlive() then + local IsInZone = self.ProcessUnit:IsInZone( self.TargetZone ) + + if self.DisplayCount >= self.DisplayInterval then + if not IsInZone then + local ZoneVec2 = self.TargetZone:GetVec2() + local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) + local TaskUnitVec2 = self.ProcessUnit:GetVec2() + local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) + local RouteText = self.ProcessUnit:GetCallSign() .. ": Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." + MESSAGE:New( RouteText, self.DisplayTime, self.DisplayCategory ):ToGroup( self.ProcessUnit:GetGroup() ) + end + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + --if not IsInZone then + self:NextEvent( Fsm.Start ) + --end + + return IsInZone -- if false, then the event will not be executed... + end + + return false + +end + diff --git a/Moose Development/Moose/Process_Smoke.lua b/Moose Development/Moose/Process_Smoke.lua new file mode 100644 index 000000000..b228a7366 --- /dev/null +++ b/Moose Development/Moose/Process_Smoke.lua @@ -0,0 +1,106 @@ +--- @module Process_Smoke + +do -- PROCESS_SMOKE_TARGETS + + --- PROCESS_SMOKE_TARGETS class + -- @type PROCESS_SMOKE_TARGETS + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Set#SET_UNIT TargetSetUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_SMOKE_TARGETS = { + ClassName = "PROCESS_SMOKE_TARGETS", + } + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #PROCESS_SMOKE_TARGETS self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_SMOKE_TARGETS self + function PROCESS_SMOKE_TARGETS:New( Task, ProcessUnit, TargetSetUnit, TargetZone ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_SMOKE_TARGETS + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'None', + events = { + { name = 'Start', from = 'None', to = 'AwaitSmoke' }, + { name = 'Next', from = 'AwaitSmoke', to = 'Smoking' }, + { name = 'Next', from = 'Smoking', to = 'AwaitSmoke' }, + { name = 'Fail', from = 'Smoking', to = 'Failed' }, + { name = 'Fail', from = 'AwaitSmoke', to = 'Failed' }, + { name = 'Fail', from = 'None', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onNext = self.OnNext, + onSmoking = self.OnSmoking, + }, + endstates = { + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_SMOKE_TARGETS self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_SMOKE_TARGETS:OnStart( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self:E("Set smoke menu") + + local ProcessGroup = self.ProcessUnit:GetGroup() + local MissionMenu = self.Task.Mission:GetMissionMenu( ProcessGroup ) + + local function MenuSmoke( MenuParam ) + self:E( MenuParam ) + local self = MenuParam.self + local SmokeColor = MenuParam.SmokeColor + self.SmokeColor = SmokeColor + self:NextEvent( self.Fsm.Next ) + end + + self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) + self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) + self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) + self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) + self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) + self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_SMOKE_TARGETS self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_SMOKE_TARGETS:OnSmoking( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.TargetSetUnit:ForEachUnit( + --- @param Unit#UNIT SmokeUnit + function( SmokeUnit ) + if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then + SCHEDULER:New( self, + function() + SmokeUnit:Smoke( self.SmokeColor, 150 ) + end, {}, math.random( 10, 60 ) + ) + end + end + ) + + end + +end \ No newline at end of file diff --git a/Moose Development/Moose/Routines.lua b/Moose Development/Moose/Routines.lua index f5f42d146..3743752b3 100644 --- a/Moose Development/Moose/Routines.lua +++ b/Moose Development/Moose/Routines.lua @@ -245,22 +245,6 @@ end --- From http://lua-users.org/wiki/SimpleRound --- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place -routines.utils.round = function(num, idp) - local mult = 10^(idp or 0) - return math.floor(num * mult + 0.5) / mult -end - --- porting in Slmod's dostring -routines.utils.dostring = function(s) - local f, err = loadstring(s) - if f then - return true, f() - else - return false, err - end -end --3D Vector manipulation diff --git a/Moose Development/Moose/Scheduler.lua b/Moose Development/Moose/Scheduler.lua index 9d6b66295..afc2352f2 100644 --- a/Moose Development/Moose/Scheduler.lua +++ b/Moose Development/Moose/Scheduler.lua @@ -95,6 +95,7 @@ function SCHEDULER:Stop() self.Repeat = false if self.ScheduleID then + self:E( "Stop Schedule" ) timer.removeFunction( self.ScheduleID ) end self.ScheduleID = nil diff --git a/Moose Development/Moose/Scoring.lua b/Moose Development/Moose/Scoring.lua index f853a4bb4..f95eeda57 100644 --- a/Moose Development/Moose/Scoring.lua +++ b/Moose Development/Moose/Scoring.lua @@ -59,6 +59,8 @@ function SCORING:New( GameName ) self.SchedulerId = SCHEDULER:New( self, self._FollowPlayersScheduled, {}, 0, 5 ) self:ScoreMenu() + + self:OpenCSV( GameName) return self @@ -262,10 +264,17 @@ end --- Registers Scores the players completing a Mission Task. -function SCORING:_AddMissionTaskScore( PlayerUnit, MissionName, Score ) - self:F( { PlayerUnit, MissionName, Score } ) +-- @param #SCORING self +-- @param Mission#MISSION Mission +-- @param Unit#UNIT PlayerUnit +-- @param #string Text +-- @param #number Score +function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) - local PlayerName = PlayerUnit:getPlayerName() + local PlayerName = PlayerUnit:GetPlayerName() + local MissionName = Mission:GetName() + + self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) if not self.Players[PlayerName].Mission[MissionName] then self.Players[PlayerName].Mission[MissionName] = {} @@ -279,26 +288,37 @@ function SCORING:_AddMissionTaskScore( PlayerUnit, MissionName, Score ) self.Players[PlayerName].Score = self.Players[PlayerName].Score + Score self.Players[PlayerName].Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score - MESSAGE:New( "Player '" .. PlayerName .. "' has finished another Task in Mission '" .. MissionName .. "'. " .. - Score .. " Score points added.", - 20 ):ToAll() + MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. + Score .. " task score!", + 30 ):ToAll() - self:ScoreCSV( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:getName() ) + self:ScoreCSV( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() ) end --- Registers Mission Scores for possible multiple players that contributed in the Mission. -function SCORING:_AddMissionScore( MissionName, Score ) - self:F( { MissionName, Score } ) +-- @param #SCORING self +-- @param Mission#MISSION Mission +-- @param Unit#UNIT PlayerUnit +-- @param #string Text +-- @param #number Score +function SCORING:_AddMissionScore( Mission, Text, Score ) + + local MissionName = Mission:GetName() + + self:F( { Mission, Text, Score } ) for PlayerName, PlayerData in pairs( self.Players ) do if PlayerData.Mission[MissionName] then + PlayerData.Score = PlayerData.Score + Score PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score - MESSAGE:New( "Player '" .. PlayerName .. "' has finished Mission '" .. MissionName .. "'. " .. - Score .. " Score points added.", - 20 ):ToAll() + + MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. + Score .. " mission score!", + 60 ):ToAll() + self:ScoreCSV( PlayerName, "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score ) end end diff --git a/Moose Development/Moose/Set.lua b/Moose Development/Moose/Set.lua index 628d57b75..b4f37cfd7 100644 --- a/Moose Development/Moose/Set.lua +++ b/Moose Development/Moose/Set.lua @@ -224,10 +224,15 @@ --- SET_BASE class -- @type SET_BASE +-- @field #table Filter +-- @field #table Set +-- @field #table List -- @extends Base#BASE SET_BASE = { ClassName = "SET_BASE", + Filter = {}, Set = {}, + List = {}, } --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. @@ -246,6 +251,10 @@ function SET_BASE:New( Database ) self.YieldInterval = 10 self.TimeInterval = 0.001 + self.List = {} + self.List.__index = self.List + self.List = setmetatable( { Count = 0 }, self.List ) + return self end @@ -275,18 +284,90 @@ end -- @param Base#BASE Object -- @return Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) + self:F2( ObjectName ) - self.Set[ObjectName] = Object + local t = { _ = Object } + + if self.List.last then + self.List.last._next = t + t._prev = self.List.last + self.List.last = t + else + -- this is the first node + self.List.first = t + self.List.last = t + end + + self.List.Count = self.List.Count + 1 + + self.Set[ObjectName] = t._ + end --- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName function SET_BASE:Remove( ObjectName ) + self:E( ObjectName ) - self.Set[ObjectName] = nil + local t = self.Set[ObjectName] + + if t then + if t._next then + if t._prev then + t._next._prev = t._prev + t._prev._next = t._next + else + -- this was the first node + t._next._prev = nil + self.List._first = t._next + end + elseif t._prev then + -- this was the last node + t._prev._next = nil + self.List._last = t._prev + else + -- this was the only node + self.List._first = nil + self.List._last = nil + end + + t._next = nil + t._prev = nil + self.List.Count = self.List.Count - 1 + + self.Set[ObjectName] = nil + end + end +--- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. +-- @param #SET_BASE self +-- @return #number Count +function SET_BASE:Count() + + return self.List.Count +end + + + +--- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). +-- @param #SET_BASE self +-- @param #SET_BASE BaseSet +-- @return #SET_BASE +function SET_BASE:SetDatabase( BaseSet ) + + -- Copy the filter criteria of the BaseSet + local OtherFilter = routines.utils.deepCopy( BaseSet.Filter ) + self.Filter = OtherFilter + + -- Now base the new Set on the BaseSet + self.Database = BaseSet:GetSet() + return self +end + + + --- Define the SET iterator **"yield interval"** and the **"time interval"**. -- @param #SET_BASE self -- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. @@ -301,6 +382,20 @@ function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) end +--- Filters for the defined collection. +-- @param #SET_BASE self +-- @return #SET_BASE self +function SET_BASE:FilterOnce() + + for ObjectName, Object in pairs( self.Database ) do + + if self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + end + end + + return self +end --- Starts the filtering for the defined collection. -- @param #SET_BASE self @@ -320,13 +415,25 @@ function SET_BASE:_FilterStart() _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) -- Follow alive players and clients --- _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) --- _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) return self end +--- Stops the filtering for the defined collection. +-- @param #SET_BASE self +-- @return #SET_BASE self +function SET_BASE:FilterStop() + + _EVENTDISPATCHER:OnBirthRemove( self ) + _EVENTDISPATCHER:OnDeadRemove( self ) + _EVENTDISPATCHER:OnCrashRemove( self ) + + return self +end + --- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. -- @param #SET_BASE self -- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. @@ -340,9 +447,9 @@ function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) for ObjectID, ObjectData in pairs( self.Set ) do if NearestObject == nil then NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetPointVec2() ) + ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) else - local Distance = PointVec2:DistanceFromVec2( ObjectData:GetPointVec2() ) + local Distance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) if Distance < ClosestDistance then NearestObject = ObjectData ClosestDistance = Distance @@ -399,49 +506,58 @@ end -- @param #SET_BASE self -- @param Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) - self:F3( { Event } ) + self:E( { Event } ) if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) - if ObjectName and Object then + self:E({ObjectName, Object}) + if ObjectName and Object ~= nil then self:Remove( ObjectName ) end end end ------ Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). ----- @param #SET_BASE self ----- @param Event#EVENTDATA Event ---function SET_BASE:_EventOnPlayerEnterUnit( Event ) --- self:F3( { Event } ) --- --- if Event.IniDCSUnit then --- if self:IsIncludeObject( Event.IniDCSUnit ) then --- if not self.PlayersAlive[Event.IniDCSUnitName] then --- self:E( { "Add player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) --- self.PlayersAlive[Event.IniDCSUnitName] = Event.IniDCSUnit:getPlayerName() --- self.ClientsAlive[Event.IniDCSUnitName] = _DATABASE.Clients[ Event.IniDCSUnitName ] --- end --- end --- end ---end --- ------ Handles the OnPlayerLeaveUnit event to clean the active players table. ----- @param #SET_BASE self ----- @param Event#EVENTDATA Event ---function SET_BASE:_EventOnPlayerLeaveUnit( Event ) --- self:F3( { Event } ) --- --- if Event.IniDCSUnit then --- if self:IsIncludeObject( Event.IniDCSUnit ) then --- if self.PlayersAlive[Event.IniDCSUnitName] then --- self:E( { "Cleaning player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) --- self.PlayersAlive[Event.IniDCSUnitName] = nil --- self.ClientsAlive[Event.IniDCSUnitName] = nil --- end --- end --- end ---end +--- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). +-- @param #SET_BASE self +-- @param Event#EVENTDATA Event +function SET_BASE:_EventOnPlayerEnterUnit( Event ) + self:F3( { Event } ) + + if Event.IniDCSUnit then + local ObjectName, Object = self:AddInDatabase( Event ) + self:T3( ObjectName, Object ) + if self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + --self:_EventOnPlayerEnterUnit( Event ) + end + end +end + +--- Handles the OnPlayerLeaveUnit event to clean the active players table. +-- @param #SET_BASE self +-- @param Event#EVENTDATA Event +function SET_BASE:_EventOnPlayerLeaveUnit( Event ) + self:F3( { Event } ) + + local ObjectName = Event.IniDCSUnit + if Event.IniDCSUnit then + if Event.IniDCSGroup then + local GroupUnits = Event.IniDCSGroup:getUnits() + local PlayerCount = 0 + for _, DCSUnit in pairs( GroupUnits ) do + if DCSUnit ~= Event.IniDCSUnit then + if DCSUnit:getPlayer() ~= nil then + PlayerCount = PlayerCount + 1 + end + end + end + self:E(PlayerCount) + if PlayerCount == 0 then + self:Remove( Event.IniDCSGroupName ) + end + end + end +end -- Iterators @@ -454,7 +570,8 @@ function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArgumen local function CoRoutine() local Count = 0 - for ObjectID, Object in pairs( Set ) do + for ObjectID, ObjectData in pairs( Set ) do + local Object = ObjectData self:T3( Object ) if Function then if Function( unpack( FunctionArguments ), Object ) == true then @@ -554,7 +671,7 @@ function SET_BASE:Flush() for ObjectName, Object in pairs( self.Set ) do ObjectNames = ObjectNames .. ObjectName .. ", " end - self:T( { "Objects in Set:", ObjectNames } ) + self:E( { "Objects in Set:", ObjectNames } ) return ObjectNames end @@ -732,6 +849,8 @@ function SET_GROUP:FilterStart() self:_FilterStart() end + + return self end @@ -966,10 +1085,6 @@ function SET_UNIT:New() -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) - _EVENTDISPATCHER:OnBirth( self._EventOnBirth, self ) - _EVENTDISPATCHER:OnDead( self._EventOnDeadOrCrash, self ) - _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) - return self end @@ -1124,6 +1239,31 @@ function SET_UNIT:FilterPrefixes( Prefixes ) return self end +--- Builds a set of units having a radar of give types. +-- All the units having a radar of a given type will be included within the set. +-- @param #SET_UNIT self +-- @param #table RadarTypes The radar types. +-- @return #SET_UNIT self +function SET_UNIT:FilterHasRadar( RadarTypes ) + + self.Filter.RadarTypes = self.Filter.RadarTypes or {} + if type( RadarTypes ) ~= "table" then + RadarTypes = { RadarTypes } + end + for RadarTypeID, RadarType in pairs( RadarTypes ) do + self.Filter.RadarTypes[RadarType] = RadarType + end + return self +end + +--- Builds a set of SEADable units. +-- @param #SET_UNIT self +-- @return #SET_UNIT self +function SET_UNIT:FilterHasSEAD() + + self.Filter.SEAD = true + return self +end @@ -1163,9 +1303,10 @@ end -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:FindInDatabase( Event ) - self:F3( { Event } ) + self:E( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] + + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. @@ -1224,6 +1365,119 @@ function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) return self end +--- Returns a comma separated string of the unit types with a count in the @{Set}. +-- @param #SET_UNIT self +-- @return #string The unit types string +function SET_UNIT:GetUnitTypesText() + self:F2() + + local MT = {} -- Message Text + local UnitTypes = {} + + for UnitID, UnitData in pairs( self:GetSet() ) do + local TextUnit = UnitData -- Unit#UNIT + if TextUnit:IsAlive() then + local UnitType = TextUnit:GetTypeName() + + if not UnitTypes[UnitType] then + UnitTypes[UnitType] = 1 + else + UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + end + end + end + + for UnitTypeID, UnitType in pairs( UnitTypes ) do + MT[#MT+1] = UnitType .. " of " .. UnitTypeID + end + + return table.concat( MT, ", " ) +end + + +--- Returns if the @{Set} has targets having a radar (of a given type). +-- @param #SET_UNIT self +-- @param DCSUnit#Unit.RadarType RadarType +-- @return #number The amount of radars in the Set with the given type +function SET_UNIT:HasRadar( RadarType ) + self:F2( RadarType ) + + local RadarCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitSensorTest = UnitData -- Unit#UNIT + local HasSensors + if RadarType then + HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) + else + HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) + end + self:T3(HasSensors) + if HasSensors then + RadarCount = RadarCount + 1 + end + end + + return RadarCount +end + +--- Returns if the @{Set} has targets that can be SEADed. +-- @param #SET_UNIT self +-- @return #number The amount of SEADable units in the Set +function SET_UNIT:HasSEAD() + self:F2() + + local SEADCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitSEAD = UnitData -- Unit#UNIT + if UnitSEAD:IsAlive() then + local UnitSEADAttributes = UnitSEAD:GetDesc().attributes + + local HasSEAD = UnitSEAD:HasSEAD() + + self:T3(HasSEAD) + if HasSEAD then + SEADCount = SEADCount + 1 + end + end + end + + return SEADCount +end + +--- Returns if the @{Set} has ground targets. +-- @param #SET_UNIT self +-- @return #number The amount of ground targets in the Set. +function SET_UNIT:HasGroundUnits() + self:F2() + + local GroundUnitCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitTest = UnitData -- Unit#UNIT + if UnitTest:IsGround() then + GroundUnitCount = GroundUnitCount + 1 + end + end + + return GroundUnitCount +end + +--- Returns if the @{Set} has friendly ground units. +-- @param #SET_UNIT self +-- @return #number The amount of ground targets in the Set. +function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) + self:F2() + + local FriendlyUnitCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitTest = UnitData -- Unit#UNIT + if UnitTest:IsFriendly( FriendlyCoalition ) then + FriendlyUnitCount = FriendlyUnitCount + 1 + end + end + + return FriendlyUnitCount +end + ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. @@ -1315,6 +1569,29 @@ function SET_UNIT:IsIncludeObject( MUnit ) MUnitInclude = MUnitInclude and MUnitPrefix end + if self.Filter.RadarTypes then + local MUnitRadar = false + for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do + self:T3( { "Radar:", RadarType } ) + if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then + if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. + self:T3( "RADAR Found" ) + end + MUnitRadar = true + end + end + MUnitInclude = MUnitInclude and MUnitRadar + end + + if self.Filter.SEAD then + local MUnitSEAD = false + if MUnit:HasSEAD() == true then + self:T3( "SEAD Found" ) + MUnitSEAD = true + end + MUnitInclude = MUnitInclude and MUnitSEAD + end + self:T2( MUnitInclude ) return MUnitInclude end diff --git a/Moose Development/Moose/Spawn.lua b/Moose Development/Moose/Spawn.lua index 5ce9654ec..4606a1e0e 100644 --- a/Moose Development/Moose/Spawn.lua +++ b/Moose Development/Moose/Spawn.lua @@ -565,7 +565,7 @@ function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex ) if SpawnTemplate then - local UnitPoint = HostUnit:GetPointVec2() + local UnitPoint = HostUnit:GetVec2() self:T( { "Current point of ", self.SpawnTemplatePrefix, UnitPoint } ) @@ -648,7 +648,7 @@ function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex ) if ZoneRandomize == true then ZonePoint = Zone:GetRandomVec2() else - ZonePoint = Zone:GetPointVec2() + ZonePoint = Zone:GetVec2() end SpawnTemplate.route.points[1].x = ZonePoint.x diff --git a/Moose Development/Moose/StateMachine.lua b/Moose Development/Moose/StateMachine.lua new file mode 100644 index 000000000..685ce7e18 --- /dev/null +++ b/Moose Development/Moose/StateMachine.lua @@ -0,0 +1,283 @@ +--- This module contains the STATEMACHINE class. +-- This development is based on a state machine implementation made by Conroy Kyle. +-- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine +-- +-- I've taken the development and enhanced it to make the state machine hierarchical... +-- It is a fantastic development, this module. +-- +-- === +-- +-- 1) @{Workflow#STATEMACHINE} class, extends @{Base#BASE} +-- ============================================== +-- +-- 1.1) Add or remove objects from the STATEMACHINE +-- -------------------------------------------- +-- @module StateMachine +-- @author FlightControl + + +--- STATEMACHINE class +-- @type STATEMACHINE +STATEMACHINE = { + ClassName = "STATEMACHINE", +} + +--- Creates a new STATEMACHINE object. +-- @param #STATEMACHINE self +-- @return #STATEMACHINE +function STATEMACHINE:New( options ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) + + + --local self = routines.utils.deepCopy( self ) -- Create a new self instance + + assert(options.events) + + --local MT = {} + --setmetatable( self, MT ) + --self.__index = self + + self.options = options + self.current = options.initial or 'none' + self.events = {} + self.subs = {} + self.endstates = {} + + for _, event in ipairs(options.events or {}) do + local name = event.name + self[name] = self[name] or self:_create_transition(name) + self.events[name] = self.events[name] or { map = {} } + self:_add_to_map(self.events[name].map, event) + end + + for name, callback in pairs(options.callbacks or {}) do + self[name] = callback + end + + for name, sub in pairs( options.subs or {} ) do + self:_submap( self.subs, sub, name ) + end + + for name, endstate in pairs( options.endstates or {} ) do + self.endstates[endstate] = endstate + end + + return self +end + + +function STATEMACHINE:_submap( subs, sub, name ) + self:E( { sub = sub, name = name } ) + subs[sub.onstateparent] = subs[sub.onstateparent] or {} + subs[sub.onstateparent][sub.oneventparent] = subs[sub.onstateparent][sub.oneventparent] or {} + local Index = #subs[sub.onstateparent][sub.oneventparent] + 1 + subs[sub.onstateparent][sub.oneventparent][Index] = {} + subs[sub.onstateparent][sub.oneventparent][Index].fsm = sub.fsm + subs[sub.onstateparent][sub.oneventparent][Index].event = sub.event + subs[sub.onstateparent][sub.oneventparent][Index].returnevents = sub.returnevents -- these events need to be given to find the correct continue event ... if none given, the processing will stop. + subs[sub.onstateparent][sub.oneventparent][Index].name = name + subs[sub.onstateparent][sub.oneventparent][Index].fsmparent = self +end + + +function STATEMACHINE:_call_handler(handler, params) + if handler then + return handler(unpack(params)) + end +end + +function STATEMACHINE:_create_transition(name) + self:E( { name = name } ) + return function(self, ...) + local can, to = self:can(name) + self:T( { name, can, to } ) + + if can then + local from = self.current + local params = { self, name, from, to, ... } + + if self:_call_handler(self["onbefore" .. name], params) == false + or self:_call_handler(self["onleave" .. from], params) == false then + return false + end + + self.current = to + + local execute = true + + local subtable = self:_gosub( to, name ) + for _, sub in pairs( subtable ) do + self:E( "calling sub: " .. sub.event ) + sub.fsm.fsmparent = self + sub.fsm.returnevents = sub.returnevents + sub.fsm[sub.event]( sub.fsm ) + execute = true + end + + local fsmparent, event = self:_isendstate( to ) + if fsmparent and event then + self:E( { "end state: ", fsmparent, event } ) + self:_call_handler(self["onenter" .. to] or self["on" .. to], params) + self:_call_handler(self["onafter" .. name] or self["on" .. name], params) + self:_call_handler(self["onstatechange"], params) + fsmparent[event]( fsmparent ) + execute = false + end + + if execute then + self:E( { "execute: " .. to, name } ) + self:_call_handler(self["onenter" .. to] or self["on" .. to], params) + self:_call_handler(self["onafter" .. name] or self["on" .. name], params) + self:_call_handler(self["onstatechange"], params) + end + + return true + end + + return false + end +end + +function STATEMACHINE:_gosub( parentstate, parentevent ) + local fsmtable = {} + if self.subs[parentstate] and self.subs[parentstate][parentevent] then + return self.subs[parentstate][parentevent] + else + return {} + end +end + +function STATEMACHINE:_isendstate( state ) + local fsmparent = self.fsmparent + if fsmparent and self.endstates[state] then + self:E( { state = state, endstates = self.endstates, endstate = self.endstates[state] } ) + local returnevent = nil + local fromstate = fsmparent.current + self:E( fromstate ) + self:E( self.returnevents ) + for _, eventname in pairs( self.returnevents ) do + local event = fsmparent.events[eventname] + self:E( event ) + local to = event and event.map[fromstate] or event.map['*'] + if to and to == state then + return fsmparent, eventname + else + self:E( { "could not find parent event name for state", fromstate, to } ) + end + end + end + + return nil +end + +function STATEMACHINE:_add_to_map(map, event) + if type(event.from) == 'string' then + map[event.from] = event.to + else + for _, from in ipairs(event.from) do + map[from] = event.to + end + end +end + +function STATEMACHINE:is(state) + return self.current == state +end + +function STATEMACHINE:can(e) + local event = self.events[e] + local to = event and event.map[self.current] or event.map['*'] + return to ~= nil, to +end + +function STATEMACHINE:cannot(e) + return not self:can(e) +end + +function STATEMACHINE:todot(filename) + local dotfile = io.open(filename,'w') + dotfile:write('digraph {\n') + local transition = function(event,from,to) + dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) + end + for _, event in pairs(self.options.events) do + if type(event.from) == 'table' then + for _, from in ipairs(event.from) do + transition(event.name,from,event.to) + end + else + transition(event.name,event.from,event.to) + end + end + dotfile:write('}\n') + dotfile:close() +end + +--- STATEMACHINE_PROCESS class +-- @type STATEMACHINE_PROCESS +-- @field Process#PROCESS Process +-- @extends StateMachine#STATEMACHINE +STATEMACHINE_PROCESS = { + ClassName = "STATEMACHINE_PROCESS", +} + +--- Creates a new STATEMACHINE_PROCESS object. +-- @param #STATEMACHINE_PROCESS self +-- @return #STATEMACHINE_PROCESS +function STATEMACHINE_PROCESS:New( Process, options ) + + local FsmProcess = routines.utils.deepCopy( self ) -- Create a new self instance + local Parent = STATEMACHINE:New(options) + + setmetatable( FsmProcess, Parent ) + FsmProcess.__index = FsmProcess + + FsmProcess["onstatechange"] = Process.OnStateChange + FsmProcess.Process = Process + + return FsmProcess +end + +function STATEMACHINE_PROCESS:_call_handler( handler, params ) + if handler then + return handler( self.Process, unpack( params ) ) + end +end + +--- STATEMACHINE_TASK class +-- @type STATEMACHINE_TASK +-- @field Task#TASK_BASE Task +-- @extends StateMachine#STATEMACHINE +STATEMACHINE_TASK = { + ClassName = "STATEMACHINE_TASK", +} + +--- Creates a new STATEMACHINE_TASK object. +-- @param #STATEMACHINE_TASK self +-- @return #STATEMACHINE_TASK +function STATEMACHINE_TASK:New( Task, TaskUnit, options ) + + local FsmTask = routines.utils.deepCopy( self ) -- Create a new self instance + local Parent = STATEMACHINE:New(options) + + setmetatable( FsmTask, Parent ) + FsmTask.__index = FsmTask + + FsmTask["onstatechange"] = Task.OnStateChange + FsmTask["onAssigned"] = Task.OnAssigned + FsmTask["onSuccess"] = Task.OnSuccess + FsmTask["onFailed"] = Task.OnFailed + + FsmTask.Task = Task + FsmTask.TaskUnit = TaskUnit + + return FsmTask +end + +function STATEMACHINE_TASK:_call_handler( handler, params ) + if handler then + return handler( self.Task, self.TaskUnit, unpack( params ) ) + end +end diff --git a/Moose Development/Moose/Task.lua b/Moose Development/Moose/Task.lua index 8ff24a085..c01cb353a 100644 --- a/Moose Development/Moose/Task.lua +++ b/Moose Development/Moose/Task.lua @@ -1,465 +1,865 @@ ---- The TASK Classes define major end-to-end activities within a MISSION. The TASK Class is the Master Class to orchestrate these activities. From this class, many concrete TASK classes are inherited. --- @module TASK +--- This module contains the TASK_BASE class. +-- +-- 1) @{#TASK_BASE} class, extends @{Base#BASE} +-- ============================================ +-- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. +-- ---------------------------------------------------------------------------------------- +-- The class provides a couple of methods to: +-- +-- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). +-- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. +-- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. +-- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. +-- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. +-- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} +-- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. +-- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. +-- +-- 1.2) Set and enquire task status (beyond the task state machine processing). +-- ---------------------------------------------------------------------------- +-- A task needs to implement as a minimum the following task states: +-- +-- * **Success**: Expresses the successful execution and finalization of the task. +-- * **Failed**: Expresses the failure of a task. +-- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. +-- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. +-- +-- A task may also implement the following task states: +-- +-- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. +-- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. +-- +-- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. +-- +-- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. +-- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. +-- +-- 1.3) Add scoring when reaching a certain task status: +-- ----------------------------------------------------- +-- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +-- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. +-- +-- 1.4) Task briefing: +-- ------------------- +-- A task briefing can be given that is shown to the player when he is assigned to the task. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task - - - - - - ---- The TASK class --- @type TASK +--- The TASK_BASE class +-- @type TASK_BASE +-- @field Scheduler#SCHEDULER TaskScheduler +-- @field Mission#MISSION Mission +-- @field StateMachine#STATEMACHINE Fsm +-- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task -- @extends Base#BASE -TASK = { - - -- Defines the different signal types with a Task. - SIGNAL = { - COLOR = { - RED = { ID = 1, COLOR = trigger.smokeColor.Red, TEXT = "A red" }, - GREEN = { ID = 2, COLOR = trigger.smokeColor.Green, TEXT = "A green" }, - BLUE = { ID = 3, COLOR = trigger.smokeColor.Blue, TEXT = "A blue" }, - WHITE = { ID = 4, COLOR = trigger.smokeColor.White, TEXT = "A white" }, - ORANGE = { ID = 5, COLOR = trigger.smokeColor.Orange, TEXT = "An orange" } - }, - TYPE = { - SMOKE = { ID = 1, TEXT = "smoke" }, - FLARE = { ID = 2, TEXT = "flare" } - } - }, - ClassName = "TASK", - Mission = {}, -- Owning mission of the Task - Name = '', - Stages = {}, - Stage = {}, - Cargos = { - InitCargos = {}, - LoadCargos = {} - }, - LandingZones = { - LandingZoneNames = {}, - LandingZones = {} - }, - ActiveStage = 0, - TaskDone = false, - TaskFailed = false, - GoalTasks = {} +TASK_BASE = { + ClassName = "TASK_BASE", + TaskScheduler = nil, + Processes = {}, + Players = nil, + Scores = {}, + Menu = {}, + SetGroup = nil, } ---- Instantiates a new TASK Base. Should never be used. Interface Class. --- @return TASK -function TASK:New() + +--- Instantiates a new TASK_BASE. Should never be used. Interface Class. +-- @param #TASK_BASE self +-- @param Mission#MISSION The mission wherein the Task is registered. +-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. +-- @param #string TaskName The name of the Task +-- @param #string TaskType The type of the Task +-- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) +-- @return #TASK_BASE self +function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) + local self = BASE:Inherit( self, BASE:New() ) - self:F() + self:E( "New TASK " .. TaskName ) + + self.Processes = {} + self.Fsm = {} + + self.Mission = Mission + self.SetGroup = SetGroup + + self:SetCategory( TaskCategory ) + self:SetType( TaskType ) + self:SetName( TaskName ) + self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. + + self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - -- assign Task default values during construction - self.TaskBriefing = "Task: No Task." - self.Time = timer.getTime() - self.ExecuteStage = _TransportExecuteStage.NONE + return self +end + +--- Cleans all references of a TASK_BASE. +-- @param #TASK_BASE self +-- @return #nil +function TASK_BASE:CleanUp() + + _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) + _EVENTDISPATCHER:OnDeadRemove( self ) + _EVENTDISPATCHER:OnCrashRemove( self ) + _EVENTDISPATCHER:OnPilotDeadRemove( self ) + + return nil +end + + +--- Assign the @{Task}to a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +function TASK_BASE:AssignToGroup( TaskGroup ) + self:F2( TaskGroup:GetName() ) + + local TaskGroupName = TaskGroup:GetName() + + TaskGroup:SetState( TaskGroup, "Assigned", self ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:AssignToUnit( TaskUnit ) + end + end +end + +--- Send the briefng message of the @{Task} to the assigned @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:SendBriefingToAssignedGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + if self:IsAssignedToGroup( TaskGroup ) then + TaskGroup:Message( self.TaskBriefing, 60 ) + end + end +end + + +--- Assign the @{Task} from the @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:UnAssignFromGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:UnAssignFromUnit( TaskUnit ) + end + end + end +end + +--- Returns if the @{Task} is assigned to the Group. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #boolean +function TASK_BASE:IsAssignedToGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + if self:IsStateAssigned() then + if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then + return true + end + end + + return false +end + +--- Assign the @{Task}to an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + return nil +end + +--- UnAssign the @{Task} from an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:UnAssignFromUnit( TaskUnitName ) + self:F( TaskUnitName ) + + if self:HasStateMachine( TaskUnitName ) == true then + self:RemoveStateMachines( TaskUnitName ) + self:RemoveProcesses( TaskUnitName ) + end return self end -function TASK:SetStage( StageSequenceIncrement ) - self:F( { StageSequenceIncrement } ) +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenu() - local Valid = false - if StageSequenceIncrement ~= 0 then - self.ActiveStage = self.ActiveStage + StageSequenceIncrement - if 1 <= self.ActiveStage and self.ActiveStage <= #self.Stages then - self.Stage = self.Stages[self.ActiveStage] - self:T( { self.Stage.Name } ) - self.Frequency = self.Stage.Frequency - Valid = true - else - Valid = false - env.info( "TASK:SetStage() self.ActiveStage is smaller or larger than self.Stages array. self.ActiveStage = " .. self.ActiveStage ) - end - end - self.Time = timer.getTime() - return Valid + local MenuText = self:GetPlannedMenuText() + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not self:IsAssignedToGroup( TaskGroup ) then + self:SetPlannedMenuForGroup( TaskGroup, MenuText ) + end + end end -function TASK:Init() - self:F() - self.ActiveStage = 0 - self:SetStage(1) - self.TaskDone = false - self.TaskFailed = false +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if self:IsAssignedToGroup( TaskGroup ) then + self:SetAssignedMenuForGroup( TaskGroup ) + end + end end +--- Remove the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenu() ---- Get progress of a TASK. --- @return string GoalsText -function TASK:GetGoalProgress() - self:F2() - - local GoalsText = "" - for GoalVerb, GoalVerbData in pairs( self.GoalTasks ) do - local Goals = self:GetGoalCompletion( GoalVerb ) - if Goals and Goals ~= "" then - Goals = '(' .. Goals .. ')' - else - Goals = '( - )' - end - GoalsText = GoalsText .. GoalVerb .. ': ' .. self:GetGoalCount(GoalVerb) .. ' goals ' .. Goals .. ' of ' .. self:GetGoalTotal(GoalVerb) .. ' goals completed (' .. self:GetGoalPercentage(GoalVerb) .. '%); ' - end - - if GoalsText == "" then - GoalsText = "( - )" - end - - return GoalsText + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + end end ---- Show progress of a TASK. --- @param MISSION Mission Group structure describing the Mission. --- @param CLIENT Client Group structure describing the Client. -function TASK:ShowGoalProgress( Mission, Client ) - self:F2() +--- Set the planned menu option of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @param #string MenuText The menu text. +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) + self:E( TaskGroup:GetName() ) - local GoalsText = "" - for GoalVerb, GoalVerbData in pairs( self.GoalTasks ) do - if Mission:IsCompleted() then - else - local Goals = self:GetGoalCompletion( GoalVerb ) - if Goals and Goals ~= "" then - else - Goals = "-" - end - GoalsText = GoalsText .. self:GetGoalProgress() - end - end - - if Mission.MissionReportFlash or Mission.MissionReportShow then - Client:Message( GoalsText, 10, "Mission Command: Task Status", 30, "Task status" ) - end + local TaskMission = self.Mission:GetName() + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + Mission.MenuCategory = Mission.MenuCategory or {} + local MenuCategory = Mission.MenuCategory + + Mission.MenuType = Mission.MenuType or {} + local MenuType = Mission.MenuType + + self.Menu = self.Menu or {} + local Menu = self.Menu + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + + MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} + MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) + + MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} + MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) + + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + end + Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) + + return self end ---- Sets a TASK to status Done. -function TASK:Done() - self:F2() - self.TaskDone = true +--- Set the assigned menu options of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) + self:E( TaskGroup:GetName() ) + + local TaskMission = self.Mission:GetName() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + self.MenuStatus = self.MenuStatus or {} + local MenuStatus = self.MenuStatus + + + self.MenuAbort = self.MenuAbort or {} + local MenuAbort = self.MenuAbort + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) + MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) + + return self end ---- Returns if a TASK is done. --- @return bool -function TASK:IsDone() - self:F2( self.TaskDone ) - return self.TaskDone -end +--- Remove the menu option of the @{Task} for a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenuForGroup( TaskGroup ) ---- Sets a TASK to status failed. -function TASK:Failed() - self:F() - self.TaskFailed = true -end + local TaskGroupName = TaskGroup:GetName() + + local Mission = self.Mission + local MenuMission = Mission.MenuMission + local MenuCategory = Mission.MenuCategory + local MenuType = Mission.MenuType + local MenuStatus = self.MenuStatus + local MenuAbort = self.MenuAbort + local Menu = self.Menu ---- Returns if a TASk has failed. --- @return bool -function TASK:IsFailed() - self:F2( self.TaskFailed ) - return self.TaskFailed -end + Menu = Menu or {} + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + Menu[TaskGroupName] = nil + end -function TASK:Reset( Mission, Client ) - self:F2() - self.ExecuteStage = _TransportExecuteStage.NONE -end + MenuType = MenuType or {} + if MenuType[TaskGroupName] then + for _, Menu in pairs( MenuType[TaskGroupName] ) do + Menu:Remove() + end + MenuType[TaskGroupName] = nil + end ---- Returns the Goals of a TASK --- @return @table Goals -function TASK:GetGoals() - return self.GoalTasks -end - ---- Returns if a TASK has Goal(s). --- @param #TASK self --- @param #string GoalVerb is the name of the Goal of the TASK. --- @return bool -function TASK:Goal( GoalVerb ) - self:F2( { GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - self:T2( {self.GoalTasks[GoalVerb] } ) - if self.GoalTasks[GoalVerb] and self.GoalTasks[GoalVerb].GoalTotal > 0 then - return true - else - return false - end -end - ---- Sets the total Goals to be achieved of the Goal Name --- @param number GoalTotal is the number of times the GoalVerb needs to be achieved. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. -function TASK:SetGoalTotal( GoalTotal, GoalVerb ) - self:F2( { GoalTotal, GoalVerb } ) - - if not GoalVerb then - GoalVerb = self.GoalVerb - end - self.GoalTasks[GoalVerb] = {} - self.GoalTasks[GoalVerb].Goals = {} - self.GoalTasks[GoalVerb].GoalTotal = GoalTotal - self.GoalTasks[GoalVerb].GoalCount = 0 - return self -end - ---- Gets the total of Goals to be achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. -function TASK:GetGoalTotal( GoalVerb ) - self:F2( { GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return self.GoalTasks[GoalVerb].GoalTotal - else - return 0 - end -end - ---- Sets the total of Goals currently achieved within the TASK of the GoalVerb. --- @param number GoalCount is the total number of Goals achieved within the TASK. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:SetGoalCount( GoalCount, GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb) then - self.GoalTasks[GoalVerb].GoalCount = GoalCount - end - return self -end - ---- Increments the total of Goals currently achieved within the TASK of the GoalVerb, with the given GoalCountIncrease. --- @param number GoalCountIncrease is the number of new Goals achieved within the TASK. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:IncreaseGoalCount( GoalCountIncrease, GoalVerb ) - self:F2( { GoalCountIncrease, GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb) then - self.GoalTasks[GoalVerb].GoalCount = self.GoalTasks[GoalVerb].GoalCount + GoalCountIncrease - end - return self -end - ---- Gets the total of Goals currently achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:GetGoalCount( GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return self.GoalTasks[GoalVerb].GoalCount - else - return 0 - end -end - ---- Gets the percentage of Goals currently achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:GetGoalPercentage( GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return math.floor( self:GetGoalCount( GoalVerb ) / self:GetGoalTotal( GoalVerb ) * 100 + .5 ) - else - return 100 - end -end - ---- Returns if all the Goals of the TASK were achieved. --- @return bool -function TASK:IsGoalReached() - self:F2() - - local GoalReached = true - - for GoalVerb, Goals in pairs( self.GoalTasks ) do - self:T2( { "GoalVerb", GoalVerb } ) - if self:Goal( GoalVerb ) then - local GoalToDo = self:GetGoalTotal( GoalVerb ) - self:GetGoalCount( GoalVerb ) - self:T2( "GoalToDo = " .. GoalToDo ) - if GoalToDo <= 0 then - else - GoalReached = false - break - end - else - break - end - end - - self:T( { GoalReached, self.GoalTasks } ) - return GoalReached -end - ---- Adds an Additional Goal for the TASK to be achieved. --- @param string GoalVerb is the name of the Goal of the TASK. --- @param string GoalTask is a text describing the Goal of the TASK to be achieved. --- @param number GoalIncrease is a number by which the Goal achievement is increasing. -function TASK:AddGoalCompletion( GoalVerb, GoalTask, GoalIncrease ) - self:F2( { GoalVerb, GoalTask, GoalIncrease } ) - - if self:Goal( GoalVerb ) then - self.GoalTasks[GoalVerb].Goals[#self.GoalTasks[GoalVerb].Goals+1] = GoalTask - self.GoalTasks[GoalVerb].GoalCount = self.GoalTasks[GoalVerb].GoalCount + GoalIncrease - end - return self -end - ---- Returns if the additional Goal for the TASK was completed. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return string Goals -function TASK:GetGoalCompletion( GoalVerb ) - self:F2( { GoalVerb } ) - - if self:Goal( GoalVerb ) then - local Goals = "" - for GoalID, GoalName in pairs( self.GoalTasks[GoalVerb].Goals ) do Goals = Goals .. GoalName .. " + " end - return Goals:gsub(" + $", ""), self.GoalTasks[GoalVerb].GoalCount - end -end - -function TASK.MenuAction( Parameter ) - Parameter.ReferenceTask.ExecuteStage = _TransportExecuteStage.EXECUTING - Parameter.ReferenceTask.Cargo = Parameter.CargoTask -end - -function TASK:StageExecute() - self:F() - - local Execute = false - - if self.Frequency == STAGE.FREQUENCY.REPEAT then - Execute = true - elseif self.Frequency == STAGE.FREQUENCY.NONE then - Execute = false - elseif self.Frequency >= 0 then - Execute = true - self.Frequency = self.Frequency - 1 + MenuCategory = MenuCategory or {} + if MenuCategory[TaskGroupName] then + for _, Menu in pairs( MenuCategory[TaskGroupName] ) do + Menu:Remove() + end + MenuCategory[TaskGroupName] = nil end - return Execute - -end - ---- Work function to set signal events within a TASK. -function TASK:AddSignal( SignalUnitNames, SignalType, SignalColor, SignalHeight ) - self:F() + MenuStatus = MenuStatus or {} + if MenuStatus[TaskGroupName] then + MenuStatus[TaskGroupName]:Remove() + MenuStatus[TaskGroupName] = nil + end - local Valid = true - - if Valid then - if type( SignalUnitNames ) == "table" then - self.LandingZoneSignalUnitNames = SignalUnitNames - else - self.LandingZoneSignalUnitNames = { SignalUnitNames } - end - self.LandingZoneSignalType = SignalType - self.LandingZoneSignalColor = SignalColor - self.Signalled = false - if SignalHeight ~= nil then - self.LandingZoneSignalHeight = SignalHeight - else - self.LandingZoneSignalHeight = 0 - end - - if self.TaskBriefing then - self.TaskBriefing = self.TaskBriefing .. " " .. SignalColor.TEXT .. " " .. SignalType.TEXT .. " will be fired when entering the landing zone." - end - end - - return Valid + MenuAbort = MenuAbort or {} + if MenuAbort[TaskGroupName] then + MenuAbort[TaskGroupName]:Remove() + MenuAbort[TaskGroupName] = nil + end + end ---- When the CLIENT is approaching the landing zone, a RED SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeRed( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.RED, SignalHeight ) +function TASK_BASE.MenuAssignToGroup( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, a GREEN SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeGreen( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.GREEN, SignalHeight ) -end - ---- When the CLIENT is approaching the landing zone, a BLUE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeBlue( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.BLUE, SignalHeight ) +function TASK_BASE.MenuTaskStatus( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, a WHITE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeWhite( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.WHITE, SignalHeight ) +function TASK_BASE.MenuTaskAbort( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, an ORANGE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeOrange( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.ORANGE, SignalHeight ) + + +--- Returns the @{Task} name. +-- @param #TASK_BASE self +-- @return #string TaskName +function TASK_BASE:GetTaskName() + return self.TaskName end ---- When the CLIENT is approaching the landing zone, a RED FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareRed( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.RED, SignalHeight ) + +--- Add Process to @{Task} with key @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddProcess( TaskUnit, Process ) + local TaskUnitName = TaskUnit:GetName() + self.Processes = self.Processes or {} + self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} + self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process + return Process end ---- When the CLIENT is approaching the landing zone, a GREEN FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareGreen( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.GREEN, SignalHeight ) -end - ---- When the CLIENT is approaching the landing zone, a BLUE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareBlue( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.BLUE, SignalHeight ) + +--- Remove Processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + Process:StopEvents() + Process = nil + self.Processes[TaskUnitName][ProcessID] = nil + self:E( self.Processes[TaskUnitName][ProcessID] ) + end + self.Processes[TaskUnitName] = nil end ---- When the CLIENT is approaching the landing zone, a WHITE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareWhite( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.WHITE, SignalHeight ) +--- Fail processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:FailProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + self:E( { "Failing process: ", Process } ) + Process.Fsm:Fail() + end end ---- When the CLIENT is approaching the landing zone, an ORANGE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareOrange( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.ORANGE, SignalHeight ) +--- Add a FiniteStateMachine to @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) + local TaskUnitName = TaskUnit:GetName() + self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} + self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm + return Fsm end + +--- Remove FiniteStateMachines from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveStateMachines( TaskUnitName ) + + for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do + Fsm = nil + self.Fsm[TaskUnitName][_] = nil + self:E( self.Fsm[TaskUnitName][_] ) + end + self.Fsm[TaskUnitName] = nil +end + +--- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:HasStateMachine( TaskUnitName ) + + self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) + return ( self.Fsm[TaskUnitName] ~= nil ) +end + + + + + +--- Register a potential new assignment for a new spawned @{Unit}. +-- Tasks only get assigned if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventAssignUnit( Event ) + if Event.IniUnit then + self:F( Event ) + local TaskUnit = Event.IniUnit + if TaskUnit:IsAlive() then + local TaskPlayerName = TaskUnit:GetPlayerName() + if TaskPlayerName ~= nil then + if not self:HasStateMachine( TaskUnit ) then + -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. + local TaskGroup = TaskUnit:GetGroup() + if self:IsAssignedToGroup( TaskGroup ) then + self:AssignToUnit( TaskUnit ) + end + end + end + end + end + return nil +end + +--- Catches the "player leave unit" event for a @{Unit} .... +-- When a player is an air unit, and leaves the unit: +-- +-- * and he is not at an airbase runway on the ground, he will fail its task. +-- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. +-- This is important to model the change from plane types for a player during mission assignment. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventPlayerLeaveUnit( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + if TaskUnit:IsAir() then + if TaskUnit:IsAboveRunway() then + -- do nothing + else + self:E( "IsNotAboveRunway" ) + -- Player left airplane during an assigned task and was not at an airbase. + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + end + end + + end + return nil +end + +--- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... +-- There are only assignments if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventDead( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + + local TaskGroup = Event.IniUnit:GetGroup() + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + end + return nil +end + +--- Gets the Scoring of the task +-- @param #TASK_BASE self +-- @return Scoring#SCORING Scoring +function TASK_BASE:GetScoring() + return self.Mission:GetScoring() +end + + +--- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. +-- @param #TASK_BASE self +-- @return #string The Task ID +function TASK_BASE:GetTaskIndex() + + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + local TaskName = self:GetName() + + return TaskCategory .. "." ..TaskType .. "." .. TaskName +end + +--- Sets the Name of the Task +-- @param #TASK_BASE self +-- @param #string TaskName +function TASK_BASE:SetName( TaskName ) + self.TaskName = TaskName +end + +--- Gets the Name of the Task +-- @param #TASK_BASE self +-- @return #string The Task Name +function TASK_BASE:GetName() + return self.TaskName +end + +--- Sets the Type of the Task +-- @param #TASK_BASE self +-- @param #string TaskType +function TASK_BASE:SetType( TaskType ) + self.TaskType = TaskType +end + +--- Gets the Type of the Task +-- @param #TASK_BASE self +-- @return #string TaskType +function TASK_BASE:GetType() + return self.TaskType +end + +--- Sets the Category of the Task +-- @param #TASK_BASE self +-- @param #string TaskCategory +function TASK_BASE:SetCategory( TaskCategory ) + self.TaskCategory = TaskCategory +end + +--- Gets the Category of the Task +-- @param #TASK_BASE self +-- @return #string TaskCategory +function TASK_BASE:GetCategory() + return self.TaskCategory +end + +--- Sets the ID of the Task +-- @param #TASK_BASE self +-- @param #string TaskID +function TASK_BASE:SetID( TaskID ) + self.TaskID = TaskID +end + +--- Gets the ID of the Task +-- @param #TASK_BASE self +-- @return #string TaskID +function TASK_BASE:GetID() + return self.TaskID +end + + +--- Sets a @{Task} to status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:StateSuccess() + self:SetState( self, "State", "Success" ) + return self +end + +--- Is the @{Task} status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateSuccess() + return self:GetStateString() == "Success" +end + +--- Sets a @{Task} to status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:StateFailed() + self:SetState( self, "State", "Failed" ) + return self +end + +--- Is the @{Task} status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateFailed() + return self:GetStateString() == "Failed" +end + +--- Sets a @{Task} to status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:StatePlanned() + self:SetState( self, "State", "Planned" ) + return self +end + +--- Is the @{Task} status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStatePlanned() + return self:GetStateString() == "Planned" +end + +--- Sets a @{Task} to status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:StateAssigned() + self:SetState( self, "State", "Assigned" ) + return self +end + +--- Is the @{Task} status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateAssigned() + return self:GetStateString() == "Assigned" +end + +--- Sets a @{Task} to status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:StateHold() + self:SetState( self, "State", "Hold" ) + return self +end + +--- Is the @{Task} status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateHold() + return self:GetStateString() == "Hold" +end + +--- Sets a @{Task} to status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:StateReplanned() + self:SetState( self, "State", "Replanned" ) + return self +end + +--- Is the @{Task} status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateReplanned() + return self:GetStateString() == "Replanned" +end + +--- Gets the @{Task} status. +-- @param #TASK_BASE self +function TASK_BASE:GetStateString() + return self:GetState( self, "State" ) +end + +--- Sets a @{Task} briefing. +-- @param #TASK_BASE self +-- @param #string TaskBriefing +-- @return #TASK_BASE self +function TASK_BASE:SetBriefing( TaskBriefing ) + self.TaskBriefing = TaskBriefing + return self +end + + + +--- Adds a score for the TASK to be achieved. +-- @param #TASK_BASE self +-- @param #string TaskStatus is the status of the TASK when the score needs to be given. +-- @param #string ScoreText is a text describing the score that is given according the status. +-- @param #number Score is a number providing the score of the status. +-- @return #TASK_BASE self +function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) + self:F2( { TaskStatus, ScoreText, Score } ) + + self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} + self.Scores[TaskStatus].ScoreText = ScoreText + self.Scores[TaskStatus].Score = Score + return self +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) + + self:E("Assigned") + + local TaskGroup = TaskUnit:GetGroup() + + TaskGroup:Message( self.TaskBriefing, 20 ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + +end + + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) + + self:E("Success") + + self:UnAssignFromGroups() + + local TaskGroup = TaskUnit:GetGroup() + self.Mission:SetPlannedMenu() + + self:StateSuccess() + + -- The task has become successful, the event catchers can be cleaned. + self:CleanUp() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) + + self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) + + -- A task cannot be "failed", so a task will always be there waiting for players to join. + -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. + -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. + + self:UnAssignFromGroups() + self:StatePlanned() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) + + if self:IsTrace() then + MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() + end + + self:E( { Event, From, To } ) + self:SetState( self, "State", To ) + + if self.Scores[To] then + local Scoring = self:GetScoring() + if Scoring then + Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + +end + + +--- @param #TASK_BASE self +function TASK_BASE:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self +end + + +--- @param #TASK_BASE self +function TASK_BASE._Scheduler() + self:F2() + + return true +end + + + + diff --git a/Moose Development/Moose/TaskMenu.lua b/Moose Development/Moose/TaskMenu.lua new file mode 100644 index 000000000..75467a81a --- /dev/null +++ b/Moose Development/Moose/TaskMenu.lua @@ -0,0 +1,84 @@ +--- @module Task_Client_Menu + +--- TASK2_MENU_CLIENT class +-- @type TASK2_MENU_CLIENT +-- @field Unit#UNIT TaskUnit +-- @field Set#SET_UNIT TargetSet +-- @field Menu#MENU_CLIENT_COMMAND MenuTask +-- @extends Task2#TASK2 +TASK2_MENU_CLIENT = { + ClassName = "TASK2_MENU_CLIENT", + TargetSet = nil, +} + + +--- Creates a new MENU handling machine. +-- @param #TASK2_MENU_CLIENT self +-- @param Mission#MISSION Mission +-- @param Unit#UNIT TaskUnit +-- @param #string MenuText The text of the menu item. +-- @return #TASK2_MENU_CLIENT self +function TASK2_MENU_CLIENT:New( Mission, TaskUnit, MenuText ) + + -- Inherits from BASE + local self = BASE:Inherit( self, TASK2:New( Mission, TaskUnit ) ) -- #TASK2_MENU_CLIENT + + self.MenuText = MenuText + + self.Fsm = STATEMACHINE_TASK:New( self, { + initial = 'Unassigned', + events = { + { name = 'Menu', from = 'Unassigned', to = 'AwaitingMenu' }, + { name = 'Assign', from = 'AwaitingMenu', to = 'Assigned' }, + }, + callbacks = { + onMenu = self.OnMenu, + onAssign = self.OnAssign, + }, + endstates = { + 'Assigned' + }, + } ) + + return self +end + +--- Task Events + +--- StateMachine callback function for a TASK2 +-- @param #TASK2_MENU_CLIENT self +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK2_MENU_CLIENT:OnMenu( Fsm, Event, From, To ) + self:E( { Event, From, To, self.TaskUnit.UnitName} ) + + self.TaskUnit:Message( "Press F10 for task menu", 15 ) + self.Menu = MENU_CLIENT:New( self.TaskUnit, self.Mission:GetName(), nil ) + self.MenuTask = MENU_CLIENT_COMMAND:New( self.TaskUnit, self.MenuText, self.Menu, self.MenuAssign, self ) +end + +--- Menu function. +-- @param #TASK2_MENU_CLIENT self +function TASK2_MENU_CLIENT:MenuAssign() + self:E( ) + + self.TaskUnit:Message( "Menu Assign", 15 ) + + self:NextEvent( self.Fsm.Assign ) +end + +--- StateMachine callback function for a TASK2 +-- @param #TASK2_MENU_CLIENT self +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK2_MENU_CLIENT:OnAssign( Fsm, Event, From, To ) + self:E( { Event, From, To, self.TaskUnit.UnitName} ) + + self.TaskUnit:Message( "Assign Task", 15 ) + self.MenuTask:Remove() +end + diff --git a/Moose Development/Moose/Task_BAI.lua b/Moose Development/Moose/Task_BAI.lua new file mode 100644 index 000000000..213f60518 --- /dev/null +++ b/Moose Development/Moose/Task_BAI.lua @@ -0,0 +1,145 @@ +--- This module contains the TASK_BAI classes. +-- +-- 1) @{#TASK_BAI} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_BAI} class defines a new BAI task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_BAI is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_BAI + + +do -- TASK_BAI + + --- The TASK_BAI class + -- @type TASK_BAI + -- @extends Task#TASK_BASE + TASK_BAI = { + ClassName = "TASK_BAI", + } + + --- Instantiates a new TASK_BAI. + -- @param #TASK_BAI self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_BAI self + function TASK_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "BAI", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_BAI. + -- @param #TASK_BAI self + -- @return #nil + function TASK_BAI:CleanUp() + + self:GetParent( self ):CleanUp() + + return nil + end + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_BAI self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_BAI self + function TASK_BAI:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "BAI", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a ground unit", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_BAI self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_BAI:OnNext( Fsm, Event, From, To, Event ) + + self:SetState( self, "State", To ) + + end + + --- @param #TASK_BAI self + function TASK_BAI:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + + --- @param #TASK_BAI self + function TASK_BAI:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_BAI self + function TASK_BAI._Scheduler() + self:F2() + + return true + end + +end + + + diff --git a/Moose Development/Moose/Task_CAS.lua b/Moose Development/Moose/Task_CAS.lua new file mode 100644 index 000000000..95a9341ab --- /dev/null +++ b/Moose Development/Moose/Task_CAS.lua @@ -0,0 +1,145 @@ +--- This module contains the TASK_CAS classes. +-- +-- 1) @{#TASK_CAS} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_CAS} class defines a new CAS task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_CAS is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_CAS + + +do -- TASK_CAS + + --- The TASK_CAS class + -- @type TASK_CAS + -- @extends Task#TASK_BASE + TASK_CAS = { + ClassName = "TASK_CAS", + } + + --- Instantiates a new TASK_CAS. + -- @param #TASK_CAS self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_CAS self + function TASK_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "CAS", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_CAS. + -- @param #TASK_CAS self + -- @return #nil + function TASK_CAS:CleanUp() + + self:GetParent( self ):CleanUp() + + return nil + end + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_CAS self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_CAS self + function TASK_CAS:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "CAS", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a ground unit", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_CAS self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_CAS:OnNext( Fsm, Event, From, To, Event ) + + self:SetState( self, "State", To ) + + end + + --- @param #TASK_CAS self + function TASK_CAS:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + + --- @param #TASK_CAS self + function TASK_CAS:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_CAS self + function TASK_CAS._Scheduler() + self:F2() + + return true + end + +end + + + diff --git a/Moose Development/Moose/Task_SEAD.lua b/Moose Development/Moose/Task_SEAD.lua new file mode 100644 index 000000000..583901eac --- /dev/null +++ b/Moose Development/Moose/Task_SEAD.lua @@ -0,0 +1,145 @@ +--- This module contains the TASK_SEAD classes. +-- +-- 1) @{#TASK_SEAD} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_SEAD} class defines a new SEAD task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_SEAD is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_SEAD + + +do -- TASK_SEAD + + --- The TASK_SEAD class + -- @type TASK_SEAD + -- @field Set#SET_UNIT TargetSetUnit + -- @extends Task#TASK_BASE + TASK_SEAD = { + ClassName = "TASK_SEAD", + } + + --- Instantiates a new TASK_SEAD. + -- @param #TASK_SEAD self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_SEAD self + function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "SEAD", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_SEAD. + -- @param #TASK_SEAD self + -- @return #nil + function TASK_SEAD:CleanUp() + + self:GetParent(self):CleanUp() + + return nil + end + + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_SEAD self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_SEAD self + function TASK_SEAD:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "SEAD", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a radar", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a radar", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a radar", -100 ) + self:AddScore( "Success", "Destroyed all target radars", 250 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_SEAD self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_SEAD:OnNext( Fsm, Event, From, To ) + + self:SetState( self, "State", To ) + + end + + + --- @param #TASK_SEAD self + function TASK_SEAD:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + --- @param #TASK_SEAD self + function TASK_SEAD:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_SEAD self + function TASK_SEAD._Scheduler() + self:F2() + + return true + end + +end diff --git a/Moose Development/Moose/Unit.lua b/Moose Development/Moose/Unit.lua index 32b4d7bb2..223e8476a 100644 --- a/Moose Development/Moose/Unit.lua +++ b/Moose Development/Moose/Unit.lua @@ -49,7 +49,7 @@ -- ----------------------------- -- The UNIT class provides methods to obtain the current point or position of the DCS Unit. -- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetPointVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including oriëntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. +-- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. -- -- 1.5) Test if alive -- ------------------ @@ -109,6 +109,14 @@ UNIT = { -- @field Orange -- @field Blue +--- Unit.SensorType +-- @type Unit.SensorType +-- @field OPTIC +-- @field RADAR +-- @field IRST +-- @field RWR + + -- Registration. --- Create a new UNIT from DCSUnit. @@ -179,6 +187,24 @@ function UNIT:IsActive() return nil end +--- Destroys the @{Unit}. +-- @param Unit#UNIT self +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:Destroy() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:destroy() + end + + return nil +end + + + --- Returns the Unit's callsign - the localized string. -- @param Unit#UNIT self -- @return #string The Callsign of the Unit. @@ -316,6 +342,46 @@ end -- Need to add here a function per sensortype -- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) +--- Returns if the unit has sensors of a certain type. +-- @param Unit#UNIT self +-- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSensors( ... ) + self:F2( arg ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) + return HasSensors + end + + return nil +end + +--- Returns if the unit is SEADable. +-- @param Unit#UNIT self +-- @return #boolean returns true if the unit is SEADable. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSEAD() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSEADAttributes = DCSUnit:getDesc().attributes + + local HasSEAD = false + if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or + UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then + HasSEAD = true + end + return HasSEAD + end + + return nil +end + --- Returns two values: -- -- * First value indicates if at least one of the unit's radar(s) is on. @@ -388,7 +454,47 @@ function UNIT:GetLife0() return nil end +--- Returns the Unit's A2G threat level on a scale from 1 to 10 ... +-- The following threat levels are foreseen: +-- +-- * Threat level 0: Unit is unarmed. +-- * Threat level 1: Unit is infantry. +-- * Threat level 2: Unit is an infantry vehicle. +-- * Threat level 3: Unit is ground artillery. +-- * Threat level 4: Unit is a tank. +-- * Threat level 5: Unit is a modern tank or ifv with ATGM. +-- * Threat level 6: Unit is a AAA. +-- * Threat level 7: Unit is a SAM or manpad, IR guided. +-- * Threat level 8: Unit is a Short Range SAM, radar guided. +-- * Threat level 9: Unit is a Medium Range SAM, radar guided. +-- * Threat level 10: Unit is a Long Range SAM, radar guided. +function UNIT:GetThreatLevel() + local Attributes = self:GetDesc().attributes + local ThreatLevel = 0 + + self:T2( Attributes ) + + if Attributes["LR SAM"] then ThreatLevel = 10 + elseif Attributes["MR SAM"] then ThreatLevel = 9 + elseif Attributes["SR SAM"] and + not Attributes["IR Guided SAM"] then ThreatLevel = 8 + elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and + Attributes["IR Guided SAM"] then ThreatLevel = 7 + elseif Attributes["AAA"] then ThreatLevel = 6 + elseif Attributes["Modern Tanks"] then ThreatLevel = 5 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + Attributes["ATGM"] then ThreatLevel = 4 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + not Attributes["ATGM"] then ThreatLevel = 3 + elseif Attributes["Old Tanks"] or Attributes["APC"] then ThreatLevel = 2 + elseif Attributes["Infantry"] then ThreatLevel = 1 + end + + self:T2( ThreatLevel ) + return ThreatLevel + +end -- Is functions @@ -405,9 +511,9 @@ function UNIT:IsInZone( Zone ) self:T( { IsInZone } ) return IsInZone - else - return false end + + return false end --- Returns true if the unit is not within a @{Zone}. @@ -489,14 +595,22 @@ end -- @param #UNIT self function UNIT:FlareRed() self:F2() - trigger.action.signalFlare( self:GetPointVec3(), trigger.flareColor.Red, 0 ) + local Vec3 = self:GetPointVec3() + if Vec3 then + trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) + end end --- Smoke the UNIT. -- @param #UNIT self -function UNIT:Smoke( SmokeColor ) +function UNIT:Smoke( SmokeColor, Range ) self:F2() - trigger.action.smoke( self:GetPointVec3(), SmokeColor ) + if Range then + trigger.action.smoke( self:GetRandomPointVec3( Range ), SmokeColor ) + else + trigger.action.smoke( self:GetPointVec3(), SmokeColor ) + end + end --- Smoke the UNIT Green. @@ -543,12 +657,83 @@ end function UNIT:IsAir() self:F2() - local UnitDescriptor = self.DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + local DCSUnit = self:GetDCSObject() - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) - - self:T3( IsAirResult ) - return IsAirResult + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + + local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) + + self:T3( IsAirResult ) + return IsAirResult + end + + return nil +end + +--- Returns if the unit is of an ground category. +-- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ground category evaluation result. +function UNIT:IsGround() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) + + local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) + + self:T3( IsGroundResult ) + return IsGroundResult + end + + return nil +end + +--- Returns if the unit is a friendly unit. +-- @param #UNIT self +-- @return #boolean IsFriendly evaluation result. +function UNIT:IsFriendly( FriendlyCoalition ) + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCoalition = DCSUnit:getCoalition() + self:T3( { UnitCoalition, FriendlyCoalition } ) + + local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) + + self:E( IsFriendlyResult ) + return IsFriendlyResult + end + + return nil +end + +--- Returns if the unit is of a ship category. +-- If the unit is a ship, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ship category evaluation result. +function UNIT:IsShip() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) + + local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) + + self:T3( IsShipResult ) + return IsShipResult + end + + return nil end diff --git a/Moose Development/Moose/Utils.lua b/Moose Development/Moose/Utils.lua new file mode 100644 index 000000000..512bc2de0 --- /dev/null +++ b/Moose Development/Moose/Utils.lua @@ -0,0 +1,282 @@ + +--- @type SMOKECOLOR +-- @field Green +-- @field Red +-- @field White +-- @field Orange +-- @field Blue + +SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR + +--- @type FLARECOLOR +-- @field Green +-- @field Red +-- @field White +-- @field Yellow + +FLARECOLOR = trigger.flareColor -- #FLARECOLOR + +--- Utilities static class. +-- @type UTILS +UTILS = {} + + +--from http://lua-users.org/wiki/CopyTable +UTILS.DeepCopy = function(object) + local lookup_table = {} + local function _copy(object) + if type(object) ~= "table" then + return object + elseif lookup_table[object] then + return lookup_table[object] + end + local new_table = {} + lookup_table[object] = new_table + for index, value in pairs(object) do + new_table[_copy(index)] = _copy(value) + end + return setmetatable(new_table, getmetatable(object)) + end + local objectreturn = _copy(object) + return objectreturn +end + + +-- porting in Slmod's serialize_slmod2 +UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function + + lookup_table = {} + + local function _Serialize( tbl ) + + if type(tbl) == 'table' then --function only works for tables! + + if lookup_table[tbl] then + return lookup_table[object] + end + + local tbl_str = {} + + lookup_table[tbl] = tbl_str + + tbl_str[#tbl_str + 1] = '{' + + for ind,val in pairs(tbl) do -- serialize its fields + local ind_str = {} + if type(ind) == "number" then + ind_str[#ind_str + 1] = '[' + ind_str[#ind_str + 1] = tostring(ind) + ind_str[#ind_str + 1] = ']=' + else --must be a string + ind_str[#ind_str + 1] = '[' + ind_str[#ind_str + 1] = routines.utils.basicSerialize(ind) + ind_str[#ind_str + 1] = ']=' + end + + local val_str = {} + if ((type(val) == 'number') or (type(val) == 'boolean')) then + val_str[#val_str + 1] = tostring(val) + val_str[#val_str + 1] = ',' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'string' then + val_str[#val_str + 1] = routines.utils.basicSerialize(val) + val_str[#val_str + 1] = ',' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'nil' then -- won't ever happen, right? + val_str[#val_str + 1] = 'nil,' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'table' then + if ind == "__index" then + -- tbl_str[#tbl_str + 1] = "__index" + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + else + + val_str[#val_str + 1] = _Serialize(val) + val_str[#val_str + 1] = ',' --I think this is right, I just added it + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + end + elseif type(val) == 'function' then + -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + else +-- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) +-- env.info( debug.traceback() ) + end + + end + tbl_str[#tbl_str + 1] = '}' + return table.concat(tbl_str) + else + return tostring(tbl) + end + end + + local objectreturn = _Serialize(tbl) + return objectreturn +end + +--porting in Slmod's "safestring" basic serialize +UTILS.BasicSerialize = function(s) + if s == nil then + return "\"\"" + else + if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'table') or (type(s) == 'userdata') ) then + return tostring(s) + elseif type(s) == 'string' then + s = string.format('%q', s) + return s + end + end +end + + +UTILS.ToDegree = function(angle) + return angle*180/math.pi +end + +UTILS.ToRadian = function(angle) + return angle*math.pi/180 +end + +UTILS.MetersToNM = function(meters) + return meters/1852 +end + +UTILS.MetersToFeet = function(meters) + return meters/0.3048 +end + +UTILS.NMToMeters = function(NM) + return NM*1852 +end + +UTILS.FeetToMeters = function(feet) + return feet*0.3048 +end + +UTILS.MpsToKnots = function(mps) + return mps*3600/1852 +end + +UTILS.MpsToKmph = function(mps) + return mps*3.6 +end + +UTILS.KnotsToMps = function(knots) + return knots*1852/3600 +end + +UTILS.KmphToMps = function(kmph) + return kmph/3.6 +end + +--[[acc: +in DM: decimal point of minutes. +In DMS: decimal point of seconds. +position after the decimal of the least significant digit: +So: +42.32 - acc of 2. +]] +UTILS.tostringLL = function( lat, lon, acc, DMS) + + local latHemi, lonHemi + if lat > 0 then + latHemi = 'N' + else + latHemi = 'S' + end + + if lon > 0 then + lonHemi = 'E' + else + lonHemi = 'W' + end + + lat = math.abs(lat) + lon = math.abs(lon) + + local latDeg = math.floor(lat) + local latMin = (lat - latDeg)*60 + + local lonDeg = math.floor(lon) + local lonMin = (lon - lonDeg)*60 + + if DMS then -- degrees, minutes, and seconds. + local oldLatMin = latMin + latMin = math.floor(latMin) + local latSec = UTILS.Round((oldLatMin - latMin)*60, acc) + + local oldLonMin = lonMin + lonMin = math.floor(lonMin) + local lonSec = UTILS.Round((oldLonMin - lonMin)*60, acc) + + if latSec == 60 then + latSec = 0 + latMin = latMin + 1 + end + + if lonSec == 60 then + lonSec = 0 + lonMin = lonMin + 1 + end + + local secFrmtStr -- create the formatting string for the seconds place + if acc <= 0 then -- no decimal place. + secFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end + + return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' + .. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi + + else -- degrees, decimal minutes. + latMin = UTILS.Round(latMin, acc) + lonMin = UTILS.Round(lonMin, acc) + + if latMin == 60 then + latMin = 0 + latDeg = latDeg + 1 + end + + if lonMin == 60 then + lonMin = 0 + lonDeg = lonDeg + 1 + end + + local minFrmtStr -- create the formatting string for the minutes place + if acc <= 0 then -- no decimal place. + minFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end + + return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' + .. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi + + end +end + + +--- From http://lua-users.org/wiki/SimpleRound +-- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place +function UTILS.Round( num, idp ) + local mult = 10 ^ ( idp or 0 ) + return math.floor( num * mult + 0.5 ) / mult +end + +-- porting in Slmod's dostring +function UTILS.DoString( s ) + local f, err = loadstring( s ) + if f then + return true, f() + else + return false, err + end +end diff --git a/Moose Development/Moose/Zone.lua b/Moose Development/Moose/Zone.lua index 893abd5c2..c1cda5fc8 100644 --- a/Moose Development/Moose/Zone.lua +++ b/Moose Development/Moose/Zone.lua @@ -101,10 +101,10 @@ end --- Returns if a location is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_BASE:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_BASE:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) return false end @@ -121,18 +121,27 @@ function ZONE_BASE:IsPointVec3InZone( PointVec3 ) return InZone end +--- Returns the Vec2 coordinate of the zone. +-- @param #ZONE_BASE self +-- @return #nil. +function ZONE_BASE:GetVec2() + self:F2( self.ZoneName ) + + return nil +end --- Define a random @{DCSTypes#Vec2} within the zone. -- @param #ZONE_BASE self --- @return DCSTypes#Vec2 The Vec2 coordinates. +-- @return #nil The Vec2 coordinates. function ZONE_BASE:GetRandomVec2() - return { x = 0, y = 0 } + return nil end --- Get the bounding square the zone. -- @param #ZONE_BASE self --- @return #ZONE_BASE.BoundingSquare The bounding square. +-- @return #nil The bounding square. function ZONE_BASE:GetBoundingSquare() - return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } + --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } + return nil end @@ -147,7 +156,7 @@ end --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS --- @field DCSTypes#Vec2 PointVec2 The current location of the zone. +-- @field DCSTypes#Vec2 Vec2 The current location of the zone. -- @field DCSTypes#Distance Radius The radius of the zone. -- @extends Zone#ZONE_BASE ZONE_RADIUS = { @@ -157,15 +166,15 @@ ZONE_RADIUS = { --- Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self -- @param #string ZoneName Name of the zone. --- @param DCSTypes#Vec2 PointVec2 The location of the zone. +-- @param DCSTypes#Vec2 Vec2 The location of the zone. -- @param DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:New( ZoneName, PointVec2, Radius ) +function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, PointVec2, Radius } ) + self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius - self.PointVec2 = PointVec2 + self.Vec2 = Vec2 return self end @@ -179,7 +188,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) self:F2( SmokeColor ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() Points = Points and Points or 360 @@ -188,8 +197,8 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 - Point.x = PointVec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = PointVec2.y + math.sin( Radial ) * self:GetRadius() + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() + Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() POINT_VEC2:New( Point.x, Point.y ):Smoke( SmokeColor ) end @@ -207,7 +216,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) self:F2( { FlareColor, Azimuth } ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() Points = Points and Points or 360 @@ -216,8 +225,8 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 - Point.x = PointVec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = PointVec2.y + math.sin( Radial ) * self:GetRadius() + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() + Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() POINT_VEC2:New( Point.x, Point.y ):Flare( FlareColor, Azimuth ) end @@ -251,26 +260,26 @@ end --- Returns the location of the zone. -- @param #ZONE_RADIUS self -- @return DCSTypes#Vec2 The location of the zone. -function ZONE_RADIUS:GetPointVec2() +function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) - self:T2( { self.PointVec2 } ) + self:T2( { self.Vec2 } ) - return self.PointVec2 + return self.Vec2 end --- Sets the location of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 PointVec2 The new location of the zone. +-- @param DCSTypes#Vec2 Vec2 The new location of the zone. -- @return DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetPointVec2( PointVec2 ) +function ZONE_RADIUS:SetPointVec2( Vec2 ) self:F2( self.ZoneName ) - self.PointVec2 = PointVec2 + self.Vec2 = Vec2 - self:T2( { self.PointVec2 } ) + self:T2( { self.Vec2 } ) - return self.PointVec2 + return self.Vec2 end --- Returns the point of the zone. @@ -280,9 +289,9 @@ end function ZONE_RADIUS:GetPointVec3( Height ) self:F2( self.ZoneName ) - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() - local PointVec3 = { x = PointVec2.x, y = land.getHeight( self:GetPointVec2() ) + Height, z = PointVec2.y } + local PointVec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { PointVec3 } ) @@ -292,15 +301,17 @@ end --- Returns if a location is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_RADIUS:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_RADIUS:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) - local ZonePointVec2 = self:GetPointVec2() - - if (( PointVec2.x - ZonePointVec2.x )^2 + ( PointVec2.y - ZonePointVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then - return true + local ZoneVec2 = self:GetVec2() + + if ZoneVec2 then + if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then + return true + end end return false @@ -310,10 +321,10 @@ end -- @param #ZONE_RADIUS self -- @param DCSTypes#Vec3 PointVec3 The point to test. -- @return #boolean true if the point is within the zone. -function ZONE_RADIUS:IsPointVec3InZone( PointVec3 ) - self:F2( PointVec3 ) +function ZONE_RADIUS:IsPointVec3InZone( Vec3 ) + self:F2( Vec3 ) - local InZone = self:IsPointVec2InZone( { x = PointVec3.x, y = PointVec3.z } ) + local InZone = self:IsPointVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone end @@ -325,11 +336,11 @@ function ZONE_RADIUS:GetRandomVec2() self:F( self.ZoneName ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() local angle = math.random() * math.pi*2; - Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = PointVec2.y + math.sin( angle ) * math.random() * self:GetRadius(); + Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); + Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); self:T( { Point } ) @@ -383,10 +394,11 @@ ZONE_UNIT = { -- @param DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetPointVec2(), Radius ) ) - self:F( { ZoneName, ZoneUNIT:GetPointVec2(), Radius } ) + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) + self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT + self.LastVec2 = ZoneUNIT:GetVec2() return self end @@ -395,14 +407,20 @@ end --- Returns the current location of the @{Unit#UNIT}. -- @param #ZONE_UNIT self -- @return DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. -function ZONE_UNIT:GetPointVec2() +function ZONE_UNIT:GetVec2() self:F( self.ZoneName ) - local ZonePointVec2 = self.ZoneUNIT:GetPointVec2() + local ZoneVec2 = self.ZoneUNIT:GetVec2() + if ZoneVec2 then + self.LastVec2 = ZoneVec2 + return ZoneVec2 + else + return self.LastVec2 + end - self:T( { ZonePointVec2 } ) - - return ZonePointVec2 + self:T( { ZoneVec2 } ) + + return nil end --- Returns a random location within the zone. @@ -413,6 +431,9 @@ function ZONE_UNIT:GetRandomVec2() local Point = {} local PointVec2 = self.ZoneUNIT:GetPointVec2() + if not PointVec2 then + PointVec2 = self.LastVec2 + end local angle = math.random() * math.pi*2; Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); @@ -423,6 +444,24 @@ function ZONE_UNIT:GetRandomVec2() return Point end +--- Returns the point of the zone. +-- @param #ZONE_RADIUS self +-- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return DCSTypes#Vec3 The point of the zone. +function ZONE_UNIT:GetPointVec3( Height ) + self:F2( self.ZoneName ) + + Height = Height or 0 + + local Vec2 = self:GetVec2() + + local PointVec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } + + self:T2( { PointVec3 } ) + + return PointVec3 +end + --- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. -- @type ZONE_GROUP -- @field Group#GROUP ZoneGROUP @@ -567,10 +606,10 @@ end --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_POLYGON_BASE:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) local Next local Prev @@ -581,8 +620,8 @@ function ZONE_POLYGON_BASE:IsPointVec2InZone( PointVec2 ) while Next <= #self.Polygon do self:T( { Next, Prev, self.Polygon[Next], self.Polygon[Prev] } ) - if ( ( ( self.Polygon[Next].y > PointVec2.y ) ~= ( self.Polygon[Prev].y > PointVec2.y ) ) and - ( PointVec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( PointVec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) + if ( ( ( self.Polygon[Next].y > Vec2.y ) ~= ( self.Polygon[Prev].y > Vec2.y ) ) and + ( Vec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( Vec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) ) then InPolygon = not InPolygon end diff --git a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua index a28bfb295..c88217738 100644 --- a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua +++ b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20160712_0855' ) +env.info( 'Moose Generation Timestamp: 20160719_1818' ) local base = _G Include = {} @@ -254,22 +254,6 @@ end --- From http://lua-users.org/wiki/SimpleRound --- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place -routines.utils.round = function(num, idp) - local mult = 10^(idp or 0) - return math.floor(num * mult + 0.5) / mult -end - --- porting in Slmod's dostring -routines.utils.dostring = function(s) - local f, err = loadstring(s) - if f then - return true, f() - else - return false, err - end -end --3D Vector manipulation @@ -2511,6 +2495,288 @@ end env.info(( 'Init: Scripts Loaded v1.1' )) + +--- @type SMOKECOLOR +-- @field Green +-- @field Red +-- @field White +-- @field Orange +-- @field Blue + +SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR + +--- @type FLARECOLOR +-- @field Green +-- @field Red +-- @field White +-- @field Yellow + +FLARECOLOR = trigger.flareColor -- #FLARECOLOR + +--- Utilities static class. +-- @type UTILS +UTILS = {} + + +--from http://lua-users.org/wiki/CopyTable +UTILS.DeepCopy = function(object) + local lookup_table = {} + local function _copy(object) + if type(object) ~= "table" then + return object + elseif lookup_table[object] then + return lookup_table[object] + end + local new_table = {} + lookup_table[object] = new_table + for index, value in pairs(object) do + new_table[_copy(index)] = _copy(value) + end + return setmetatable(new_table, getmetatable(object)) + end + local objectreturn = _copy(object) + return objectreturn +end + + +-- porting in Slmod's serialize_slmod2 +UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function + + lookup_table = {} + + local function _Serialize( tbl ) + + if type(tbl) == 'table' then --function only works for tables! + + if lookup_table[tbl] then + return lookup_table[object] + end + + local tbl_str = {} + + lookup_table[tbl] = tbl_str + + tbl_str[#tbl_str + 1] = '{' + + for ind,val in pairs(tbl) do -- serialize its fields + local ind_str = {} + if type(ind) == "number" then + ind_str[#ind_str + 1] = '[' + ind_str[#ind_str + 1] = tostring(ind) + ind_str[#ind_str + 1] = ']=' + else --must be a string + ind_str[#ind_str + 1] = '[' + ind_str[#ind_str + 1] = routines.utils.basicSerialize(ind) + ind_str[#ind_str + 1] = ']=' + end + + local val_str = {} + if ((type(val) == 'number') or (type(val) == 'boolean')) then + val_str[#val_str + 1] = tostring(val) + val_str[#val_str + 1] = ',' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'string' then + val_str[#val_str + 1] = routines.utils.basicSerialize(val) + val_str[#val_str + 1] = ',' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'nil' then -- won't ever happen, right? + val_str[#val_str + 1] = 'nil,' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'table' then + if ind == "__index" then + -- tbl_str[#tbl_str + 1] = "__index" + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + else + + val_str[#val_str + 1] = _Serialize(val) + val_str[#val_str + 1] = ',' --I think this is right, I just added it + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + end + elseif type(val) == 'function' then + -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + else +-- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) +-- env.info( debug.traceback() ) + end + + end + tbl_str[#tbl_str + 1] = '}' + return table.concat(tbl_str) + else + return tostring(tbl) + end + end + + local objectreturn = _Serialize(tbl) + return objectreturn +end + +--porting in Slmod's "safestring" basic serialize +UTILS.BasicSerialize = function(s) + if s == nil then + return "\"\"" + else + if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'table') or (type(s) == 'userdata') ) then + return tostring(s) + elseif type(s) == 'string' then + s = string.format('%q', s) + return s + end + end +end + + +UTILS.ToDegree = function(angle) + return angle*180/math.pi +end + +UTILS.ToRadian = function(angle) + return angle*math.pi/180 +end + +UTILS.MetersToNM = function(meters) + return meters/1852 +end + +UTILS.MetersToFeet = function(meters) + return meters/0.3048 +end + +UTILS.NMToMeters = function(NM) + return NM*1852 +end + +UTILS.FeetToMeters = function(feet) + return feet*0.3048 +end + +UTILS.MpsToKnots = function(mps) + return mps*3600/1852 +end + +UTILS.MpsToKmph = function(mps) + return mps*3.6 +end + +UTILS.KnotsToMps = function(knots) + return knots*1852/3600 +end + +UTILS.KmphToMps = function(kmph) + return kmph/3.6 +end + +--[[acc: +in DM: decimal point of minutes. +In DMS: decimal point of seconds. +position after the decimal of the least significant digit: +So: +42.32 - acc of 2. +]] +UTILS.tostringLL = function( lat, lon, acc, DMS) + + local latHemi, lonHemi + if lat > 0 then + latHemi = 'N' + else + latHemi = 'S' + end + + if lon > 0 then + lonHemi = 'E' + else + lonHemi = 'W' + end + + lat = math.abs(lat) + lon = math.abs(lon) + + local latDeg = math.floor(lat) + local latMin = (lat - latDeg)*60 + + local lonDeg = math.floor(lon) + local lonMin = (lon - lonDeg)*60 + + if DMS then -- degrees, minutes, and seconds. + local oldLatMin = latMin + latMin = math.floor(latMin) + local latSec = UTILS.Round((oldLatMin - latMin)*60, acc) + + local oldLonMin = lonMin + lonMin = math.floor(lonMin) + local lonSec = UTILS.Round((oldLonMin - lonMin)*60, acc) + + if latSec == 60 then + latSec = 0 + latMin = latMin + 1 + end + + if lonSec == 60 then + lonSec = 0 + lonMin = lonMin + 1 + end + + local secFrmtStr -- create the formatting string for the seconds place + if acc <= 0 then -- no decimal place. + secFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end + + return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' + .. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi + + else -- degrees, decimal minutes. + latMin = UTILS.Round(latMin, acc) + lonMin = UTILS.Round(lonMin, acc) + + if latMin == 60 then + latMin = 0 + latDeg = latDeg + 1 + end + + if lonMin == 60 then + lonMin = 0 + lonDeg = lonDeg + 1 + end + + local minFrmtStr -- create the formatting string for the minutes place + if acc <= 0 then -- no decimal place. + minFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end + + return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' + .. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi + + end +end + + +--- From http://lua-users.org/wiki/SimpleRound +-- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place +function UTILS.Round( num, idp ) + local mult = 10 ^ ( idp or 0 ) + return math.floor( num * mult + 0.5 ) / mult +end + +-- porting in Slmod's dostring +function UTILS.DoString( s ) + local f, err = loadstring( s ) + if f then + return true, f() + else + return false, err + end +end --- This module contains the BASE class. -- -- 1) @{#BASE} class @@ -2568,8 +2834,9 @@ env.info(( 'Init: Scripts Loaded v1.1' )) -- -- ==== -- +-- ### Author: FlightControl +-- -- @module Base --- @author FlightControl @@ -2628,7 +2895,6 @@ function BASE:New() self.__index = self _ClassID = _ClassID + 1 self.ClassID = _ClassID - self.ClassNameAndID = string.format( '%s#%09d', self.ClassName, self.ClassID ) return self end @@ -2645,8 +2911,7 @@ function BASE:Inherit( Child, Parent ) setmetatable( Child, Parent ) Child.__index = Child end - --Child.ClassName = Child.ClassName .. '.' .. Child.ClassID - self:T( 'Inherited from ' .. Parent.ClassName ) + --self:T( 'Inherited from ' .. Parent.ClassName ) return Child end @@ -2654,7 +2919,7 @@ end -- @param #BASE self -- @param #BASE Child is the Child class from which the Parent class needs to be retrieved. -- @return #BASE -function BASE:Inherited( Child ) +function BASE:GetParent( Child ) local Parent = getmetatable( Child ) -- env.info('Inherited class of ' .. Child.ClassName .. ' is ' .. Parent.ClassName ) return Parent @@ -2665,7 +2930,7 @@ end -- @param #BASE self -- @return #string The ClassName + ClassID of the class instance. function BASE:GetClassNameAndID() - return self.ClassNameAndID + return string.format( '%s#%09d', self.ClassName, self.ClassID ) end --- Get the ClassName of the class instance. @@ -2852,11 +3117,9 @@ function BASE:SetState( Object, StateName, State ) local ClassNameAndID = Object:GetClassNameAndID() - if not self.States[ClassNameAndID] then - self.States[ClassNameAndID] = {} - end + self.States[ClassNameAndID] = self.States[ClassNameAndID] or {} self.States[ClassNameAndID][StateName] = State - self:F2( { ClassNameAndID, StateName, State } ) + self:T2( { ClassNameAndID, StateName, State } ) return self.States[ClassNameAndID][StateName] end @@ -2867,7 +3130,7 @@ function BASE:GetState( Object, StateName ) if self.States[ClassNameAndID] then local State = self.States[ClassNameAndID][StateName] - self:F2( { ClassNameAndID, StateName, State } ) + self:T2( { ClassNameAndID, StateName, State } ) return State end @@ -2892,7 +3155,7 @@ end -- When Moose is loaded statically, (as one file), tracing is switched off by default. -- So tracing must be switched on manually in your mission if you are using Moose statically. -- When moose is loading dynamically (for moose class development), tracing is switched on by default. --- @param BASE self +-- @param #BASE self -- @param #boolean TraceOnOff Switch the tracing on or off. -- @usage -- -- Switch the tracing On @@ -2904,6 +3167,19 @@ function BASE:TraceOnOff( TraceOnOff ) _TraceOnOff = TraceOnOff end + +--- Enquires if tracing is on (for the class). +-- @param #BASE self +-- @return #boolean +function BASE:IsTrace() + + if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + return true + else + return false + end +end + --- Set trace level -- @param #BASE self -- @param #number Level @@ -3490,7 +3766,7 @@ end -- @param Positionable#POSITIONABLE self -- @return DCSTypes#Vec2 The 2D point vector of the DCS Positionable. -- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetPointVec2() +function POSITIONABLE:GetVec2() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() @@ -3510,6 +3786,30 @@ function POSITIONABLE:GetPointVec2() end +--- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the DCS Positionable within the mission. +-- @param Positionable#POSITIONABLE self +-- @return DCSTypes#Vec3 The 3D point vector of the DCS Positionable. +-- @return #nil The DCS Positionable is not existing or alive. +function POSITIONABLE:GetRandomPointVec3( Radius ) + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableRandomPointVec3 = {} + local angle = math.random() * math.pi*2; + PositionableRandomPointVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; + PositionableRandomPointVec3.y = PositionablePointVec3.y + PositionableRandomPointVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; + + self:T3( PositionableRandomPointVec3 ) + return PositionableRandomPointVec3 + end + + return nil +end + --- Returns the @{DCSTypes#Vec3} vector indicating the point in 3D of the DCS Positionable within the mission. -- @param Positionable#POSITIONABLE self -- @return DCSTypes#Vec3 The 3D point vector of the DCS Positionable. @@ -3556,7 +3856,7 @@ function POSITIONABLE:IsAboveRunway() if DCSPositionable then - local PointVec2 = self:GetPointVec2() + local PointVec2 = self:GetVec2() local SurfaceType = land.getSurfaceType( PointVec2 ) local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY @@ -3628,6 +3928,27 @@ function POSITIONABLE:GetVelocity() return nil end +--- Returns the @{Unit#UNIT} velocity in km/h. +-- @param Positionable#POSITIONABLE self +-- @return #number The velocity in km/h +-- @return #nil The DCS Positionable is not existing or alive. +function POSITIONABLE:GetVelocityKMH() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local VelocityVec3 = self:GetVelocity() + local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec + local Velocity = Velocity * 3.6 -- now it is in km/h. + self:T3( Velocity ) + return Velocity + end + + return nil +end + + --- This module contains the CONTROLLABLE class. @@ -4232,7 +4553,7 @@ function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) local DCSControllable = self:GetDCSObject() if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) end @@ -4409,7 +4730,7 @@ function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) if RandomPoint then Point = Zone:GetRandomVec2() else - Point = Zone:GetPointVec2() + Point = Zone:GetVec2() end local DCSTask = self:TaskLandAtVec2( Point, Duration ) @@ -5009,7 +5330,7 @@ end function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) self:F2( { Point, Speed } ) - local ControllablePoint = self:GetUnit( 1 ):GetPointVec2() + local ControllablePoint = self:GetUnit( 1 ):GetVec2() local PointFrom = {} PointFrom.x = ControllablePoint.x @@ -5148,7 +5469,7 @@ function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() local PointFrom = {} PointFrom.x = ControllablePoint.x @@ -5164,7 +5485,7 @@ function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) if Randomize then ZonePoint = Zone:GetRandomVec2() else - ZonePoint = Zone:GetPointVec2() + ZonePoint = Zone:GetVec2() end PointTo.x = ZonePoint.x @@ -5246,7 +5567,7 @@ function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() local ControllableVelocity = self:GetMaxVelocity() local PointFrom = {} @@ -5258,7 +5579,7 @@ function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) local PointTo = {} - local AirbasePoint = ReturnAirbase:GetPointVec2() + local AirbasePoint = ReturnAirbase:GetVec2() PointTo.x = AirbasePoint.x PointTo.y = AirbasePoint.y @@ -5926,6 +6247,7 @@ function SCHEDULER:Stop() self.Repeat = false if self.ScheduleID then + self:E( "Stop Schedule" ) timer.removeFunction( self.ScheduleID ) end self.ScheduleID = nil @@ -6093,6 +6415,18 @@ function EVENT:Init( EventID, EventClass ) return self.Events[EventID][EventClass] end +--- Removes an Events entry +-- @param #EVENT self +-- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param DCSWorld#world.event EventID +-- @return #EVENT.Events +function EVENT:Remove( EventSelf, EventID ) + self:F3( { EventSelf, _EVENTCODES[EventID] } ) + + local EventClass = EventSelf:GetClassNameAndID() + self.Events[EventID][EventClass] = nil +end + --- Create an OnDead event handler for a group -- @param #EVENT self @@ -6146,328 +6480,533 @@ function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, Event return self end +do -- OnBirth ---- Create an OnBirth event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + --- Create an OnBirth event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnBirth( EventFunction, EventSelf ) - self:F2() + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + + return self + end - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirth( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end - return self -end + --- Set a new listener for an S_EVENT_BIRTH event. + -- @param #EVENT self + -- @param #string EventDCSUnitName The id of the unit for the event to be handled. + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end ---- Set a new listener for an S_EVENT_BIRTH event. --- @param #EVENT self --- @param #string EventDCSUnitName The id of the unit for the event to be handled. --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) + --- Stop listening to S_EVENT_BIRTH event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirthRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + +end + +do -- OnCrash + + --- Create an OnCrash event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Create an OnCrash event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) - - return self -end - ---- Set a new listener for an S_EVENT_CRASH event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnCrash( EventFunction, EventSelf ) - self:F2() + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + return self + end - return self -end - ---- Set a new listener for an S_EVENT_CRASH event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) + --- Set a new listener for an S_EVENT_CRASH event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnCrash( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + + return self + end - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) + --- Set a new listener for an S_EVENT_CRASH event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) + return self + end + + --- Stop listening to S_EVENT_CRASH event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnCrashRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_CRASH ) + + return self + end - return self end ---- Set a new listener for an S_EVENT_DEAD event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnDead( EventFunction, EventSelf ) - self:F2() +do -- OnDead + + --- Create an OnDead event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + return self + end - return self -end - - ---- Set a new listener for an S_EVENT_DEAD event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + --- Set a new listener for an S_EVENT_DEAD event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnDead( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_PILOT_DEAD event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) - return self -end - ---- Set a new listener for an S_EVENT_LAND event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) - - return self -end - ---- Set a new listener for an S_EVENT_TAKEOFF event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + --- Set a new listener for an S_EVENT_DEAD event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self -end - ---- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_ENGINE_STARTUP event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + --- Stop listening to S_EVENT_DEAD event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnDeadRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self + end ---- Set a new listener for an S_EVENT_SHOT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnShot( EventFunction, EventSelf ) - self:F2() +do -- OnPilotDead - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + --- Set a new listener for an S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPilotDead( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_SHOT event for a unit. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + --- Set a new listener for an S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self -end - ---- Set a new listener for an S_EVENT_HIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnHit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) - return self + return self + end + + --- Stop listening to S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPilotDeadRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PILOT_DEAD ) + + return self + end + end ---- Set a new listener for an S_EVENT_HIT event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) +do -- OnLand + --- Create an OnLand event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + --- Set a new listener for an S_EVENT_LAND event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) + + return self + end + + --- Stop listening to S_EVENT_LAND event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnLandRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_LAND ) + + return self + end + + end +do -- OnTakeOff + --- Create an OnTakeOff event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) + + return self + end + + --- Set a new listener for an S_EVENT_TAKEOFF event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) + + return self + end + + --- Stop listening to S_EVENT_TAKEOFF event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnTakeOffRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_TAKEOFF ) + + return self + end + + +end + +do -- OnEngineShutDown + + --- Create an OnDead event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + + return self + end + + --- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self + end + + --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnEngineShutDownRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self + end + +end + +do -- OnEngineStartUp + + --- Set a new listener for an S_EVENT_ENGINE_STARTUP event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + + return self + end + + --- Stop listening to S_EVENT_ENGINE_STARTUP event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnEngineStartUpRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + + return self + end + +end + +do -- OnShot + --- Set a new listener for an S_EVENT_SHOT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnShot( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + --- Set a new listener for an S_EVENT_SHOT event for a unit. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + --- Stop listening to S_EVENT_SHOT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnShotRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + +end + +do -- OnHit + + --- Set a new listener for an S_EVENT_HIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnHit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + + return self + end + + --- Set a new listener for an S_EVENT_HIT event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) + + return self + end + + --- Stop listening to S_EVENT_HIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnHitRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_HIT ) + + return self + end + +end + +do -- OnPlayerEnterUnit + + --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self + end + + --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPlayerEnterRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self + end + +end + +do -- OnPlayerLeaveUnit + --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self + end + + --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPlayerLeaveRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self + end + +end + + --- @param #EVENT self -- @param #EVENTDATA Event function EVENT:onEvent( Event ) - self:F2( { _EVENTCODES[Event.id], Event } ) if self and self.Events and self.Events[Event.id] then if Event.initiator and Event.initiator:getCategory() == Object.Category.UNIT then @@ -6501,16 +7040,21 @@ function EVENT:onEvent( Event ) end self:E( { _EVENTCODES[Event.id], Event.IniUnitName, Event.TgtUnitName, Event.WeaponName } ) for ClassName, EventData in pairs( self.Events[Event.id] ) do + self:T( { "Evaluating class ", { EventData.EventSelf:GetClassNameAndID(), ClassName } } ) if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then - self:E( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) + self:T( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventSelf, Event ) else if Event.IniDCSUnit and not EventData.IniUnit then - self:E( { "Calling event function for class ", ClassName } ) - EventData.EventFunction( EventData.EventSelf, Event ) + if ClassName == EventData.EventSelf:GetClassNameAndID() then + self:T( { "Calling event function for class ", ClassName } ) + EventData.EventFunction( EventData.EventSelf, Event ) + end end end end + else + self:E( { _EVENTCODES[Event.id], Event } ) end end @@ -6586,180 +7130,365 @@ function SUBMENU:New( MenuText, ParentMenu ) return Child end --- This local variable is used to cache the menus registered under clients. --- Menus don't dissapear when clients are destroyed and restarted. --- So every menu for a client created must be tracked so that program logic accidentally does not create --- the same menus twice during initialization logic. --- These menu classes are handling this logic with this variable. -local _MENUCLIENTS = {} +do ---- The MENU_CLIENT class --- @type MENU_CLIENT --- @extends Menu#MENU -MENU_CLIENT = { - ClassName = "MENU_CLIENT" -} - ---- Creates a new menu item for a group --- @param self --- @param Client#CLIENT MenuClient The Client owning the menu. --- @param #string MenuText The text for the menu. --- @param #table ParentMenu The parent menu. --- @return #MENU_CLIENT self -function MENU_CLIENT:New( MenuClient, MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { MenuClient, MenuText, ParentMenu } ) - - self.MenuClient = MenuClient - self.MenuClientGroupID = MenuClient:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu + -- This local variable is used to cache the menus registered under clients. + -- Menus don't dissapear when clients are destroyed and restarted. + -- So every menu for a client created must be tracked so that program logic accidentally does not create + -- the same menus twice during initialization logic. + -- These menu classes are handling this logic with this variable. + local _MENUCLIENTS = {} - self.Menus = {} - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} + --- The MENU_CLIENT class + -- @type MENU_CLIENT + -- @extends Menu#MENU + MENU_CLIENT = { + ClassName = "MENU_CLIENT" + } + + --- Creates a new menu item for a group + -- @param self + -- @param Client#CLIENT MenuClient The Client owning the menu. + -- @param #string MenuText The text for the menu. + -- @param #table ParentMenu The parent menu. + -- @return #MENU_CLIENT self + function MENU_CLIENT:New( MenuClient, MenuText, ParentMenu ) + + -- Arrange meta tables + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + self:F( { MenuClient, MenuText, ParentMenu } ) + + self.MenuClient = MenuClient + self.MenuClientGroupID = MenuClient:GetClientGroupID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self.Menus = {} + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + end + + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) + MenuPath[MenuPathID] = self.MenuPath + + self:T( { MenuClient:GetClientGroupName(), self.MenuPath } ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end + return self end - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + --- Removes the sub menus recursively of this MENU_CLIENT. + -- @param #MENU_CLIENT self + -- @return #MENU_CLIENT self + function MENU_CLIENT:RemoveSubMenus() + self:F( self.MenuPath ) + + for MenuID, Menu in pairs( self.Menus ) do + Menu:Remove() + end + end + + --- Removes the sub menus recursively of this MENU_CLIENT. + -- @param #MENU_CLIENT self + -- @return #MENU_CLIENT self + function MENU_CLIENT:Remove() + self:F( self.MenuPath ) + + self:RemoveSubMenus() + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil + end + + + --- The MENU_CLIENT_COMMAND class + -- @type MENU_CLIENT_COMMAND + -- @extends Menu#MENU + MENU_CLIENT_COMMAND = { + ClassName = "MENU_CLIENT_COMMAND" + } + + --- Creates a new radio command item for a group + -- @param self + -- @param Client#CLIENT MenuClient The Client owning the menu. + -- @param MenuText The text for the menu. + -- @param ParentMenu The parent menu. + -- @param CommandMenuFunction A function that is called when the menu key is pressed. + -- @param CommandMenuArgument An argument for the function. + -- @return Menu#MENU_CLIENT_COMMAND self + function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) + + -- Arrange meta tables + + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + + self.MenuClient = MenuClient + self.MenuClientGroupID = MenuClient:GetClientGroupID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + end + + self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) + MenuPath[MenuPathID] = self.MenuPath + + self.CommandMenuFunction = CommandMenuFunction + self.CommandMenuArgument = CommandMenuArgument + + ParentMenu.Menus[self.MenuPath] = self + + return self + end + + function MENU_CLIENT_COMMAND:Remove() + self:F( self.MenuPath ) + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil + end +end - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath +--- MENU_GROUP - self:T( { MenuClient:GetClientGroupName(), self.MenuPath } ) +do + -- This local variable is used to cache the menus registered under clients. + -- Menus don't dissapear when clients are destroyed and restarted. + -- So every menu for a client created must be tracked so that program logic accidentally does not create + -- the same menus twice during initialization logic. + -- These menu classes are handling this logic with this variable. + local _MENUGROUPS = {} - if ParentMenu and ParentMenu.Menus then + --- The MENU_GROUP class + -- @type MENU_GROUP + -- @extends Menu#MENU + MENU_GROUP = { + ClassName = "MENU_GROUP" + } + + --- Creates a new menu item for a group + -- @param self + -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param #string MenuText The text for the menu. + -- @param #table ParentMenu The parent menu. + -- @return #MENU_GROUP self + function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) + + -- Arrange meta tables + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + self:F( { MenuGroup, MenuText, ParentMenu } ) + + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self.Menus = {} + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) + end + + self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, MenuParentPath ) + MenuPath[MenuPathID] = self.MenuPath + + self:T( { self.MenuGroupID, self.MenuPath } ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end + return self + end + + --- Removes the sub menus recursively of this MENU_GROUP. + -- @param #MENU_GROUP self + -- @return #MENU_GROUP self + function MENU_GROUP:RemoveSubMenus() + self:F( self.MenuPath ) + + for MenuID, Menu in pairs( self.Menus ) do + Menu:Remove() + end + + end + + --- Removes the sub menus recursively of this MENU_GROUP. + -- @param #MENU_GROUP self + -- @return #MENU_GROUP self + function MENU_GROUP:Remove() + self:F( self.MenuPath ) + + self:RemoveSubMenus() + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + return nil + end + + + --- The MENU_GROUP_COMMAND class + -- @type MENU_GROUP_COMMAND + -- @extends Menu#MENU + MENU_GROUP_COMMAND = { + ClassName = "MENU_GROUP_COMMAND" + } + + --- Creates a new radio command item for a group + -- @param #MENU_GROUP_COMMAND self + -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param MenuText The text for the menu. + -- @param ParentMenu The parent menu. + -- @param CommandMenuFunction A function that is called when the menu key is pressed. + -- @param CommandMenuArgument An argument for the function. + -- @return Menu#MENU_GROUP_COMMAND self + function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) + + -- Arrange meta tables + + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) + end + + self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) + self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) + MenuPath[MenuPathID] = self.MenuPath + + self.CommandMenuFunction = CommandMenuFunction + self.CommandMenuArgument = CommandMenuArgument + ParentMenu.Menus[self.MenuPath] = self + + return self end - return self -end + + function MENU_GROUP_COMMAND:Remove() + self:F( self.MenuPath ) + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] ---- Removes the sub menus recursively of this MENU_CLIENT. --- @param #MENU_CLIENT self --- @return #MENU_CLIENT self -function MENU_CLIENT:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil end end ---- Removes the sub menus recursively of this MENU_CLIENT. --- @param #MENU_CLIENT self --- @return #MENU_CLIENT self -function MENU_CLIENT:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil -end - - ---- The MENU_CLIENT_COMMAND class --- @type MENU_CLIENT_COMMAND --- @extends Menu#MENU -MENU_CLIENT_COMMAND = { - ClassName = "MENU_CLIENT_COMMAND" -} - ---- Creates a new radio command item for a group --- @param self --- @param Client#CLIENT MenuClient The Client owning the menu. --- @param MenuText The text for the menu. --- @param ParentMenu The parent menu. --- @param CommandMenuFunction A function that is called when the menu key is pressed. --- @param CommandMenuArgument An argument for the function. --- @return Menu#MENU_CLIENT_COMMAND self -function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - - self.MenuClient = MenuClient - self.MenuClientGroupID = MenuClient:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) - MenuPath[MenuPathID] = self.MenuPath - - self.CommandMenuFunction = CommandMenuFunction - self.CommandMenuArgument = CommandMenuArgument - - ParentMenu.Menus[self.MenuPath] = self - - return self -end - -function MENU_CLIENT_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil -end - - --- The MENU_COALITION class -- @type MENU_COALITION -- @extends Menu#MENU @@ -6769,11 +7498,11 @@ MENU_COALITION = { --- Creates a new coalition menu item -- @param #MENU_COALITION self --- @param DCSCoalition#coalition.side MenuCoalition The coalition owning the menu. +-- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_COALITION self -function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) +function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) -- Arrange meta tables local MenuParentPath = {} @@ -6782,9 +7511,9 @@ function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) end local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { MenuCoalition, MenuText, ParentMenu } ) + self:F( { Coalition, MenuText, ParentMenu } ) - self.MenuCoalition = MenuCoalition + self.Coalition = Coalition self.MenuParentPath = MenuParentPath self.MenuText = MenuText self.ParentMenu = ParentMenu @@ -6793,7 +7522,7 @@ function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) self:T( { MenuParentPath, MenuText } ) - self.MenuPath = missionCommands.addSubMenuForCoalition( self.MenuCoalition, MenuText, MenuParentPath ) + self.MenuPath = missionCommands.addSubMenuForCoalition( self.Coalition, MenuText, MenuParentPath ) self:T( { self.MenuPath } ) @@ -7360,12 +8089,12 @@ end --- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. -- @param #GROUP self -- @return DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. -function GROUP:GetPointVec2() +function GROUP:GetVec2() self:F2( self.GroupName ) local UnitPoint = self:GetUnit(1) - UnitPoint:GetPointVec2() - local GroupPointVec2 = UnitPoint:GetPointVec2() + UnitPoint:GetVec2() + local GroupPointVec2 = UnitPoint:GetVec2() self:T3( GroupPointVec2 ) return GroupPointVec2 end @@ -7757,7 +8486,7 @@ end -- @param #string Message The message text -- @param DCSTypes#Duration Duration The duration of the message. -- @return Message#MESSAGE -function GROUP:Message( Message, Duration ) +function GROUP:GetMessage( Message, Duration ) self:F2( { Message, Duration } ) local DCSGroup = self:GetDCSObject() @@ -7778,7 +8507,7 @@ function GROUP:MessageToAll( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToAll() + self:GetMessage( Message, Duration ):ToAll() end return nil @@ -7794,7 +8523,7 @@ function GROUP:MessageToRed( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToRed() + self:GetMessage( Message, Duration ):ToRed() end return nil @@ -7810,7 +8539,7 @@ function GROUP:MessageToBlue( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToBlue() + self:GetMessage( Message, Duration ):ToBlue() end return nil @@ -7827,7 +8556,42 @@ function GROUP:MessageToClient( Message, Duration, Client ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToClient( Client ) + self:GetMessage( Message, Duration ):ToClient( Client ) + end + + return nil +end + +--- Send a message to a @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #GROUP self +-- @param #string Message The message text +-- @param DCSTypes#Duration Duration The duration of the message. +-- @param Group#GROUP MessageGroup The GROUP object receiving the message. +function GROUP:MessageToGroup( Message, Duration, MsgGroup ) + self:F2( { Message, Duration } ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + if DCSGroup:isExist() then + self:GetMessage( Message, Duration ):ToGroup( MsgGroup ) + end + end + + return nil +end + +--- Send a message to the players in the @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #GROUP self +-- @param #string Message The message text +-- @param DCSTypes#Duration Duration The duration of the message. +function GROUP:Message( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + self:GetMessage( Message, Duration ):ToGroup( self ) end return nil @@ -7883,7 +8647,7 @@ end -- ----------------------------- -- The UNIT class provides methods to obtain the current point or position of the DCS Unit. -- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetPointVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including oriëntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. +-- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. -- -- 1.5) Test if alive -- ------------------ @@ -7943,6 +8707,14 @@ UNIT = { -- @field Orange -- @field Blue +--- Unit.SensorType +-- @type Unit.SensorType +-- @field OPTIC +-- @field RADAR +-- @field IRST +-- @field RWR + + -- Registration. --- Create a new UNIT from DCSUnit. @@ -8013,6 +8785,24 @@ function UNIT:IsActive() return nil end +--- Destroys the @{Unit}. +-- @param Unit#UNIT self +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:Destroy() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:destroy() + end + + return nil +end + + + --- Returns the Unit's callsign - the localized string. -- @param Unit#UNIT self -- @return #string The Callsign of the Unit. @@ -8150,6 +8940,46 @@ end -- Need to add here a function per sensortype -- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) +--- Returns if the unit has sensors of a certain type. +-- @param Unit#UNIT self +-- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSensors( ... ) + self:F2( arg ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) + return HasSensors + end + + return nil +end + +--- Returns if the unit is SEADable. +-- @param Unit#UNIT self +-- @return #boolean returns true if the unit is SEADable. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSEAD() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSEADAttributes = DCSUnit:getDesc().attributes + + local HasSEAD = false + if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or + UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then + HasSEAD = true + end + return HasSEAD + end + + return nil +end + --- Returns two values: -- -- * First value indicates if at least one of the unit's radar(s) is on. @@ -8222,7 +9052,47 @@ function UNIT:GetLife0() return nil end +--- Returns the Unit's A2G threat level on a scale from 1 to 10 ... +-- The following threat levels are foreseen: +-- +-- * Threat level 0: Unit is unarmed. +-- * Threat level 1: Unit is infantry. +-- * Threat level 2: Unit is an infantry vehicle. +-- * Threat level 3: Unit is ground artillery. +-- * Threat level 4: Unit is a tank. +-- * Threat level 5: Unit is a modern tank or ifv with ATGM. +-- * Threat level 6: Unit is a AAA. +-- * Threat level 7: Unit is a SAM or manpad, IR guided. +-- * Threat level 8: Unit is a Short Range SAM, radar guided. +-- * Threat level 9: Unit is a Medium Range SAM, radar guided. +-- * Threat level 10: Unit is a Long Range SAM, radar guided. +function UNIT:GetThreatLevel() + local Attributes = self:GetDesc().attributes + local ThreatLevel = 0 + + self:T2( Attributes ) + + if Attributes["LR SAM"] then ThreatLevel = 10 + elseif Attributes["MR SAM"] then ThreatLevel = 9 + elseif Attributes["SR SAM"] and + not Attributes["IR Guided SAM"] then ThreatLevel = 8 + elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and + Attributes["IR Guided SAM"] then ThreatLevel = 7 + elseif Attributes["AAA"] then ThreatLevel = 6 + elseif Attributes["Modern Tanks"] then ThreatLevel = 5 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + Attributes["ATGM"] then ThreatLevel = 4 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + not Attributes["ATGM"] then ThreatLevel = 3 + elseif Attributes["Old Tanks"] or Attributes["APC"] then ThreatLevel = 2 + elseif Attributes["Infantry"] then ThreatLevel = 1 + end + + self:T2( ThreatLevel ) + return ThreatLevel + +end -- Is functions @@ -8239,9 +9109,9 @@ function UNIT:IsInZone( Zone ) self:T( { IsInZone } ) return IsInZone - else - return false end + + return false end --- Returns true if the unit is not within a @{Zone}. @@ -8323,14 +9193,22 @@ end -- @param #UNIT self function UNIT:FlareRed() self:F2() - trigger.action.signalFlare( self:GetPointVec3(), trigger.flareColor.Red, 0 ) + local Vec3 = self:GetPointVec3() + if Vec3 then + trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) + end end --- Smoke the UNIT. -- @param #UNIT self -function UNIT:Smoke( SmokeColor ) +function UNIT:Smoke( SmokeColor, Range ) self:F2() - trigger.action.smoke( self:GetPointVec3(), SmokeColor ) + if Range then + trigger.action.smoke( self:GetRandomPointVec3( Range ), SmokeColor ) + else + trigger.action.smoke( self:GetPointVec3(), SmokeColor ) + end + end --- Smoke the UNIT Green. @@ -8377,13 +9255,84 @@ end function UNIT:IsAir() self:F2() - local UnitDescriptor = self.DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + local DCSUnit = self:GetDCSObject() - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + + local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) + + self:T3( IsAirResult ) + return IsAirResult + end + + return nil +end - self:T3( IsAirResult ) - return IsAirResult +--- Returns if the unit is of an ground category. +-- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ground category evaluation result. +function UNIT:IsGround() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) + + local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) + + self:T3( IsGroundResult ) + return IsGroundResult + end + + return nil +end + +--- Returns if the unit is a friendly unit. +-- @param #UNIT self +-- @return #boolean IsFriendly evaluation result. +function UNIT:IsFriendly( FriendlyCoalition ) + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCoalition = DCSUnit:getCoalition() + self:T3( { UnitCoalition, FriendlyCoalition } ) + + local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) + + self:E( IsFriendlyResult ) + return IsFriendlyResult + end + + return nil +end + +--- Returns if the unit is of a ship category. +-- If the unit is a ship, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ship category evaluation result. +function UNIT:IsShip() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) + + local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) + + self:T3( IsShipResult ) + return IsShipResult + end + + return nil end --- This module contains the ZONE classes, inherited from @{Zone#ZONE_BASE}. @@ -8489,10 +9438,10 @@ end --- Returns if a location is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_BASE:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_BASE:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) return false end @@ -8509,18 +9458,27 @@ function ZONE_BASE:IsPointVec3InZone( PointVec3 ) return InZone end +--- Returns the Vec2 coordinate of the zone. +-- @param #ZONE_BASE self +-- @return #nil. +function ZONE_BASE:GetVec2() + self:F2( self.ZoneName ) + + return nil +end --- Define a random @{DCSTypes#Vec2} within the zone. -- @param #ZONE_BASE self --- @return DCSTypes#Vec2 The Vec2 coordinates. +-- @return #nil The Vec2 coordinates. function ZONE_BASE:GetRandomVec2() - return { x = 0, y = 0 } + return nil end --- Get the bounding square the zone. -- @param #ZONE_BASE self --- @return #ZONE_BASE.BoundingSquare The bounding square. +-- @return #nil The bounding square. function ZONE_BASE:GetBoundingSquare() - return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } + --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } + return nil end @@ -8535,7 +9493,7 @@ end --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS --- @field DCSTypes#Vec2 PointVec2 The current location of the zone. +-- @field DCSTypes#Vec2 Vec2 The current location of the zone. -- @field DCSTypes#Distance Radius The radius of the zone. -- @extends Zone#ZONE_BASE ZONE_RADIUS = { @@ -8545,15 +9503,15 @@ ZONE_RADIUS = { --- Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self -- @param #string ZoneName Name of the zone. --- @param DCSTypes#Vec2 PointVec2 The location of the zone. +-- @param DCSTypes#Vec2 Vec2 The location of the zone. -- @param DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:New( ZoneName, PointVec2, Radius ) +function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, PointVec2, Radius } ) + self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius - self.PointVec2 = PointVec2 + self.Vec2 = Vec2 return self end @@ -8567,7 +9525,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) self:F2( SmokeColor ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() Points = Points and Points or 360 @@ -8576,8 +9534,8 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 - Point.x = PointVec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = PointVec2.y + math.sin( Radial ) * self:GetRadius() + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() + Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() POINT_VEC2:New( Point.x, Point.y ):Smoke( SmokeColor ) end @@ -8595,7 +9553,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) self:F2( { FlareColor, Azimuth } ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() Points = Points and Points or 360 @@ -8604,8 +9562,8 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 - Point.x = PointVec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = PointVec2.y + math.sin( Radial ) * self:GetRadius() + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() + Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() POINT_VEC2:New( Point.x, Point.y ):Flare( FlareColor, Azimuth ) end @@ -8639,26 +9597,26 @@ end --- Returns the location of the zone. -- @param #ZONE_RADIUS self -- @return DCSTypes#Vec2 The location of the zone. -function ZONE_RADIUS:GetPointVec2() +function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) - self:T2( { self.PointVec2 } ) + self:T2( { self.Vec2 } ) - return self.PointVec2 + return self.Vec2 end --- Sets the location of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 PointVec2 The new location of the zone. +-- @param DCSTypes#Vec2 Vec2 The new location of the zone. -- @return DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetPointVec2( PointVec2 ) +function ZONE_RADIUS:SetPointVec2( Vec2 ) self:F2( self.ZoneName ) - self.PointVec2 = PointVec2 + self.Vec2 = Vec2 - self:T2( { self.PointVec2 } ) + self:T2( { self.Vec2 } ) - return self.PointVec2 + return self.Vec2 end --- Returns the point of the zone. @@ -8668,9 +9626,9 @@ end function ZONE_RADIUS:GetPointVec3( Height ) self:F2( self.ZoneName ) - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() - local PointVec3 = { x = PointVec2.x, y = land.getHeight( self:GetPointVec2() ) + Height, z = PointVec2.y } + local PointVec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { PointVec3 } ) @@ -8680,15 +9638,17 @@ end --- Returns if a location is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_RADIUS:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_RADIUS:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) - local ZonePointVec2 = self:GetPointVec2() - - if (( PointVec2.x - ZonePointVec2.x )^2 + ( PointVec2.y - ZonePointVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then - return true + local ZoneVec2 = self:GetVec2() + + if ZoneVec2 then + if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then + return true + end end return false @@ -8698,10 +9658,10 @@ end -- @param #ZONE_RADIUS self -- @param DCSTypes#Vec3 PointVec3 The point to test. -- @return #boolean true if the point is within the zone. -function ZONE_RADIUS:IsPointVec3InZone( PointVec3 ) - self:F2( PointVec3 ) +function ZONE_RADIUS:IsPointVec3InZone( Vec3 ) + self:F2( Vec3 ) - local InZone = self:IsPointVec2InZone( { x = PointVec3.x, y = PointVec3.z } ) + local InZone = self:IsPointVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone end @@ -8713,11 +9673,11 @@ function ZONE_RADIUS:GetRandomVec2() self:F( self.ZoneName ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() local angle = math.random() * math.pi*2; - Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = PointVec2.y + math.sin( angle ) * math.random() * self:GetRadius(); + Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); + Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); self:T( { Point } ) @@ -8771,10 +9731,11 @@ ZONE_UNIT = { -- @param DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetPointVec2(), Radius ) ) - self:F( { ZoneName, ZoneUNIT:GetPointVec2(), Radius } ) + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) + self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT + self.LastVec2 = ZoneUNIT:GetVec2() return self end @@ -8783,14 +9744,20 @@ end --- Returns the current location of the @{Unit#UNIT}. -- @param #ZONE_UNIT self -- @return DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. -function ZONE_UNIT:GetPointVec2() +function ZONE_UNIT:GetVec2() self:F( self.ZoneName ) - local ZonePointVec2 = self.ZoneUNIT:GetPointVec2() + local ZoneVec2 = self.ZoneUNIT:GetVec2() + if ZoneVec2 then + self.LastVec2 = ZoneVec2 + return ZoneVec2 + else + return self.LastVec2 + end - self:T( { ZonePointVec2 } ) - - return ZonePointVec2 + self:T( { ZoneVec2 } ) + + return nil end --- Returns a random location within the zone. @@ -8801,6 +9768,9 @@ function ZONE_UNIT:GetRandomVec2() local Point = {} local PointVec2 = self.ZoneUNIT:GetPointVec2() + if not PointVec2 then + PointVec2 = self.LastVec2 + end local angle = math.random() * math.pi*2; Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); @@ -8811,6 +9781,24 @@ function ZONE_UNIT:GetRandomVec2() return Point end +--- Returns the point of the zone. +-- @param #ZONE_RADIUS self +-- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return DCSTypes#Vec3 The point of the zone. +function ZONE_UNIT:GetPointVec3( Height ) + self:F2( self.ZoneName ) + + Height = Height or 0 + + local Vec2 = self:GetVec2() + + local PointVec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } + + self:T2( { PointVec3 } ) + + return PointVec3 +end + --- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. -- @type ZONE_GROUP -- @field Group#GROUP ZoneGROUP @@ -8955,10 +9943,10 @@ end --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_POLYGON_BASE:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) local Next local Prev @@ -8969,8 +9957,8 @@ function ZONE_POLYGON_BASE:IsPointVec2InZone( PointVec2 ) while Next <= #self.Polygon do self:T( { Next, Prev, self.Polygon[Next], self.Polygon[Prev] } ) - if ( ( ( self.Polygon[Next].y > PointVec2.y ) ~= ( self.Polygon[Prev].y > PointVec2.y ) ) and - ( PointVec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( PointVec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) + if ( ( ( self.Polygon[Next].y > Vec2.y ) ~= ( self.Polygon[Prev].y > Vec2.y ) ) and + ( Vec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( Vec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) ) then InPolygon = not InPolygon end @@ -9838,7 +10826,6 @@ function DATABASE:AddUnit( DCSUnitName ) if not self.UNITS[DCSUnitName] then local UnitRegister = UNIT:Register( DCSUnitName ) - self:E( UnitRegister.UnitName ) self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) end @@ -10709,10 +11696,15 @@ end --- SET_BASE class -- @type SET_BASE +-- @field #table Filter +-- @field #table Set +-- @field #table List -- @extends Base#BASE SET_BASE = { ClassName = "SET_BASE", + Filter = {}, Set = {}, + List = {}, } --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. @@ -10731,6 +11723,10 @@ function SET_BASE:New( Database ) self.YieldInterval = 10 self.TimeInterval = 0.001 + self.List = {} + self.List.__index = self.List + self.List = setmetatable( { Count = 0 }, self.List ) + return self end @@ -10760,18 +11756,90 @@ end -- @param Base#BASE Object -- @return Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) + self:F2( ObjectName ) - self.Set[ObjectName] = Object + local t = { _ = Object } + + if self.List.last then + self.List.last._next = t + t._prev = self.List.last + self.List.last = t + else + -- this is the first node + self.List.first = t + self.List.last = t + end + + self.List.Count = self.List.Count + 1 + + self.Set[ObjectName] = t._ + end --- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName function SET_BASE:Remove( ObjectName ) + self:E( ObjectName ) - self.Set[ObjectName] = nil + local t = self.Set[ObjectName] + + if t then + if t._next then + if t._prev then + t._next._prev = t._prev + t._prev._next = t._next + else + -- this was the first node + t._next._prev = nil + self.List._first = t._next + end + elseif t._prev then + -- this was the last node + t._prev._next = nil + self.List._last = t._prev + else + -- this was the only node + self.List._first = nil + self.List._last = nil + end + + t._next = nil + t._prev = nil + self.List.Count = self.List.Count - 1 + + self.Set[ObjectName] = nil + end + end +--- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. +-- @param #SET_BASE self +-- @return #number Count +function SET_BASE:Count() + + return self.List.Count +end + + + +--- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). +-- @param #SET_BASE self +-- @param #SET_BASE BaseSet +-- @return #SET_BASE +function SET_BASE:SetDatabase( BaseSet ) + + -- Copy the filter criteria of the BaseSet + local OtherFilter = routines.utils.deepCopy( BaseSet.Filter ) + self.Filter = OtherFilter + + -- Now base the new Set on the BaseSet + self.Database = BaseSet:GetSet() + return self +end + + + --- Define the SET iterator **"yield interval"** and the **"time interval"**. -- @param #SET_BASE self -- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. @@ -10786,6 +11854,20 @@ function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) end +--- Filters for the defined collection. +-- @param #SET_BASE self +-- @return #SET_BASE self +function SET_BASE:FilterOnce() + + for ObjectName, Object in pairs( self.Database ) do + + if self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + end + end + + return self +end --- Starts the filtering for the defined collection. -- @param #SET_BASE self @@ -10805,13 +11887,25 @@ function SET_BASE:_FilterStart() _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) -- Follow alive players and clients --- _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) --- _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) return self end +--- Stops the filtering for the defined collection. +-- @param #SET_BASE self +-- @return #SET_BASE self +function SET_BASE:FilterStop() + + _EVENTDISPATCHER:OnBirthRemove( self ) + _EVENTDISPATCHER:OnDeadRemove( self ) + _EVENTDISPATCHER:OnCrashRemove( self ) + + return self +end + --- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. -- @param #SET_BASE self -- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. @@ -10825,9 +11919,9 @@ function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) for ObjectID, ObjectData in pairs( self.Set ) do if NearestObject == nil then NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetPointVec2() ) + ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) else - local Distance = PointVec2:DistanceFromVec2( ObjectData:GetPointVec2() ) + local Distance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) if Distance < ClosestDistance then NearestObject = ObjectData ClosestDistance = Distance @@ -10884,49 +11978,58 @@ end -- @param #SET_BASE self -- @param Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) - self:F3( { Event } ) + self:E( { Event } ) if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) - if ObjectName and Object then + self:E({ObjectName, Object}) + if ObjectName and Object ~= nil then self:Remove( ObjectName ) end end end ------ Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). ----- @param #SET_BASE self ----- @param Event#EVENTDATA Event ---function SET_BASE:_EventOnPlayerEnterUnit( Event ) --- self:F3( { Event } ) --- --- if Event.IniDCSUnit then --- if self:IsIncludeObject( Event.IniDCSUnit ) then --- if not self.PlayersAlive[Event.IniDCSUnitName] then --- self:E( { "Add player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) --- self.PlayersAlive[Event.IniDCSUnitName] = Event.IniDCSUnit:getPlayerName() --- self.ClientsAlive[Event.IniDCSUnitName] = _DATABASE.Clients[ Event.IniDCSUnitName ] --- end --- end --- end ---end --- ------ Handles the OnPlayerLeaveUnit event to clean the active players table. ----- @param #SET_BASE self ----- @param Event#EVENTDATA Event ---function SET_BASE:_EventOnPlayerLeaveUnit( Event ) --- self:F3( { Event } ) --- --- if Event.IniDCSUnit then --- if self:IsIncludeObject( Event.IniDCSUnit ) then --- if self.PlayersAlive[Event.IniDCSUnitName] then --- self:E( { "Cleaning player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) --- self.PlayersAlive[Event.IniDCSUnitName] = nil --- self.ClientsAlive[Event.IniDCSUnitName] = nil --- end --- end --- end ---end +--- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). +-- @param #SET_BASE self +-- @param Event#EVENTDATA Event +function SET_BASE:_EventOnPlayerEnterUnit( Event ) + self:F3( { Event } ) + + if Event.IniDCSUnit then + local ObjectName, Object = self:AddInDatabase( Event ) + self:T3( ObjectName, Object ) + if self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + --self:_EventOnPlayerEnterUnit( Event ) + end + end +end + +--- Handles the OnPlayerLeaveUnit event to clean the active players table. +-- @param #SET_BASE self +-- @param Event#EVENTDATA Event +function SET_BASE:_EventOnPlayerLeaveUnit( Event ) + self:F3( { Event } ) + + local ObjectName = Event.IniDCSUnit + if Event.IniDCSUnit then + if Event.IniDCSGroup then + local GroupUnits = Event.IniDCSGroup:getUnits() + local PlayerCount = 0 + for _, DCSUnit in pairs( GroupUnits ) do + if DCSUnit ~= Event.IniDCSUnit then + if DCSUnit:getPlayer() ~= nil then + PlayerCount = PlayerCount + 1 + end + end + end + self:E(PlayerCount) + if PlayerCount == 0 then + self:Remove( Event.IniDCSGroupName ) + end + end + end +end -- Iterators @@ -10939,7 +12042,8 @@ function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArgumen local function CoRoutine() local Count = 0 - for ObjectID, Object in pairs( Set ) do + for ObjectID, ObjectData in pairs( Set ) do + local Object = ObjectData self:T3( Object ) if Function then if Function( unpack( FunctionArguments ), Object ) == true then @@ -11039,7 +12143,7 @@ function SET_BASE:Flush() for ObjectName, Object in pairs( self.Set ) do ObjectNames = ObjectNames .. ObjectName .. ", " end - self:T( { "Objects in Set:", ObjectNames } ) + self:E( { "Objects in Set:", ObjectNames } ) return ObjectNames end @@ -11217,6 +12321,8 @@ function SET_GROUP:FilterStart() self:_FilterStart() end + + return self end @@ -11451,10 +12557,6 @@ function SET_UNIT:New() -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) - _EVENTDISPATCHER:OnBirth( self._EventOnBirth, self ) - _EVENTDISPATCHER:OnDead( self._EventOnDeadOrCrash, self ) - _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) - return self end @@ -11609,6 +12711,31 @@ function SET_UNIT:FilterPrefixes( Prefixes ) return self end +--- Builds a set of units having a radar of give types. +-- All the units having a radar of a given type will be included within the set. +-- @param #SET_UNIT self +-- @param #table RadarTypes The radar types. +-- @return #SET_UNIT self +function SET_UNIT:FilterHasRadar( RadarTypes ) + + self.Filter.RadarTypes = self.Filter.RadarTypes or {} + if type( RadarTypes ) ~= "table" then + RadarTypes = { RadarTypes } + end + for RadarTypeID, RadarType in pairs( RadarTypes ) do + self.Filter.RadarTypes[RadarType] = RadarType + end + return self +end + +--- Builds a set of SEADable units. +-- @param #SET_UNIT self +-- @return #SET_UNIT self +function SET_UNIT:FilterHasSEAD() + + self.Filter.SEAD = true + return self +end @@ -11648,9 +12775,10 @@ end -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:FindInDatabase( Event ) - self:F3( { Event } ) + self:E( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] + + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. @@ -11709,6 +12837,119 @@ function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) return self end +--- Returns a comma separated string of the unit types with a count in the @{Set}. +-- @param #SET_UNIT self +-- @return #string The unit types string +function SET_UNIT:GetUnitTypesText() + self:F2() + + local MT = {} -- Message Text + local UnitTypes = {} + + for UnitID, UnitData in pairs( self:GetSet() ) do + local TextUnit = UnitData -- Unit#UNIT + if TextUnit:IsAlive() then + local UnitType = TextUnit:GetTypeName() + + if not UnitTypes[UnitType] then + UnitTypes[UnitType] = 1 + else + UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + end + end + end + + for UnitTypeID, UnitType in pairs( UnitTypes ) do + MT[#MT+1] = UnitType .. " of " .. UnitTypeID + end + + return table.concat( MT, ", " ) +end + + +--- Returns if the @{Set} has targets having a radar (of a given type). +-- @param #SET_UNIT self +-- @param DCSUnit#Unit.RadarType RadarType +-- @return #number The amount of radars in the Set with the given type +function SET_UNIT:HasRadar( RadarType ) + self:F2( RadarType ) + + local RadarCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitSensorTest = UnitData -- Unit#UNIT + local HasSensors + if RadarType then + HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) + else + HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) + end + self:T3(HasSensors) + if HasSensors then + RadarCount = RadarCount + 1 + end + end + + return RadarCount +end + +--- Returns if the @{Set} has targets that can be SEADed. +-- @param #SET_UNIT self +-- @return #number The amount of SEADable units in the Set +function SET_UNIT:HasSEAD() + self:F2() + + local SEADCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitSEAD = UnitData -- Unit#UNIT + if UnitSEAD:IsAlive() then + local UnitSEADAttributes = UnitSEAD:GetDesc().attributes + + local HasSEAD = UnitSEAD:HasSEAD() + + self:T3(HasSEAD) + if HasSEAD then + SEADCount = SEADCount + 1 + end + end + end + + return SEADCount +end + +--- Returns if the @{Set} has ground targets. +-- @param #SET_UNIT self +-- @return #number The amount of ground targets in the Set. +function SET_UNIT:HasGroundUnits() + self:F2() + + local GroundUnitCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitTest = UnitData -- Unit#UNIT + if UnitTest:IsGround() then + GroundUnitCount = GroundUnitCount + 1 + end + end + + return GroundUnitCount +end + +--- Returns if the @{Set} has friendly ground units. +-- @param #SET_UNIT self +-- @return #number The amount of ground targets in the Set. +function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) + self:F2() + + local FriendlyUnitCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitTest = UnitData -- Unit#UNIT + if UnitTest:IsFriendly( FriendlyCoalition ) then + FriendlyUnitCount = FriendlyUnitCount + 1 + end + end + + return FriendlyUnitCount +end + ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. @@ -11800,6 +13041,29 @@ function SET_UNIT:IsIncludeObject( MUnit ) MUnitInclude = MUnitInclude and MUnitPrefix end + if self.Filter.RadarTypes then + local MUnitRadar = false + for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do + self:T3( { "Radar:", RadarType } ) + if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then + if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. + self:T3( "RADAR Found" ) + end + MUnitRadar = true + end + end + MUnitInclude = MUnitInclude and MUnitRadar + end + + if self.Filter.SEAD then + local MUnitSEAD = false + if MUnit:HasSEAD() == true then + self:T3( "SEAD Found" ) + MUnitSEAD = true + end + MUnitInclude = MUnitInclude and MUnitSEAD + end + self:T2( MUnitInclude ) return MUnitInclude end @@ -12391,6 +13655,9 @@ end -- =============================================== -- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. -- +-- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. +-- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. +-- -- 1.1) POINT_VEC3 constructor -- --------------------------- -- @@ -12415,6 +13682,7 @@ end --- The POINT_VEC3 class -- @type POINT_VEC3 -- @extends Base#BASE +-- @field DCSTypes#Vec3 PointVec3 -- @field #POINT_VEC3.SmokeColor SmokeColor -- @field #POINT_VEC3.FlareColor FlareColor -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType @@ -12435,6 +13703,7 @@ POINT_VEC3 = { White = trigger.flareColor.White, Yellow = trigger.flareColor.Yellow }, + Metric = true, RoutePointAltType = { BARO = "BARO", }, @@ -12501,6 +13770,160 @@ function POINT_VEC3:New( x, y, z ) end +--- Return the coordinates of the POINT_VEC3 in Vec3 format. +-- @param #POINT_VEC3 self +-- @return DCSTypes#Vec3 The Vec3 coodinate. +function POINT_VEC3:GetVec3() + return self.PointVec3 +end + + +--- Return the x coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The x coodinate. +function POINT_VEC3:GetX() + return self.PointVec3.x +end + +--- Return the y coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The y coodinate. +function POINT_VEC3:GetY() + return self.PointVec3.y +end + +--- Return the z coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The z coodinate. +function POINT_VEC3:GetZ() + return self.PointVec3.z +end + + +--- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) + return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } +end + +--- Get a correction in radians of the real magnetic north of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number CorrectionRadians The correction in radians. +function POINT_VEC3:GetNorthCorrectionRadians() + local TargetVec3 = self:GetVec3() + local lat, lon = coord.LOtoLL(TargetVec3) + local north_posit = coord.LLtoLO(lat + 1, lon) + return math.atan2( north_posit.z - TargetVec3.z, north_posit.x - TargetVec3.x ) +end + + +--- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. +-- @param #POINT_VEC3 self +-- @param DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @return #number DirectionRadians The direction in radians. +function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) + local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) + --DirectionRadians = DirectionRadians + self:GetNorthCorrectionRadians() + if DirectionRadians < 0 then + DirectionRadians = DirectionRadians + 2 * math.pi -- put dir in range of 0 to 2*pi ( the full circle ) + end + return DirectionRadians +end + +--- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Distance Distance The distance in meters. +function POINT_VEC3:Get2DDistance( TargetPointVec3 ) + local TargetVec3 = TargetPointVec3:GetVec3() + local SourceVec3 = self:GetVec3() + return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 +end + +--- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Distance Distance The distance in meters. +function POINT_VEC3:Get3DDistance( TargetPointVec3 ) + local TargetVec3 = TargetPointVec3:GetVec3() + local SourceVec3 = self:GetVec3() + return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 +end + +--- Provides a Bearing / Range string +-- @param #POINT_VEC3 self +-- @param #number AngleRadians The angle in randians +-- @param #number Distance The distance +-- @return #string The BR Text +function POINT_VEC3:ToStringBR( AngleRadians, Distance ) + + AngleRadians = UTILS.Round( UTILS.ToDegree( AngleRadians ), 0 ) + if self:IsMetric() then + Distance = UTILS.Round( Distance / 1000, 2 ) + else + Distance = UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) + end + + local s = string.format( '%03d', AngleRadians ) .. ' for ' .. Distance + + s = s .. self:GetAltitudeText() -- When the POINT is a VEC2, there will be no altitude shown. + + return s +end + +--- Provides a Bearing / Range string +-- @param #POINT_VEC3 self +-- @param #number AngleRadians The angle in randians +-- @param #number Distance The distance +-- @return #string The BR Text +function POINT_VEC3:ToStringLL( acc, DMS ) + + acc = acc or 3 + local lat, lon = coord.LOtoLL( self.PointVec3 ) + return UTILS.tostringLL(lat, lon, acc, DMS) +end + +--- Return the altitude text of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #string Altitude text. +function POINT_VEC3:GetAltitudeText() + if self:IsMetric() then + return ' at ' .. UTILS.Round( self:GetY(), 0 ) + else + return ' at ' .. UTILS.Round( UTILS.MetersToFeet( self:GetY() ), 0 ) + end +end + +--- Return a BR string from a POINT_VEC3 to the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return #string The BR text. +function POINT_VEC3:GetBRText( TargetPointVec3 ) + local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 ) + local AngleRadians = self:GetDirectionRadians( DirectionVec3 ) + local Distance = self:Get2DDistance( TargetPointVec3 ) + return self:ToStringBR( AngleRadians, Distance ) +end + +--- Sets the POINT_VEC3 metric or NM. +-- @param #POINT_VEC3 self +-- @param #boolean Metric true means metric, false means NM. +function POINT_VEC3:SetMetric( Metric ) + self.Metric = Metric +end + +--- Gets if the POINT_VEC3 is metric or NM. +-- @param #POINT_VEC3 self +-- @return #boolean Metric true means metric, false means NM. +function POINT_VEC3:IsMetric() + return self.Metric +end + + + + --- Build an air type route point. -- @param #POINT_VEC3 self -- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. @@ -12686,9 +14109,17 @@ function POINT_VEC2:DistanceFromVec2( Vec2Reference ) end +--- Return no text for the altitude of the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #string Empty string. +function POINT_VEC2:GetAltitudeText() + return '' +end + --- The main include file for the MOOSE system. Include.File( "Routines" ) +Include.File( "Utils" ) Include.File( "Base" ) Include.File( "Object" ) Include.File( "Identifiable" ) @@ -12731,13 +14162,27 @@ Include.File( "MissileTrainer" ) Include.File( "PatrolZone" ) Include.File( "AIBalancer" ) Include.File( "AirbasePolice" ) + Include.File( "Detection" ) -Include.File( "FAC" ) +Include.File( "DetectionManager" ) + +Include.File( "StateMachine" ) + +Include.File( "Process" ) +Include.File( "Process_Assign" ) +Include.File( "Process_Route" ) +Include.File( "Process_Smoke" ) +Include.File( "Process_Destroy" ) + +Include.File( "Task" ) +Include.File( "Task_SEAD" ) +Include.File( "Task_CAS" ) +Include.File( "Task_BAI" ) -- The order of the declarations is important here. Don't touch it. --- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- #EVENT +_EVENTDISPATCHER = EVENT:New() -- Event#EVENT --- Declare the main database object, which is used internally by the MOOSE classes. _DATABASE = DATABASE:New() -- Database#DATABASE @@ -12803,6 +14248,8 @@ function SCORING:New( GameName ) self.SchedulerId = SCHEDULER:New( self, self._FollowPlayersScheduled, {}, 0, 5 ) self:ScoreMenu() + + self:OpenCSV( GameName) return self @@ -13006,10 +14453,17 @@ end --- Registers Scores the players completing a Mission Task. -function SCORING:_AddMissionTaskScore( PlayerUnit, MissionName, Score ) - self:F( { PlayerUnit, MissionName, Score } ) +-- @param #SCORING self +-- @param Mission#MISSION Mission +-- @param Unit#UNIT PlayerUnit +-- @param #string Text +-- @param #number Score +function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) - local PlayerName = PlayerUnit:getPlayerName() + local PlayerName = PlayerUnit:GetPlayerName() + local MissionName = Mission:GetName() + + self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) if not self.Players[PlayerName].Mission[MissionName] then self.Players[PlayerName].Mission[MissionName] = {} @@ -13023,26 +14477,37 @@ function SCORING:_AddMissionTaskScore( PlayerUnit, MissionName, Score ) self.Players[PlayerName].Score = self.Players[PlayerName].Score + Score self.Players[PlayerName].Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score - MESSAGE:New( "Player '" .. PlayerName .. "' has finished another Task in Mission '" .. MissionName .. "'. " .. - Score .. " Score points added.", - 20 ):ToAll() + MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. + Score .. " task score!", + 30 ):ToAll() - self:ScoreCSV( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:getName() ) + self:ScoreCSV( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() ) end --- Registers Mission Scores for possible multiple players that contributed in the Mission. -function SCORING:_AddMissionScore( MissionName, Score ) - self:F( { MissionName, Score } ) +-- @param #SCORING self +-- @param Mission#MISSION Mission +-- @param Unit#UNIT PlayerUnit +-- @param #string Text +-- @param #number Score +function SCORING:_AddMissionScore( Mission, Text, Score ) + + local MissionName = Mission:GetName() + + self:F( { Mission, Text, Score } ) for PlayerName, PlayerData in pairs( self.Players ) do if PlayerData.Mission[MissionName] then + PlayerData.Score = PlayerData.Score + Score PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score - MESSAGE:New( "Player '" .. PlayerName .. "' has finished Mission '" .. MissionName .. "'. " .. - Score .. " Score points added.", - 20 ):ToAll() + + MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. + Score .. " mission score!", + 60 ):ToAll() + self:ScoreCSV( PlayerName, "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score ) end end @@ -14683,7 +16148,7 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) self.MessageCategory = "" end - self.MessageDuration = MessageDuration + self.MessageDuration = MessageDuration or 5 self.MessageTime = timer.getTime() self.MessageText = MessageText @@ -14726,6 +16191,21 @@ function MESSAGE:ToClient( Client ) return self end +--- Sends a MESSAGE to a Group. +-- @param #MESSAGE self +-- @param Group#GROUP Group is the Group. +-- @return #MESSAGE +function MESSAGE:ToGroup( Group ) + self:F( Group.GroupName ) + + if Group then + + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end --- Sends a MESSAGE to the Blue coalition. -- @param #MESSAGE self -- @return #MESSAGE @@ -15850,471 +17330,871 @@ _TransportStageAction = { NONE = 0, ONCE = 1 } ---- The TASK Classes define major end-to-end activities within a MISSION. The TASK Class is the Master Class to orchestrate these activities. From this class, many concrete TASK classes are inherited. --- @module TASK +--- This module contains the TASK_BASE class. +-- +-- 1) @{#TASK_BASE} class, extends @{Base#BASE} +-- ============================================ +-- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. +-- ---------------------------------------------------------------------------------------- +-- The class provides a couple of methods to: +-- +-- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). +-- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. +-- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. +-- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. +-- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. +-- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} +-- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. +-- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. +-- +-- 1.2) Set and enquire task status (beyond the task state machine processing). +-- ---------------------------------------------------------------------------- +-- A task needs to implement as a minimum the following task states: +-- +-- * **Success**: Expresses the successful execution and finalization of the task. +-- * **Failed**: Expresses the failure of a task. +-- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. +-- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. +-- +-- A task may also implement the following task states: +-- +-- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. +-- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. +-- +-- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. +-- +-- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. +-- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. +-- +-- 1.3) Add scoring when reaching a certain task status: +-- ----------------------------------------------------- +-- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +-- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. +-- +-- 1.4) Task briefing: +-- ------------------- +-- A task briefing can be given that is shown to the player when he is assigned to the task. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task - - - - - - ---- The TASK class --- @type TASK +--- The TASK_BASE class +-- @type TASK_BASE +-- @field Scheduler#SCHEDULER TaskScheduler +-- @field Mission#MISSION Mission +-- @field StateMachine#STATEMACHINE Fsm +-- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task -- @extends Base#BASE -TASK = { - - -- Defines the different signal types with a Task. - SIGNAL = { - COLOR = { - RED = { ID = 1, COLOR = trigger.smokeColor.Red, TEXT = "A red" }, - GREEN = { ID = 2, COLOR = trigger.smokeColor.Green, TEXT = "A green" }, - BLUE = { ID = 3, COLOR = trigger.smokeColor.Blue, TEXT = "A blue" }, - WHITE = { ID = 4, COLOR = trigger.smokeColor.White, TEXT = "A white" }, - ORANGE = { ID = 5, COLOR = trigger.smokeColor.Orange, TEXT = "An orange" } - }, - TYPE = { - SMOKE = { ID = 1, TEXT = "smoke" }, - FLARE = { ID = 2, TEXT = "flare" } - } - }, - ClassName = "TASK", - Mission = {}, -- Owning mission of the Task - Name = '', - Stages = {}, - Stage = {}, - Cargos = { - InitCargos = {}, - LoadCargos = {} - }, - LandingZones = { - LandingZoneNames = {}, - LandingZones = {} - }, - ActiveStage = 0, - TaskDone = false, - TaskFailed = false, - GoalTasks = {} +TASK_BASE = { + ClassName = "TASK_BASE", + TaskScheduler = nil, + Processes = {}, + Players = nil, + Scores = {}, + Menu = {}, + SetGroup = nil, } ---- Instantiates a new TASK Base. Should never be used. Interface Class. --- @return TASK -function TASK:New() + +--- Instantiates a new TASK_BASE. Should never be used. Interface Class. +-- @param #TASK_BASE self +-- @param Mission#MISSION The mission wherein the Task is registered. +-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. +-- @param #string TaskName The name of the Task +-- @param #string TaskType The type of the Task +-- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) +-- @return #TASK_BASE self +function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) + local self = BASE:Inherit( self, BASE:New() ) - self:F() + self:E( "New TASK " .. TaskName ) + + self.Processes = {} + self.Fsm = {} + + self.Mission = Mission + self.SetGroup = SetGroup + + self:SetCategory( TaskCategory ) + self:SetType( TaskType ) + self:SetName( TaskName ) + self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. + + self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - -- assign Task default values during construction - self.TaskBriefing = "Task: No Task." - self.Time = timer.getTime() - self.ExecuteStage = _TransportExecuteStage.NONE + return self +end + +--- Cleans all references of a TASK_BASE. +-- @param #TASK_BASE self +-- @return #nil +function TASK_BASE:CleanUp() + + _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) + _EVENTDISPATCHER:OnDeadRemove( self ) + _EVENTDISPATCHER:OnCrashRemove( self ) + _EVENTDISPATCHER:OnPilotDeadRemove( self ) + + return nil +end + + +--- Assign the @{Task}to a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +function TASK_BASE:AssignToGroup( TaskGroup ) + self:F2( TaskGroup:GetName() ) + + local TaskGroupName = TaskGroup:GetName() + + TaskGroup:SetState( TaskGroup, "Assigned", self ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:AssignToUnit( TaskUnit ) + end + end +end + +--- Send the briefng message of the @{Task} to the assigned @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:SendBriefingToAssignedGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + if self:IsAssignedToGroup( TaskGroup ) then + TaskGroup:Message( self.TaskBriefing, 60 ) + end + end +end + + +--- Assign the @{Task} from the @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:UnAssignFromGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:UnAssignFromUnit( TaskUnit ) + end + end + end +end + +--- Returns if the @{Task} is assigned to the Group. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #boolean +function TASK_BASE:IsAssignedToGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + if self:IsStateAssigned() then + if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then + return true + end + end + + return false +end + +--- Assign the @{Task}to an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + return nil +end + +--- UnAssign the @{Task} from an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:UnAssignFromUnit( TaskUnitName ) + self:F( TaskUnitName ) + + if self:HasStateMachine( TaskUnitName ) == true then + self:RemoveStateMachines( TaskUnitName ) + self:RemoveProcesses( TaskUnitName ) + end return self end -function TASK:SetStage( StageSequenceIncrement ) - self:F( { StageSequenceIncrement } ) +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenu() - local Valid = false - if StageSequenceIncrement ~= 0 then - self.ActiveStage = self.ActiveStage + StageSequenceIncrement - if 1 <= self.ActiveStage and self.ActiveStage <= #self.Stages then - self.Stage = self.Stages[self.ActiveStage] - self:T( { self.Stage.Name } ) - self.Frequency = self.Stage.Frequency - Valid = true - else - Valid = false - env.info( "TASK:SetStage() self.ActiveStage is smaller or larger than self.Stages array. self.ActiveStage = " .. self.ActiveStage ) - end - end - self.Time = timer.getTime() - return Valid + local MenuText = self:GetPlannedMenuText() + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not self:IsAssignedToGroup( TaskGroup ) then + self:SetPlannedMenuForGroup( TaskGroup, MenuText ) + end + end end -function TASK:Init() - self:F() - self.ActiveStage = 0 - self:SetStage(1) - self.TaskDone = false - self.TaskFailed = false +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if self:IsAssignedToGroup( TaskGroup ) then + self:SetAssignedMenuForGroup( TaskGroup ) + end + end end +--- Remove the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenu() ---- Get progress of a TASK. --- @return string GoalsText -function TASK:GetGoalProgress() - self:F2() - - local GoalsText = "" - for GoalVerb, GoalVerbData in pairs( self.GoalTasks ) do - local Goals = self:GetGoalCompletion( GoalVerb ) - if Goals and Goals ~= "" then - Goals = '(' .. Goals .. ')' - else - Goals = '( - )' - end - GoalsText = GoalsText .. GoalVerb .. ': ' .. self:GetGoalCount(GoalVerb) .. ' goals ' .. Goals .. ' of ' .. self:GetGoalTotal(GoalVerb) .. ' goals completed (' .. self:GetGoalPercentage(GoalVerb) .. '%); ' - end - - if GoalsText == "" then - GoalsText = "( - )" - end - - return GoalsText + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + end end ---- Show progress of a TASK. --- @param MISSION Mission Group structure describing the Mission. --- @param CLIENT Client Group structure describing the Client. -function TASK:ShowGoalProgress( Mission, Client ) - self:F2() +--- Set the planned menu option of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @param #string MenuText The menu text. +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) + self:E( TaskGroup:GetName() ) - local GoalsText = "" - for GoalVerb, GoalVerbData in pairs( self.GoalTasks ) do - if Mission:IsCompleted() then - else - local Goals = self:GetGoalCompletion( GoalVerb ) - if Goals and Goals ~= "" then - else - Goals = "-" - end - GoalsText = GoalsText .. self:GetGoalProgress() - end - end - - if Mission.MissionReportFlash or Mission.MissionReportShow then - Client:Message( GoalsText, 10, "Mission Command: Task Status", 30, "Task status" ) - end + local TaskMission = self.Mission:GetName() + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + Mission.MenuCategory = Mission.MenuCategory or {} + local MenuCategory = Mission.MenuCategory + + Mission.MenuType = Mission.MenuType or {} + local MenuType = Mission.MenuType + + self.Menu = self.Menu or {} + local Menu = self.Menu + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + + MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} + MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) + + MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} + MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) + + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + end + Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) + + return self end ---- Sets a TASK to status Done. -function TASK:Done() - self:F2() - self.TaskDone = true +--- Set the assigned menu options of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) + self:E( TaskGroup:GetName() ) + + local TaskMission = self.Mission:GetName() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + self.MenuStatus = self.MenuStatus or {} + local MenuStatus = self.MenuStatus + + + self.MenuAbort = self.MenuAbort or {} + local MenuAbort = self.MenuAbort + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) + MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) + + return self end ---- Returns if a TASK is done. --- @return bool -function TASK:IsDone() - self:F2( self.TaskDone ) - return self.TaskDone -end +--- Remove the menu option of the @{Task} for a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenuForGroup( TaskGroup ) ---- Sets a TASK to status failed. -function TASK:Failed() - self:F() - self.TaskFailed = true -end + local TaskGroupName = TaskGroup:GetName() + + local Mission = self.Mission + local MenuMission = Mission.MenuMission + local MenuCategory = Mission.MenuCategory + local MenuType = Mission.MenuType + local MenuStatus = self.MenuStatus + local MenuAbort = self.MenuAbort + local Menu = self.Menu ---- Returns if a TASk has failed. --- @return bool -function TASK:IsFailed() - self:F2( self.TaskFailed ) - return self.TaskFailed -end + Menu = Menu or {} + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + Menu[TaskGroupName] = nil + end -function TASK:Reset( Mission, Client ) - self:F2() - self.ExecuteStage = _TransportExecuteStage.NONE -end + MenuType = MenuType or {} + if MenuType[TaskGroupName] then + for _, Menu in pairs( MenuType[TaskGroupName] ) do + Menu:Remove() + end + MenuType[TaskGroupName] = nil + end ---- Returns the Goals of a TASK --- @return @table Goals -function TASK:GetGoals() - return self.GoalTasks -end - ---- Returns if a TASK has Goal(s). --- @param #TASK self --- @param #string GoalVerb is the name of the Goal of the TASK. --- @return bool -function TASK:Goal( GoalVerb ) - self:F2( { GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - self:T2( {self.GoalTasks[GoalVerb] } ) - if self.GoalTasks[GoalVerb] and self.GoalTasks[GoalVerb].GoalTotal > 0 then - return true - else - return false - end -end - ---- Sets the total Goals to be achieved of the Goal Name --- @param number GoalTotal is the number of times the GoalVerb needs to be achieved. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. -function TASK:SetGoalTotal( GoalTotal, GoalVerb ) - self:F2( { GoalTotal, GoalVerb } ) - - if not GoalVerb then - GoalVerb = self.GoalVerb - end - self.GoalTasks[GoalVerb] = {} - self.GoalTasks[GoalVerb].Goals = {} - self.GoalTasks[GoalVerb].GoalTotal = GoalTotal - self.GoalTasks[GoalVerb].GoalCount = 0 - return self -end - ---- Gets the total of Goals to be achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. -function TASK:GetGoalTotal( GoalVerb ) - self:F2( { GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return self.GoalTasks[GoalVerb].GoalTotal - else - return 0 - end -end - ---- Sets the total of Goals currently achieved within the TASK of the GoalVerb. --- @param number GoalCount is the total number of Goals achieved within the TASK. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:SetGoalCount( GoalCount, GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb) then - self.GoalTasks[GoalVerb].GoalCount = GoalCount - end - return self -end - ---- Increments the total of Goals currently achieved within the TASK of the GoalVerb, with the given GoalCountIncrease. --- @param number GoalCountIncrease is the number of new Goals achieved within the TASK. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:IncreaseGoalCount( GoalCountIncrease, GoalVerb ) - self:F2( { GoalCountIncrease, GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb) then - self.GoalTasks[GoalVerb].GoalCount = self.GoalTasks[GoalVerb].GoalCount + GoalCountIncrease - end - return self -end - ---- Gets the total of Goals currently achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:GetGoalCount( GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return self.GoalTasks[GoalVerb].GoalCount - else - return 0 - end -end - ---- Gets the percentage of Goals currently achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:GetGoalPercentage( GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return math.floor( self:GetGoalCount( GoalVerb ) / self:GetGoalTotal( GoalVerb ) * 100 + .5 ) - else - return 100 - end -end - ---- Returns if all the Goals of the TASK were achieved. --- @return bool -function TASK:IsGoalReached() - self:F2() - - local GoalReached = true - - for GoalVerb, Goals in pairs( self.GoalTasks ) do - self:T2( { "GoalVerb", GoalVerb } ) - if self:Goal( GoalVerb ) then - local GoalToDo = self:GetGoalTotal( GoalVerb ) - self:GetGoalCount( GoalVerb ) - self:T2( "GoalToDo = " .. GoalToDo ) - if GoalToDo <= 0 then - else - GoalReached = false - break - end - else - break - end - end - - self:T( { GoalReached, self.GoalTasks } ) - return GoalReached -end - ---- Adds an Additional Goal for the TASK to be achieved. --- @param string GoalVerb is the name of the Goal of the TASK. --- @param string GoalTask is a text describing the Goal of the TASK to be achieved. --- @param number GoalIncrease is a number by which the Goal achievement is increasing. -function TASK:AddGoalCompletion( GoalVerb, GoalTask, GoalIncrease ) - self:F2( { GoalVerb, GoalTask, GoalIncrease } ) - - if self:Goal( GoalVerb ) then - self.GoalTasks[GoalVerb].Goals[#self.GoalTasks[GoalVerb].Goals+1] = GoalTask - self.GoalTasks[GoalVerb].GoalCount = self.GoalTasks[GoalVerb].GoalCount + GoalIncrease - end - return self -end - ---- Returns if the additional Goal for the TASK was completed. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return string Goals -function TASK:GetGoalCompletion( GoalVerb ) - self:F2( { GoalVerb } ) - - if self:Goal( GoalVerb ) then - local Goals = "" - for GoalID, GoalName in pairs( self.GoalTasks[GoalVerb].Goals ) do Goals = Goals .. GoalName .. " + " end - return Goals:gsub(" + $", ""), self.GoalTasks[GoalVerb].GoalCount - end -end - -function TASK.MenuAction( Parameter ) - Parameter.ReferenceTask.ExecuteStage = _TransportExecuteStage.EXECUTING - Parameter.ReferenceTask.Cargo = Parameter.CargoTask -end - -function TASK:StageExecute() - self:F() - - local Execute = false - - if self.Frequency == STAGE.FREQUENCY.REPEAT then - Execute = true - elseif self.Frequency == STAGE.FREQUENCY.NONE then - Execute = false - elseif self.Frequency >= 0 then - Execute = true - self.Frequency = self.Frequency - 1 + MenuCategory = MenuCategory or {} + if MenuCategory[TaskGroupName] then + for _, Menu in pairs( MenuCategory[TaskGroupName] ) do + Menu:Remove() + end + MenuCategory[TaskGroupName] = nil end - return Execute - -end - ---- Work function to set signal events within a TASK. -function TASK:AddSignal( SignalUnitNames, SignalType, SignalColor, SignalHeight ) - self:F() + MenuStatus = MenuStatus or {} + if MenuStatus[TaskGroupName] then + MenuStatus[TaskGroupName]:Remove() + MenuStatus[TaskGroupName] = nil + end - local Valid = true - - if Valid then - if type( SignalUnitNames ) == "table" then - self.LandingZoneSignalUnitNames = SignalUnitNames - else - self.LandingZoneSignalUnitNames = { SignalUnitNames } - end - self.LandingZoneSignalType = SignalType - self.LandingZoneSignalColor = SignalColor - self.Signalled = false - if SignalHeight ~= nil then - self.LandingZoneSignalHeight = SignalHeight - else - self.LandingZoneSignalHeight = 0 - end - - if self.TaskBriefing then - self.TaskBriefing = self.TaskBriefing .. " " .. SignalColor.TEXT .. " " .. SignalType.TEXT .. " will be fired when entering the landing zone." - end - end - - return Valid + MenuAbort = MenuAbort or {} + if MenuAbort[TaskGroupName] then + MenuAbort[TaskGroupName]:Remove() + MenuAbort[TaskGroupName] = nil + end + end ---- When the CLIENT is approaching the landing zone, a RED SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeRed( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.RED, SignalHeight ) +function TASK_BASE.MenuAssignToGroup( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, a GREEN SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeGreen( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.GREEN, SignalHeight ) -end - ---- When the CLIENT is approaching the landing zone, a BLUE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeBlue( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.BLUE, SignalHeight ) +function TASK_BASE.MenuTaskStatus( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, a WHITE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeWhite( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.WHITE, SignalHeight ) +function TASK_BASE.MenuTaskAbort( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, an ORANGE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeOrange( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.ORANGE, SignalHeight ) + + +--- Returns the @{Task} name. +-- @param #TASK_BASE self +-- @return #string TaskName +function TASK_BASE:GetTaskName() + return self.TaskName end ---- When the CLIENT is approaching the landing zone, a RED FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareRed( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.RED, SignalHeight ) + +--- Add Process to @{Task} with key @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddProcess( TaskUnit, Process ) + local TaskUnitName = TaskUnit:GetName() + self.Processes = self.Processes or {} + self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} + self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process + return Process end ---- When the CLIENT is approaching the landing zone, a GREEN FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareGreen( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.GREEN, SignalHeight ) -end - ---- When the CLIENT is approaching the landing zone, a BLUE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareBlue( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.BLUE, SignalHeight ) + +--- Remove Processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + Process:StopEvents() + Process = nil + self.Processes[TaskUnitName][ProcessID] = nil + self:E( self.Processes[TaskUnitName][ProcessID] ) + end + self.Processes[TaskUnitName] = nil end ---- When the CLIENT is approaching the landing zone, a WHITE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareWhite( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.WHITE, SignalHeight ) +--- Fail processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:FailProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + self:E( { "Failing process: ", Process } ) + Process.Fsm:Fail() + end end ---- When the CLIENT is approaching the landing zone, an ORANGE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareOrange( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.ORANGE, SignalHeight ) +--- Add a FiniteStateMachine to @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) + local TaskUnitName = TaskUnit:GetName() + self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} + self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm + return Fsm end + +--- Remove FiniteStateMachines from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveStateMachines( TaskUnitName ) + + for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do + Fsm = nil + self.Fsm[TaskUnitName][_] = nil + self:E( self.Fsm[TaskUnitName][_] ) + end + self.Fsm[TaskUnitName] = nil +end + +--- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:HasStateMachine( TaskUnitName ) + + self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) + return ( self.Fsm[TaskUnitName] ~= nil ) +end + + + + + +--- Register a potential new assignment for a new spawned @{Unit}. +-- Tasks only get assigned if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventAssignUnit( Event ) + if Event.IniUnit then + self:F( Event ) + local TaskUnit = Event.IniUnit + if TaskUnit:IsAlive() then + local TaskPlayerName = TaskUnit:GetPlayerName() + if TaskPlayerName ~= nil then + if not self:HasStateMachine( TaskUnit ) then + -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. + local TaskGroup = TaskUnit:GetGroup() + if self:IsAssignedToGroup( TaskGroup ) then + self:AssignToUnit( TaskUnit ) + end + end + end + end + end + return nil +end + +--- Catches the "player leave unit" event for a @{Unit} .... +-- When a player is an air unit, and leaves the unit: +-- +-- * and he is not at an airbase runway on the ground, he will fail its task. +-- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. +-- This is important to model the change from plane types for a player during mission assignment. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventPlayerLeaveUnit( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + if TaskUnit:IsAir() then + if TaskUnit:IsAboveRunway() then + -- do nothing + else + self:E( "IsNotAboveRunway" ) + -- Player left airplane during an assigned task and was not at an airbase. + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + end + end + + end + return nil +end + +--- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... +-- There are only assignments if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventDead( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + + local TaskGroup = Event.IniUnit:GetGroup() + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + end + return nil +end + +--- Gets the Scoring of the task +-- @param #TASK_BASE self +-- @return Scoring#SCORING Scoring +function TASK_BASE:GetScoring() + return self.Mission:GetScoring() +end + + +--- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. +-- @param #TASK_BASE self +-- @return #string The Task ID +function TASK_BASE:GetTaskIndex() + + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + local TaskName = self:GetName() + + return TaskCategory .. "." ..TaskType .. "." .. TaskName +end + +--- Sets the Name of the Task +-- @param #TASK_BASE self +-- @param #string TaskName +function TASK_BASE:SetName( TaskName ) + self.TaskName = TaskName +end + +--- Gets the Name of the Task +-- @param #TASK_BASE self +-- @return #string The Task Name +function TASK_BASE:GetName() + return self.TaskName +end + +--- Sets the Type of the Task +-- @param #TASK_BASE self +-- @param #string TaskType +function TASK_BASE:SetType( TaskType ) + self.TaskType = TaskType +end + +--- Gets the Type of the Task +-- @param #TASK_BASE self +-- @return #string TaskType +function TASK_BASE:GetType() + return self.TaskType +end + +--- Sets the Category of the Task +-- @param #TASK_BASE self +-- @param #string TaskCategory +function TASK_BASE:SetCategory( TaskCategory ) + self.TaskCategory = TaskCategory +end + +--- Gets the Category of the Task +-- @param #TASK_BASE self +-- @return #string TaskCategory +function TASK_BASE:GetCategory() + return self.TaskCategory +end + +--- Sets the ID of the Task +-- @param #TASK_BASE self +-- @param #string TaskID +function TASK_BASE:SetID( TaskID ) + self.TaskID = TaskID +end + +--- Gets the ID of the Task +-- @param #TASK_BASE self +-- @return #string TaskID +function TASK_BASE:GetID() + return self.TaskID +end + + +--- Sets a @{Task} to status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:StateSuccess() + self:SetState( self, "State", "Success" ) + return self +end + +--- Is the @{Task} status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateSuccess() + return self:GetStateString() == "Success" +end + +--- Sets a @{Task} to status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:StateFailed() + self:SetState( self, "State", "Failed" ) + return self +end + +--- Is the @{Task} status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateFailed() + return self:GetStateString() == "Failed" +end + +--- Sets a @{Task} to status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:StatePlanned() + self:SetState( self, "State", "Planned" ) + return self +end + +--- Is the @{Task} status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStatePlanned() + return self:GetStateString() == "Planned" +end + +--- Sets a @{Task} to status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:StateAssigned() + self:SetState( self, "State", "Assigned" ) + return self +end + +--- Is the @{Task} status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateAssigned() + return self:GetStateString() == "Assigned" +end + +--- Sets a @{Task} to status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:StateHold() + self:SetState( self, "State", "Hold" ) + return self +end + +--- Is the @{Task} status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateHold() + return self:GetStateString() == "Hold" +end + +--- Sets a @{Task} to status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:StateReplanned() + self:SetState( self, "State", "Replanned" ) + return self +end + +--- Is the @{Task} status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateReplanned() + return self:GetStateString() == "Replanned" +end + +--- Gets the @{Task} status. +-- @param #TASK_BASE self +function TASK_BASE:GetStateString() + return self:GetState( self, "State" ) +end + +--- Sets a @{Task} briefing. +-- @param #TASK_BASE self +-- @param #string TaskBriefing +-- @return #TASK_BASE self +function TASK_BASE:SetBriefing( TaskBriefing ) + self.TaskBriefing = TaskBriefing + return self +end + + + +--- Adds a score for the TASK to be achieved. +-- @param #TASK_BASE self +-- @param #string TaskStatus is the status of the TASK when the score needs to be given. +-- @param #string ScoreText is a text describing the score that is given according the status. +-- @param #number Score is a number providing the score of the status. +-- @return #TASK_BASE self +function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) + self:F2( { TaskStatus, ScoreText, Score } ) + + self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} + self.Scores[TaskStatus].ScoreText = ScoreText + self.Scores[TaskStatus].Score = Score + return self +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) + + self:E("Assigned") + + local TaskGroup = TaskUnit:GetGroup() + + TaskGroup:Message( self.TaskBriefing, 20 ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + +end + + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) + + self:E("Success") + + self:UnAssignFromGroups() + + local TaskGroup = TaskUnit:GetGroup() + self.Mission:SetPlannedMenu() + + self:StateSuccess() + + -- The task has become successful, the event catchers can be cleaned. + self:CleanUp() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) + + self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) + + -- A task cannot be "failed", so a task will always be there waiting for players to join. + -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. + -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. + + self:UnAssignFromGroups() + self:StatePlanned() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) + + if self:IsTrace() then + MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() + end + + self:E( { Event, From, To } ) + self:SetState( self, "State", To ) + + if self.Scores[To] then + local Scoring = self:GetScoring() + if Scoring then + Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + +end + + +--- @param #TASK_BASE self +function TASK_BASE:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self +end + + +--- @param #TASK_BASE self +function TASK_BASE._Scheduler() + self:F2() + + return true +end + + + + --- A GOHOMETASK orchestrates the travel back to the home base, which is a specific zone defined within the ME. -- @module GOHOMETASK @@ -16964,13 +18844,17 @@ end -- @type MISSION -- @extends Base#BASE -- @field #MISSION.Clients _Clients +-- @field Menu#MENU_COALITION MissionMenu -- @field #string MissionBriefing MISSION = { ClassName = "MISSION", Name = "", MissionStatus = "PENDING", _Clients = {}, - _Tasks = {}, + Tasks = {}, + TaskMenus = {}, + TaskCategoryMenus = {}, + TaskTypeMenus = {}, _ActiveTasks = {}, GoalFunction = nil, MissionReportTrigger = 0, @@ -16991,49 +18875,177 @@ MISSION = { function MISSION:Meta() local self = BASE:Inherit( self, BASE:New() ) - self:F() return self end --- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. --- @param string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param string MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... --- @return MISSION --- @usage --- -- Declare a few missions. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Patriots', 'Primary', 'Our intelligence reports that 3 Patriot SAM defense batteries are located near Ruisi, Kvarhiti and Gori.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Package Delivery', 'Operational', 'In order to be in full control of the situation, we need you to deliver a very important package at a secret location. Fly undetected through the NATO defenses and deliver the secret package. The secret agent is located at waypoint 4.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Rescue General', 'Tactical', 'Our intelligence has received a remote signal behind Gori. We believe it is a very important Russian General that was captured by Georgia. Go out there and rescue him! Ensure you stay out of the battle zone, keep south. Waypoint 4 is the location of our Russian General.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'SA-6 SAMs', 'Primary', 'Our intelligence reports that 3 SA-6 SAM defense batteries are located near Didmukha, Khetagurov and Berula. Eliminate the Russian SAMs.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Sling Load', 'Operational', 'Fly to the cargo pickup zone at Dzegvi or Kaspi, and sling the cargo to Soganlug airbase.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Rescue secret agent', 'Tactical', 'In order to be in full control of the situation, we need you to rescue a secret agent from the woods behind enemy lines. Avoid the Russian defenses and rescue the agent. Keep south until Khasuri, and keep your eyes open for any SAM presence. The agent is located at waypoint 4 on your kneeboard.', 'NATO' ) +-- @param #MISSION self +-- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. +-- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. +-- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. +-- @param DCSCoalitionObject#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... +-- @return #MISSION self function MISSION:New( MissionName, MissionPriority, MissionBriefing, MissionCoalition ) self = MISSION:Meta() - self:T({ MissionName, MissionPriority, MissionBriefing, MissionCoalition }) + self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - local Valid = true - - Valid = routines.ValidateString( MissionName, "MissionName", Valid ) - Valid = routines.ValidateString( MissionPriority, "MissionPriority", Valid ) - Valid = routines.ValidateString( MissionBriefing, "MissionBriefing", Valid ) - Valid = routines.ValidateString( MissionCoalition, "MissionCoalition", Valid ) - - if Valid then - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - end + self.Name = MissionName + self.MissionPriority = MissionPriority + self.MissionBriefing = MissionBriefing + self.MissionCoalition = MissionCoalition return self end +--- Gets the mission name. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:GetName() + return self.Name +end + +--- Add a scoring to the mission. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:AddScoring( Scoring ) + self.Scoring = Scoring + return self +end + +--- Get the scoring object of a mission. +-- @param #MISSION self +-- @return #SCORING Scoring +function MISSION:GetScoring() + return self.Scoring +end + + +--- Sets the Planned Task menu. +-- @param #MISSION self +function MISSION:SetPlannedMenu() + + for _, Task in pairs( self.Tasks ) do + local Task = Task -- Task#TASK_BASE + Task:RemoveMenu() + Task:SetPlannedMenu() + end + +end + +--- Sets the Assigned Task menu. +-- @param #MISSION self +-- @param Task#TASK_BASE Task +-- @param #string MenuText The menu text. +-- @return #MISSION self +function MISSION:SetAssignedMenu( Task ) + + for _, Task in pairs( self.Tasks ) do + local Task = Task -- Task#TASK_BASE + Task:RemoveMenu() + Task:SetAssignedMenu() + end + +end + +--- Removes a Task menu. +-- @param #MISSION self +-- @param Task#TASK_BASE Task +-- @return #MISSION self +function MISSION:RemoveTaskMenu( Task ) + + Task:RemoveMenu() +end + + +--- Gets the mission menu for the coalition. +-- @param #MISSION self +-- @param Group#GROUP TaskGroup +-- @return Menu#MENU_COALITION self +function MISSION:GetMissionMenu( TaskGroup ) + local TaskGroupName = TaskGroup:GetName() + return self.MenuMission[TaskGroupName] +end + + +--- Clears the mission menu for the coalition. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:ClearMissionMenu() + self.MissionMenu:Remove() + self.MissionMenu = nil +end + +--- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. +-- @param #string TaskIndex is the Index of the @{Task} within the @{Mission}. +-- @param #number TaskID is the ID of the @{Task} within the @{Mission}. +-- @return Task#TASK_BASE The Task +-- @return #nil Returns nil if no task was found. +function MISSION:GetTask( TaskName ) + self:F( { TaskName } ) + + return self.Tasks[TaskName] +end + + +--- Register a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return Task#TASK_BASE The task added. +function MISSION:AddTask( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName] = Task + + return Task +end + +--- Removes a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return #nil The cleaned Task reference. +function MISSION:RemoveTask( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + Task:CleanUp() -- Cleans all events and sets task to nil to get Garbage Collected + + -- Ensure everything gets garbarge collected. + self.Tasks[TaskName] = nil + Task = nil + + return nil +end + +--- Return the next @{Task} ID to be completed within the @{Mission}. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return Task#TASK_BASE The task added. +function MISSION:GetNextTaskID( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 + + return self.Tasks[TaskName].n +end + + + +--- old stuff + --- Returns if a Mission has completed. -- @return bool function MISSION:IsCompleted() @@ -17222,66 +19234,6 @@ function MISSION:FindClient( ClientName ) end ---- Register a @{TASK} to be completed within the @{MISSION}. Note that there can be multiple @{TASK}s registered to be completed. Each TASK can be set a certain Goal. The MISSION will not be completed until all Goals are reached. --- @param TASK Task is the @{TASK} object. The object must have been instantiated with @{TASK:New} or any of its inherited @{TASK}s. --- @param number TaskNumber is the sequence number of the TASK within the MISSION. This number does have to be chronological. --- @return TASK --- @usage --- -- Define a few tasks for the Mission. --- PickupZones = { "NATO Gold Pickup Zone", "NATO Titan Pickup Zone" } --- PickupSignalUnits = { "NATO Gold Coordination Center", "NATO Titan Coordination Center" } --- --- -- Assign the Pickup Task --- local PickupTask = PICKUPTASK:New( PickupZones, CARGO_TYPE.ENGINEERS, CLIENT.ONBOARDSIDE.LEFT ) --- PickupTask:AddSmokeBlue( PickupSignalUnits ) --- PickupTask:SetGoalTotal( 3 ) --- Mission:AddTask( PickupTask, 1 ) --- --- -- Assign the Deploy Task --- local PatriotActivationZones = { "US Patriot Battery 1 Activation", "US Patriot Battery 2 Activation", "US Patriot Battery 3 Activation" } --- local PatriotActivationZonesSmokeUnits = { "US SAM Patriot - Battery 1 Control", "US SAM Patriot - Battery 2 Control", "US SAM Patriot - Battery 3 Control" } --- local DeployTask = DEPLOYTASK:New( PatriotActivationZones, CARGO_TYPE.ENGINEERS ) --- --DeployTask:SetCargoTargetZoneName( 'US Troops Attack ' .. math.random(2) ) --- DeployTask:AddSmokeBlue( PatriotActivationZonesSmokeUnits ) --- DeployTask:SetGoalTotal( 3 ) --- DeployTask:SetGoalTotal( 3, "Patriots activated" ) --- Mission:AddTask( DeployTask, 2 ) - -function MISSION:AddTask( Task, TaskNumber ) - self:F() - - self._Tasks[TaskNumber] = Task - self._Tasks[TaskNumber]:EnableEvents() - self._Tasks[TaskNumber].ID = TaskNumber - - return Task - end - ---- Get the TASK idenified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param number TaskNumber is the number of the @{TASK} within the @{MISSION}. --- @return TASK --- @usage --- -- Get Task 2 from the Mission. --- Task2 = Mission:GetTask( 2 ) - -function MISSION:GetTask( TaskNumber ) - self:F() - - local Valid = true - - local Task = nil - - if type(TaskNumber) ~= "number" then - Valid = false - end - - if Valid then - Task = self._Tasks[TaskNumber] - end - - return Task -end - --- Get all the TASKs from the Mission. This function is useful in GoalFunctions. -- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. -- @usage @@ -18509,7 +20461,7 @@ function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex ) if SpawnTemplate then - local UnitPoint = HostUnit:GetPointVec2() + local UnitPoint = HostUnit:GetVec2() self:T( { "Current point of ", self.SpawnTemplatePrefix, UnitPoint } ) @@ -18592,7 +20544,7 @@ function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex ) if ZoneRandomize == true then ZonePoint = Zone:GetRandomVec2() else - ZonePoint = Zone:GetPointVec2() + ZonePoint = Zone:GetVec2() end SpawnTemplate.route.points[1].x = ZonePoint.x @@ -20133,7 +22085,7 @@ function ESCORT._HoldPosition( MenuParam ) PointFrom.alt = GroupPoint.y PointFrom.alt_type = AI.Task.AltitudeType.BARO - local OrbitPoint = OrbitUnit:GetPointVec2() + local OrbitPoint = OrbitUnit:GetVec2() local PointTo = {} PointTo.x = OrbitPoint.x PointTo.y = OrbitPoint.y @@ -20326,7 +22278,7 @@ function ESCORT._AttackTarget( MenuParam ) SCHEDULER:New( EscortGroup, EscortGroup.PushTask, { EscortGroup:TaskCombo( - { EscortGroup:TaskFireAtPoint( AttackUnit:GetPointVec2(), 50 ) + { EscortGroup:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) } ) }, 10 @@ -20366,7 +22318,7 @@ function ESCORT._AssistTarget( MenuParam ) SCHEDULER:New( EscortGroupAttack, EscortGroupAttack.PushTask, { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetPointVec2(), 50 ) + { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) } ) }, 10 @@ -21569,7 +23521,7 @@ function PATROLZONE:NewPatrolRoute() -- If not, make a waypoint within the to that the PatrolGroup will fly at maximum speed to that point. -- --- Calculate the current route point. --- local CurrentVec2 = self.PatrolGroup:GetPointVec2() +-- local CurrentVec2 = self.PatrolGroup:GetVec2() -- local CurrentAltitude = self.PatrolGroup:GetUnit(1):GetAltitude() -- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) -- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( @@ -21848,7 +23800,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetPointVec2(), self.ReturnTresholdRange ) + local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) self:E( RangeZone ) @@ -21878,7 +23830,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() else -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetPointVec2().x, AIGroup:GetPointVec2().y ) + local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) self:T( ClosestAirbase.AirbaseName ) AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) @@ -22093,6 +24045,7 @@ function AIRBASEPOLICE_BASE:_AirbaseMonitor() self:SetState( self, "Taxi", true ) end + -- TODO: GetVelocityKMH function usage local VelocityVec3 = Client:GetVelocity() local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec local Velocity = Velocity * 3.6 -- now it is in km/h. @@ -23133,6 +25086,7 @@ end -- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} -- ========================================================== -- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. +-- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). -- -- 1.1) DETECTION_BASE constructor -- ------------------------------- @@ -23161,16 +25115,16 @@ end -- -- === -- --- 2) @{Detection#DETECTION_UNITGROUPS} class, extends @{Detection#DETECTION_BASE} +-- 2) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} -- =============================================================================== --- The @{Detection#DETECTION_UNITGROUPS} class will detect units within the battle zone for a FAC group, +-- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), -- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. -- -- 2.1) Retrieve the Detected Unit sets and Detected Zones -- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_UNITGROUPS}. +-- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. -- -- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. -- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). @@ -23182,53 +25136,59 @@ end -- -- 1.4) Flare or Smoke detected units -- ---------------------------------- --- Use the methods @{Detection#DETECTION_UNITGROUPS.FlareDetectedUnits}() or @{Detection#DETECTION_UNITGROUPS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. +-- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. -- -- 1.5) Flare or Smoke detected zones -- ---------------------------------- --- Use the methods @{Detection#DETECTION_UNITGROUPS.FlareDetectedZones}() or @{Detection#DETECTION_UNITGROUPS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. +-- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. -- -- === -- +-- ### Contributions: Mechanic - Concept & Testing +-- ### Authors: FlightControl : Design & Programming +-- -- @module Detection --- @author Mechanic : Concept & Testing --- @author FlightControl : Design & Programming --- DETECTION_BASE class -- @type DETECTION_BASE --- @field Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +-- @field Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @field DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @field #DETECTION_BASE.DetectedSets DetectedSets A list of @{Set#SET_BASE}s containing the objects in each set that were detected. The base class will not build the detected sets, but will leave that to the derived classes. +-- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. +-- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. +-- @field #number DetectionRun -- @extends Base#BASE DETECTION_BASE = { ClassName = "DETECTION_BASE", - DetectedSets = {}, - DetectedObjects = {}, - FACGroup = nil, + DetectionSetGroup = nil, DetectionRange = nil, + DetectedObjects = {}, + DetectionRun = 0, + DetectedObjectsIdentified = {}, } ---- @type DETECTION_BASE.DetectedSets --- @list - - ---- @type DETECTION_BASE.DetectedZones --- @list +--- @type DETECTION_BASE.DetectedObjects +-- @list <#DETECTION_BASE.DetectedObject> +--- @type DETECTION_BASE.DetectedObject +-- @field #string Name +-- @field #boolean Visible +-- @field #string Type +-- @field #number Distance +-- @field #boolean Identified --- DETECTION constructor. -- @param #DETECTION_BASE self --- @param Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +-- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @return #DETECTION_BASE self -function DETECTION_BASE:New( FACGroup, DetectionRange ) +function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) - self.FACGroup = FACGroup + self.DetectionSetGroup = DetectionSetGroup self.DetectionRange = DetectionRange self:InitDetectVisual( false ) @@ -23300,13 +25260,65 @@ function DETECTION_BASE:InitDetectDLINK( DetectDLINK ) self.DetectDLINK = DetectDLINK end ---- Gets the FAC group. +--- Determines if a detected object has already been identified during detection processing. -- @param #DETECTION_BASE self --- @return Group#GROUP self -function DETECTION_BASE:GetFACGroup() - self:F2() +-- @param #DETECTION_BASE.DetectedObject DetectedObject +-- @return #boolean true if already identified. +function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) + self:F3( DetectedObject.Name ) - return self.FACGroup + local DetectedObjectName = DetectedObject.Name + local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true + self:T3( DetectedObjectIdentified ) + return DetectedObjectIdentified +end + +--- Identifies a detected object during detection processing. +-- @param #DETECTION_BASE self +-- @param #DETECTION_BASE.DetectedObject DetectedObject +function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) + self:F( DetectedObject.Name ) + + local DetectedObjectName = DetectedObject.Name + self.DetectedObjectsIdentified[DetectedObjectName] = true +end + +--- UnIdentify a detected object during detection processing. +-- @param #DETECTION_BASE self +-- @param #DETECTION_BASE.DetectedObject DetectedObject +function DETECTION_BASE:UnIdentifyDetectedObject( DetectedObject ) + + local DetectedObjectName = DetectedObject.Name + self.DetectedObjectsIdentified[DetectedObjectName] = false +end + +--- UnIdentify all detected objects during detection processing. +-- @param #DETECTION_BASE self +function DETECTION_BASE:UnIdentifyAllDetectedObjects() + + self.DetectedObjectsIdentified = {} -- Table will be garbage collected. +end + +--- Gets a detected object with a given name. +-- @param #DETECTION_BASE self +-- @param #string ObjectName +-- @return #DETECTION_BASE.DetectedObject +function DETECTION_BASE:GetDetectedObject( ObjectName ) + self:F3( ObjectName ) + + if ObjectName then + local DetectedObject = self.DetectedObjects[ObjectName] + + -- Only return detected objects that are alive! + local DetectedUnit = UNIT:FindByName( ObjectName ) + if DetectedUnit and DetectedUnit:IsAlive() then + if self:IsDetectedObjectIdentified( DetectedObject ) == false then + return DetectedObject + end + end + end + + return nil end --- Get the detected @{Set#SET_BASE}s. @@ -23341,6 +25353,14 @@ function DETECTION_BASE:GetDetectedSet( Index ) return nil end +--- Get the detection Groups. +-- @param #DETECTION_BASE self +-- @return Group#GROUP +function DETECTION_BASE:GetDetectionSetGroup() + + local DetectionSetGroup = self.DetectionSetGroup + return DetectionSetGroup +end --- Make a DetectionSet table. This function will be overridden in the derived clsses. -- @param #DETECTION_BASE self @@ -23352,6 +25372,7 @@ function DETECTION_BASE:CreateDetectionSets() end + --- Schedule the DETECTION construction. -- @param #DETECTION_BASE self -- @param #number DelayTime The delay in seconds to wait the reporting. @@ -23373,141 +25394,195 @@ end function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:F2( { SchedulerName } ) - self.DetectedObjects = {} - self.DetectedSets = {} - self.DetectedZones = {} + self.DetectionRun = self.DetectionRun + 1 - if self.FACGroup:IsAlive() then - local FACGroupName = self.FACGroup:GetName() - - local FACDetectedTargets = self.FACGroup:GetDetectedTargets( - self.DetectVisual, - self.DetectOptical, - self.DetectRadar, - self.DetectIRST, - self.DetectRWR, - self.DetectDLINK - ) - - for FACDetectedTargetID, FACDetectedTarget in pairs( FACDetectedTargets ) do - local FACObject = FACDetectedTarget.object -- DCSObject#Object - self:T2( FACObject ) + self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table + + for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do + local DetectionGroup = DetectionGroupData -- Group#GROUP + + if DetectionGroup:IsAlive() then + + local DetectionGroupName = DetectionGroup:GetName() - if FACObject and FACObject:isExist() and FACObject.id_ < 50000000 then - - local FACDetectedObjectName = FACObject:getName() - - local FACDetectedObjectPositionVec3 = FACObject:getPoint() - local FACGroupPositionVec3 = self.FACGroup:GetPointVec3() - - local Distance = ( ( FACDetectedObjectPositionVec3.x - FACGroupPositionVec3.x )^2 + - ( FACDetectedObjectPositionVec3.y - FACGroupPositionVec3.y )^2 + - ( FACDetectedObjectPositionVec3.z - FACGroupPositionVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T( { FACGroupName, FACDetectedObjectName, Distance } ) - - if Distance <= self.DetectionRange then - - if not self.DetectedObjects[FACDetectedObjectName] then - self.DetectedObjects[FACDetectedObjectName] = {} - end - self.DetectedObjects[FACDetectedObjectName].Name = FACDetectedObjectName - self.DetectedObjects[FACDetectedObjectName].Visible = FACDetectedTarget.visible - self.DetectedObjects[FACDetectedObjectName].Type = FACDetectedTarget.type - self.DetectedObjects[FACDetectedObjectName].Distance = FACDetectedTarget.distance - else - -- if beyond the DetectionRange then nullify... - if self.DetectedObjects[FACDetectedObjectName] then - self.DetectedObjects[FACDetectedObjectName] = nil + local DetectionDetectedTargets = DetectionGroup:GetDetectedTargets( + self.DetectVisual, + self.DetectOptical, + self.DetectRadar, + self.DetectIRST, + self.DetectRWR, + self.DetectDLINK + ) + + for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do + local DetectionObject = DetectionDetectedTarget.object -- DCSObject#Object + self:T2( DetectionObject ) + + if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then + + local DetectionDetectedObjectName = DetectionObject:getName() + + local DetectionDetectedObjectPositionVec3 = DetectionObject:getPoint() + local DetectionGroupPositionVec3 = DetectionGroup:GetPointVec3() + + local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupPositionVec3.x )^2 + + ( DetectionDetectedObjectPositionVec3.y - DetectionGroupPositionVec3.y )^2 + + ( DetectionDetectedObjectPositionVec3.z - DetectionGroupPositionVec3.z )^2 + ) ^ 0.5 / 1000 + + self:T2( { DetectionGroupName, DetectionDetectedObjectName, Distance } ) + + if Distance <= self.DetectionRange then + + if not self.DetectedObjects[DetectionDetectedObjectName] then + self.DetectedObjects[DetectionDetectedObjectName] = {} + end + self.DetectedObjects[DetectionDetectedObjectName].Name = DetectionDetectedObjectName + self.DetectedObjects[DetectionDetectedObjectName].Visible = DetectionDetectedTarget.visible + self.DetectedObjects[DetectionDetectedObjectName].Type = DetectionDetectedTarget.type + self.DetectedObjects[DetectionDetectedObjectName].Distance = DetectionDetectedTarget.distance + else + -- if beyond the DetectionRange then nullify... + if self.DetectedObjects[DetectionDetectedObjectName] then + self.DetectedObjects[DetectionDetectedObjectName] = nil + end end end end + + self:T2( self.DetectedObjects ) + + -- okay, now we have a list of detected object names ... + -- Sort the table based on distance ... + table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) end - - self:T2( self.DetectedObjects ) - - -- okay, now we have a list of detected object names ... - -- Sort the table based on distance ... - self:T( { "Sorting DetectedObjects table:", self.DetectedObjects } ) - table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) - self:T( { "Sorted Targets Table:", self.DetectedObjects } ) - - -- Now group the DetectedObjects table into SET_BASEs, evaluating the DetectionZoneRange. - - if self.DetectedObjects then - self:CreateDetectionSets() - end - - end + + if self.DetectedObjects then + self:CreateDetectionSets() + end + + return true end ---- @type DETECTION_UNITGROUPS.DetectedSets --- @list --- - - ---- @type DETECTION_UNITGROUPS.DetectedZones --- @list --- ---- DETECTION_UNITGROUPS class --- @type DETECTION_UNITGROUPS --- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @field #DETECTION_UNITGROUPS.DetectedSets DetectedSets A list of @{Set#SET_UNIT}s containing the units in each set that were detected within a DetectionZoneRange. --- @field #DETECTION_UNITGROUPS.DetectedZones DetectedZones A list of @{Zone#ZONE_UNIT}s containing the zones of the reference detected units. +--- DETECTION_AREAS class +-- @type DETECTION_AREAS +-- @field DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @field #DETECTION_AREAS.DetectedAreas DetectedAreas A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. -- @extends Detection#DETECTION_BASE -DETECTION_UNITGROUPS = { - ClassName = "DETECTION_UNITGROUPS", - DetectedZones = {}, +DETECTION_AREAS = { + ClassName = "DETECTION_AREAS", + DetectedAreas = { n = 0 }, + DetectionZoneRange = nil, } +--- @type DETECTION_AREAS.DetectedAreas +-- @list <#DETECTION_AREAS.DetectedArea> + +--- @type DETECTION_AREAS.DetectedArea +-- @field Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @field Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @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 #number AreaID -- The identifier of the detected area. +-- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. ---- DETECTION_UNITGROUPS constructor. --- @param Detection#DETECTION_UNITGROUPS self --- @param Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +--- DETECTION_AREAS constructor. +-- @param Detection#DETECTION_AREAS self +-- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Detection#DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:New( FACGroup, DetectionRange, DetectionZoneRange ) +-- @return Detection#DETECTION_AREAS self +function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( FACGroup, DetectionRange ) ) + local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) ) + self.DetectionZoneRange = DetectionZoneRange - self:Schedule( 10, 30 ) + self._SmokeDetectedUnits = false + self._FlareDetectedUnits = false + self._SmokeDetectedZones = false + self._FlareDetectedZones = false + + self:Schedule( 0, 15 ) return self end ---- Get the detected @{Zone#ZONE_UNIT}s. --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS.DetectedZones DetectedZones -function DETECTION_UNITGROUPS:GetDetectedZones() - - local DetectedZones = self.DetectedZones - return DetectedZones +--- Add a detected @{#DETECTION_AREAS.DetectedArea}. +-- @param Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @param Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @return #DETECTION_AREAS.DetectedArea DetectedArea +function DETECTION_AREAS:AddDetectedArea( Set, Zone ) + local DetectedAreas = self:GetDetectedAreas() + DetectedAreas.n = self:GetDetectedAreaCount() + 1 + DetectedAreas[DetectedAreas.n] = {} + local DetectedArea = DetectedAreas[DetectedAreas.n] + DetectedArea.Set = Set + DetectedArea.Zone = Zone + DetectedArea.Removed = false + DetectedArea.AreaID = DetectedAreas.n + + return DetectedArea end ---- Get the amount of @{Zone#ZONE_UNIT}s with detected units. --- @param #DETECTION_UNITGROUPS self --- @return #number Count -function DETECTION_UNITGROUPS:GetDetectedZoneCount() - - local DetectedZoneCount = #self.DetectedZones - return DetectedZoneCount +--- Remove a detected @{#DETECTION_AREAS.DetectedArea} with a given Index. +-- @param #DETECTION_AREAS self +-- @param #number Index The Index of the detection are to be removed. +-- @return #nil +function DETECTION_AREAS:RemoveDetectedArea( Index ) + local DetectedAreas = self:GetDetectedAreas() + local DetectedAreaCount = self:GetDetectedAreaCount() + local DetectedArea = DetectedAreas[Index] + local DetectedAreaSet = DetectedArea.Set + DetectedArea[Index] = nil + return nil end ---- Get a SET of detected objects using a given numeric index. --- @param #DETECTION_UNITGROUPS self + +--- Get the detected @{#DETECTION_AREAS.DetectedAreas}. +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS.DetectedAreas DetectedAreas +function DETECTION_AREAS:GetDetectedAreas() + + local DetectedAreas = self.DetectedAreas + return DetectedAreas +end + +--- Get the amount of @{#DETECTION_AREAS.DetectedAreas}. +-- @param #DETECTION_AREAS self +-- @return #number DetectedAreaCount +function DETECTION_AREAS:GetDetectedAreaCount() + + local DetectedAreaCount = self.DetectedAreas.n + return DetectedAreaCount +end + +--- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. +-- @param #DETECTION_AREAS self -- @param #number Index --- @return Zone#ZONE_UNIT -function DETECTION_UNITGROUPS:GetDetectedZone( Index ) +-- @return Set#SET_UNIT DetectedSet +function DETECTION_AREAS:GetDetectedSet( Index ) - local DetectedZone = self.DetectedZones[Index] + local DetectedSetUnit = self.DetectedAreas[Index].Set + if DetectedSetUnit then + return DetectedSetUnit + end + + return nil +end + +--- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. +-- @param #DETECTION_AREAS self +-- @param #number Index +-- @return Zone#ZONE_UNIT DetectedZone +function DETECTION_AREAS:GetDetectedZone( Index ) + + local DetectedZone = self.DetectedAreas[Index].Zone if DetectedZone then return DetectedZone end @@ -23515,10 +25590,107 @@ function DETECTION_UNITGROUPS:GetDetectedZone( Index ) return nil end +--- Background worker function to determine if there are friendlies nearby ... +-- @param #DETECTION_AREAS self +-- @param Unit#UNIT ReportUnit +function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) + self:F2() + + local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT + + DetectedArea.FriendliesNearBy = false + + local SphereSearch = { + id = world.VolumeType.SPHERE, + params = { + point = DetectedZoneUnit:GetPointVec3(), + radius = 6000, + } + + } + + --- @param DCSUnit#Unit FoundDCSUnit + -- @param Group#GROUP ReportGroup + -- @param Set#SET_GROUP ReportSetGroup + local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) + + local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Unit#UNIT + local ReportSetGroup = ReportGroupData.ReportSetGroup + + local EnemyCoalition = DetectedZoneUnit:GetCoalition() + + local FoundUnitCoalition = FoundDCSUnit:getCoalition() + local FoundUnitName = FoundDCSUnit:getName() + local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() + local EnemyUnitName = DetectedZoneUnit:GetName() + local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil + + self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + + if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then + DetectedArea.FriendliesNearBy = true + return false + end + + return true + end + + world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) + +end + + + +--- Returns if there are friendlies nearby the FAC units ... +-- @param #DETECTION_AREAS self +-- @return #boolean trhe if there are friendlies nearby +function DETECTION_AREAS:IsFriendliesNearBy( DetectedArea ) + + self:T3( DetectedArea.FriendliesNearBy ) + return DetectedArea.FriendliesNearBy or false +end + +--- Calculate the maxium A2G threat level of the DetectedArea. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) + + local MaxThreatLevelA2G = 0 + for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do + local ThreatUnit = UnitData -- Unit#UNIT + local ThreatLevelA2G = ThreatUnit:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + end + end + + self:T3( MaxThreatLevelA2G ) + DetectedArea.MaxThreatLevelA2G = MaxThreatLevelA2G + +end + +--- Returns the A2G threat level of the units in the DetectedArea +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #number a scale from 0 to 10. +function DETECTION_AREAS:GetTreatLevelA2G( DetectedArea ) + + self:T3( DetectedArea.MaxThreatLevelA2G ) + return DetectedArea.MaxThreatLevelA2G +end + + + --- Smoke the detected units --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:SmokeDetectedUnits() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:SmokeDetectedUnits() self:F2() self._SmokeDetectedUnits = true @@ -23526,9 +25698,9 @@ function DETECTION_UNITGROUPS:SmokeDetectedUnits() end --- Flare the detected units --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:FlareDetectedUnits() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:FlareDetectedUnits() self:F2() self._FlareDetectedUnits = true @@ -23536,9 +25708,9 @@ function DETECTION_UNITGROUPS:FlareDetectedUnits() end --- Smoke the detected zones --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:SmokeDetectedZones() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:SmokeDetectedZones() self:F2() self._SmokeDetectedZones = true @@ -23546,77 +25718,310 @@ function DETECTION_UNITGROUPS:SmokeDetectedZones() end --- Flare the detected zones --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:FlareDetectedZones() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:FlareDetectedZones() self:F2() self._FlareDetectedZones = true return self end +--- Add a change to the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @param #string ChangeCode +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AddChangeArea( DetectedArea, ChangeCode, AreaUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode].AreaID = AreaID + DetectedArea.Changes[ChangeCode].AreaUnitType = AreaUnitType + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, AreaUnitType } ) + + return self +end + + +--- Add a change to the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @param #string ChangeCode +-- @param #string ChangeUnitType +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AddChangeUnit( DetectedArea, ChangeCode, ChangeUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] or 0 + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] + 1 + DetectedArea.Changes[ChangeCode].AreaID = AreaID + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, ChangeUnitType } ) + + return self +end + +--- Make text documenting the changes of the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #string The Changes text +function DETECTION_AREAS:GetChangeText( DetectedArea ) + self:F( DetectedArea ) + + local MT = {} + + for ChangeCode, ChangeData in pairs( DetectedArea.Changes ) do + + if ChangeCode == "AA" then + MT[#MT+1] = "Detected new area " .. ChangeData.AreaID .. ". The center target is a " .. ChangeData.AreaUnitType .. "." + end + + if ChangeCode == "RAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". Removed the center target " .. ChangeData.AreaUnitType "." + end + + if ChangeCode == "AAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". The new center target is a " .. ChangeData.AreaUnitType "." + end + + if ChangeCode == "RA" then + MT[#MT+1] = "Removed old area " .. ChangeData.AreaID .. ". No more targets in this area." + end + + if ChangeCode == "AU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Detected for area " .. ChangeData.AreaID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + if ChangeCode == "RU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Removed for area " .. ChangeData.AreaID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + end + + return table.concat( MT, "\n" ) + +end + + +--- Accepts changes from the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AcceptChanges( DetectedArea ) + + DetectedArea.Changed = false + DetectedArea.Changes = {} + + return self +end + --- Make a DetectionSet table. This function will be overridden in the derived clsses. --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:CreateDetectionSets() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:CreateDetectionSets() self:F2() - for DetectedUnitName, DetectedUnitData in pairs( self.DetectedObjects ) do - self:T( DetectedUnitData.Name ) - local DetectedUnit = UNIT:FindByName( DetectedUnitData.Name ) -- Unit#UNIT - if DetectedUnit and DetectedUnit:IsAlive() then - self:T( DetectedUnit:GetName() ) - if #self.DetectedSets == 0 then - self:T( { "Adding Unit Set #", 1 } ) - self.DetectedZones[1] = ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - self.DetectedSets[1] = SET_UNIT:New() - self.DetectedSets[1]:AddUnit( DetectedUnit ) + -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. + -- Regroup when needed, split groups when needed. + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + + local DetectedSet = DetectedArea.Set + + local AreaExists = false -- This flag will determine of the detected area is still existing. + + -- First test if the center unit is detected in the detection area. + self:T3( DetectedArea.Zone.ZoneUNIT.UnitName ) + local DetectedZoneObject = self:GetDetectedObject( DetectedArea.Zone.ZoneUNIT.UnitName ) + self:T3( { "Detecting Zone Object", DetectedArea.AreaID, DetectedArea.Zone, DetectedZoneObject } ) + + if DetectedZoneObject then + + --self:IdentifyDetectedObject( DetectedZoneObject ) + AreaExists = true + + + else - local AddedToSet = false - for DetectedZoneIndex = 1, #self.DetectedZones do - self:T( "Detected Unit Set #" .. DetectedZoneIndex ) - local DetectedUnitSet = self.DetectedSets[DetectedZoneIndex] -- Set#SET_BASE - local DetectedZone = self.DetectedZones[DetectedZoneIndex] -- Zone#ZONE_UNIT - if DetectedUnit:IsInZone( DetectedZone ) then - self:T( "Adding to Unit Set #" .. DetectedZoneIndex ) - DetectedUnitSet:AddUnit( DetectedUnit ) - AddedToSet = true + -- The center object of the detected area has not been detected. Find an other unit of the set to become the center of the area. + -- First remove the center unit from the set. + DetectedSet:RemoveUnitsByName( DetectedArea.Zone.ZoneUNIT.UnitName ) + + self:AddChangeArea( DetectedArea, 'RAU', DetectedArea.Zone.ZoneUNIT:GetTypeName() ) + + -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. + for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do + + local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) + + -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. + -- If the DetectedUnit was already identified, DetectedObject will be nil. + if DetectedObject then + self:IdentifyDetectedObject( DetectedObject ) + AreaExists = true + + -- Assign the Unit as the new center unit of the detected area. + DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) + + self:AddChangeArea( DetectedArea, "AAU", DetectedArea.Zone.ZoneUNIT:GetTypeName() ) + + -- We don't need to add the DetectedObject to the area set, because it is already there ... + break end end - if AddedToSet == false then - local DetectedZoneIndex = #self.DetectedZones + 1 - self:T( "Adding new zone #" .. DetectedZoneIndex ) - self.DetectedZones[DetectedZoneIndex] = ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - self.DetectedSets[DetectedZoneIndex] = SET_UNIT:New() - self.DetectedSets[DetectedZoneIndex]:AddUnit( DetectedUnit ) - end + end + + -- Now we've determined the center unit of the area, now we can iterate the units in the detected area. + -- Note that the position of the area may have moved due to the center unit repositioning. + -- If no center unit was identified, then the detected area does not exist anymore and should be deleted, as there are no valid units that can be the center unit. + if AreaExists then + + -- ok, we found the center unit of the area, now iterate through the detected area set and see which units are still within the center unit zone ... + -- Those units within the zone are flagged as Identified. + -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. + for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do + + local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedObject = nil + if DetectedUnit:IsAlive() then + --self:E(DetectedUnit:GetName()) + DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) + end + if DetectedObject then + + -- Check if the DetectedUnit is within the DetectedArea.Zone + if DetectedUnit:IsInZone( DetectedArea.Zone ) then + + -- Yes, the DetectedUnit is within the DetectedArea.Zone, no changes, DetectedUnit can be kept within the Set. + self:IdentifyDetectedObject( DetectedObject ) + + else + -- No, the DetectedUnit is not within the DetectedArea.Zone, remove DetectedUnit from the Set. + DetectedSet:Remove( DetectedUnitName ) + self:AddChangeUnit( DetectedArea, "RU", DetectedUnit:GetTypeName() ) + end + + else + -- There was no DetectedObject, remove DetectedUnit from the Set. + self:AddChangeUnit( DetectedArea, "RU", "destroyed target" ) + DetectedSet:Remove( DetectedUnitName ) + + -- The DetectedObject has been identified, because it does not exist ... + -- self:IdentifyDetectedObject( DetectedObject ) + end + end + else + self:RemoveDetectedArea( DetectedAreaID ) + self:AddChangeArea( DetectedArea, "RA" ) end end end - -- Now all the tests should have been build, now make some smoke and flares... + -- We iterated through the existing detection areas and: + -- - We checked which units are still detected in each detection area. Those units were flagged as Identified. + -- - We recentered the detection area to new center units where it was needed. + -- + -- Now we need to loop through the unidentified detected units and see where they belong: + -- - They can be added to a new detection area and become the new center unit. + -- - They can be added to a new detection area. + for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do + + local DetectedObject = self:GetDetectedObject( DetectedUnitName ) + + if DetectedObject then + + -- We found an unidentified unit outside of any existing detection area. + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Unit#UNIT + + local AddedToDetectionArea = false + + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + self:T( "Detection Area #" .. DetectedArea.AreaID ) + local DetectedSet = DetectedArea.Set + if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedArea.Zone ) then + self:IdentifyDetectedObject( DetectedObject ) + DetectedSet:AddUnit( DetectedUnit ) + AddedToDetectionArea = true + self:AddChangeUnit( DetectedArea, "AU", DetectedUnit:GetTypeName() ) + end + end + end + + if AddedToDetectionArea == false then + + -- New detection area + local DetectedArea = self:AddDetectedArea( + SET_UNIT:New(), + ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) + ) + --self:E( DetectedArea.Zone.ZoneUNIT.UnitName ) + DetectedArea.Set:AddUnit( DetectedUnit ) + self:AddChangeArea( DetectedArea, "AA", DetectedUnit:GetTypeName() ) + end + end + end - for DetectedZoneIndex = 1, #self.DetectedZones do - local DetectedUnitSet = self.DetectedSets[DetectedZoneIndex] -- Set#SET_BASE - local DetectedZone = self.DetectedZones[DetectedZoneIndex] -- Zone#ZONE_UNIT - self:T( "Detected Set #" .. DetectedZoneIndex ) - DetectedUnitSet:ForEachUnit( + -- Now all the tests should have been build, now make some smoke and flares... + -- We also report here the friendlies within the detected areas. + + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + self:ReportFriendliesNearBy( { DetectedArea = DetectedArea, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table + self:CalculateThreatLevelA2G( DetectedArea ) -- Calculate A2G threat level + + if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then + DetectedZone.ZoneUNIT:SmokeRed() + end + DetectedSet:ForEachUnit( --- @param Unit#UNIT DetectedUnit function( DetectedUnit ) - self:T( DetectedUnit:GetName() ) - if self._FlareDetectedUnits then - DetectedUnit:FlareRed() - end - if self._SmokeDetectedUnits then - DetectedUnit:SmokeRed() + if DetectedUnit:IsAlive() then + self:T( "Detected Set #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) + if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then + DetectedUnit:FlareGreen() + end + if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then + DetectedUnit:SmokeGreen() + end end end ) - if self._FlareDetectedZones then + if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then DetectedZone:FlareZone( POINT_VEC3.SmokeColor.White, 30, math.random( 0,90 ) ) end - if self._SmokeDetectedZones then + if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then DetectedZone:SmokeZone( POINT_VEC3.SmokeColor.White, 30 ) end end @@ -23624,216 +26029,2741 @@ function DETECTION_UNITGROUPS:CreateDetectionSets() end ---- This module contains the FAC classes. +--- This module contains the DETECTION_MANAGER class and derived classes. -- -- === -- --- 1) @{Fac#FAC_BASE} class, extends @{Base#BASE} --- ============================================== --- The @{Fac#FAC_BASE} class defines the core functions to report detected objects to clients. --- Reportings can be done in several manners, and it is up to the derived classes if FAC_BASE to model the reporting behaviour. +-- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} +-- ==================================================================== +-- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. +-- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. -- --- 1.1) FAC_BASE constructor: --- ---------------------------- --- * @{Fac#FAC_BASE.New}(): Create a new FAC_BASE instance. +-- 1.1) DETECTION_MANAGER constructor: +-- ----------------------------------- +-- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. -- --- 1.2) FAC_BASE reporting: --- ------------------------ --- Derived FAC_BASE classes will reports detected units using the method @{Fac#FAC_BASE.ReportDetected}(). This method implements polymorphic behaviour. +-- 1.2) DETECTION_MANAGER reporting: +-- --------------------------------- +-- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. -- --- The time interval in seconds of the reporting can be changed using the methods @{Fac#FAC_BASE.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{Fac#FAC_BASE.SetReportDisplayTime}(). --- Derived classes need to implement the method @{Fac#FAC_BASE.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. +-- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). +-- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). +-- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. -- --- Reporting can be started and stopped using the methods @{Fac#FAC_BASE.StartReporting}() and @{Fac#FAC_BASE.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{Fac#FAC_BASE#ReportNow}(). +-- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. +-- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). -- -- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. -- -- === -- --- 2) @{Fac#FAC_REPORTING} class, extends @{Fac#FAC_BASE} --- ====================================================== --- The @{Fac#FAC_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Fac#FAC_BASE} class. +-- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} +-- ========================================================================================= +-- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. -- --- 2.1) FAC_REPORTING constructor: +-- 2.1) DETECTION_REPORTING constructor: -- ------------------------------- --- The @{Fac#FAC_REPORTING.New}() method creates a new FAC_REPORTING instance. +-- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- -- === -- --- @module Fac --- @author Mechanic, Prof_Hilactic, FlightControl : Concept & Testing --- @author FlightControl : Design & Programming +-- 3) @{#DETECTION_DISPATCHER} class, extends @{#DETECTION_MANAGER} +-- ================================================================ +-- The @{#DETECTION_DISPATCHER} class implements the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of FAC (groups). +-- The FAC will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. +-- Find a summary below describing for which situation a task type is created: +-- +-- * **CAS Task**: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter. +-- * **BAI Task**: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter. +-- * **SEAD Task**: Is created when there are enemy ground units wihtin range of the FAC, with air search radars. +-- +-- Other task types will follow... +-- +-- 3.1) DETECTION_DISPATCHER constructor: +-- -------------------------------------- +-- The @{#DETECTION_DISPATCHER.New}() method creates a new DETECTION_DISPATCHER instance. +-- +-- === +-- +-- ### Contributions: Mechanic, Prof_Hilactic, FlightControl - Concept & Testing +-- ### Author: FlightControl - Framework Design & Programming +-- +-- @module DetectionManager - - ---- FAC_BASE class. --- @type FAC_BASE --- @field Set#SET_CLIENT ClientSet The clients to which the FAC will report to. --- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. --- @extends Base#BASE -FAC_BASE = { - ClassName = "FAC_BASE", - ClientSet = nil, - Detection = nil, -} - ---- FAC constructor. --- @param #FAC_BASE self --- @param Set#SET_CLIENT ClientSet --- @param Detection#DETECTION_BASE Detection --- @return #FAC_BASE self -function FAC_BASE:New( ClientSet, Detection ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Fac#FAC_BASE +do -- DETECTION MANAGER - self.ClientSet = ClientSet - self.Detection = Detection + --- DETECTION_MANAGER class. + -- @type DETECTION_MANAGER + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @extends Base#BASE + DETECTION_MANAGER = { + ClassName = "DETECTION_MANAGER", + SetGroup = nil, + Detection = nil, + } - self:SetReportInterval( 60 ) - self:SetReportDisplayTime( 15 ) + --- FAC constructor. + -- @param #DETECTION_MANAGER self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:New( SetGroup, Detection ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) -- Detection#DETECTION_MANAGER + + self.SetGroup = SetGroup + self.Detection = Detection + + self:SetReportInterval( 30 ) + self:SetReportDisplayTime( 25 ) + + return self + end + + --- Set the reporting time interval. + -- @param #DETECTION_MANAGER self + -- @param #number ReportInterval The interval in seconds when a report needs to be done. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:SetReportInterval( ReportInterval ) + self:F2() + + self._ReportInterval = ReportInterval + end + + + --- Set the reporting message display time. + -- @param #DETECTION_MANAGER self + -- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime ) + self:F2() + + self._ReportDisplayTime = ReportDisplayTime + end + + --- Get the reporting message display time. + -- @param #DETECTION_MANAGER self + -- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. + function DETECTION_MANAGER:GetReportDisplayTime() + self:F2() + + return self._ReportDisplayTime + end + + + + --- Reports the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_MANAGER self + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:ReportDetected( Detection ) + self:F2() + + end + + --- Schedule the FAC reporting. + -- @param #DETECTION_MANAGER self + -- @param #number DelayTime The delay in seconds to wait the reporting. + -- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:Schedule( DelayTime, ReportInterval ) + self:F2() + + self._ScheduleDelayTime = DelayTime + + self:SetReportInterval( ReportInterval ) + + self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "DetectionManager" }, self._ScheduleDelayTime, self._ReportInterval ) + return self + end + + --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. + -- @param #DETECTION_MANAGER self + function DETECTION_MANAGER:_FacScheduler( SchedulerName ) + self:F2( { SchedulerName } ) + + return self:ProcessDetected( self.Detection ) + +-- self.SetGroup:ForEachGroup( +-- --- @param Group#GROUP Group +-- function( Group ) +-- if Group:IsAlive() then +-- return self:ProcessDetected( self.Detection ) +-- end +-- end +-- ) + +-- return true + end - return self -end - ---- Set the reporting time interval. --- @param #FAC_BASE self --- @param #number ReportInterval The interval in seconds when a report needs to be done. --- @return #FAC_BASE self -function FAC_BASE:SetReportInterval( ReportInterval ) - self:F2() - - self._ReportInterval = ReportInterval end ---- Set the reporting message display time. --- @param #FAC_BASE self --- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. --- @return #FAC_BASE self -function FAC_BASE:SetReportDisplayTime( ReportDisplayTime ) - self:F2() - - self._ReportDisplayTime = ReportDisplayTime -end - ---- Get the reporting message display time. --- @param #FAC_BASE self --- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. -function FAC_BASE:GetReportDisplayTime() - self:F2() - - return self._ReportDisplayTime -end - ---- Reports the detected items to the @{Set#SET_CLIENT}. --- @param #FAC_BASE self --- @param Set#SET_BASE DetectedSets The detected Sets created by the @{Detection#DETECTION_BASE} object. --- @return #FAC_BASE self -function FAC_BASE:ReportDetected( DetectedSets ) - self:F2() +do -- DETECTION_REPORTING + --- DETECTION_REPORTING class. + -- @type DETECTION_REPORTING + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @extends #DETECTION_MANAGER + DETECTION_REPORTING = { + ClassName = "DETECTION_REPORTING", + } - -end - ---- Schedule the FAC reporting. --- @param #FAC_BASE self --- @param #number DelayTime The delay in seconds to wait the reporting. --- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. --- @return #FAC_BASE self -function FAC_BASE:Schedule( DelayTime, ReportInterval ) - self:F2() - - self._ScheduleDelayTime = DelayTime - self:SetReportInterval( ReportInterval ) + --- DETECTION_REPORTING constructor. + -- @param #DETECTION_REPORTING self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_AREAS Detection + -- @return #DETECTION_REPORTING self + function DETECTION_REPORTING:New( SetGroup, Detection ) - self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "Fac" }, self._ScheduleDelayTime, self._ReportInterval ) - return self -end - ---- Report the detected @{Unit#UNIT}s detected within the @{DetectION#DETECTION_BASE} object to the @{Set#SET_CLIENT}s. --- @param #FAC_BASE self -function FAC_BASE:_FacScheduler( SchedulerName ) - self:F2( { SchedulerName } ) + -- Inherits from DETECTION_MANAGER + local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING + + self:Schedule( 1, 30 ) + return self + end - self.ClientSet:ForEachClient( - --- @param Client#CLIENT Client - function( Client ) - if Client:IsAlive() then - local DetectedSets = self.Detection:GetDetectedSets() - return self:ReportDetected( Client, DetectedSets ) - end - end - ) + --- Creates a string of the detected items in a @{Detection}. + -- @param #DETECTION_MANAGER self + -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. + -- @return #DETECTION_MANAGER self + function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) + self:F2() - return true -end - --- FAC_REPORTING - ---- FAC_REPORTING class. --- @type FAC_REPORTING --- @field Set#SET_CLIENT ClientSet The clients to which the FAC will report to. --- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. --- @extends #FAC_BASE -FAC_REPORTING = { - ClassName = "FAC_REPORTING", -} - - ---- FAC_REPORTING constructor. --- @param #FAC_REPORTING self --- @param Set#SET_CLIENT ClientSet --- @param Detection#DETECTION_BASE Detection --- @return #FAC_REPORTING self -function FAC_REPORTING:New( ClientSet, Detection ) - - -- Inherits from FAC_BASE - local self = BASE:Inherit( self, FAC_BASE:New( ClientSet, Detection ) ) -- #FAC_REPORTING - - self:Schedule( 5, 60 ) - return self -end - - ---- Reports the detected items to the @{Set#SET_CLIENT}. --- @param #FAC_REPORTING self --- @param Client#CLIENT Client The @{Client} object to where the report needs to go. --- @param Set#SET_BASE DetectedSets The detected Sets created by the @{Detection#DETECTION_BASE} object. --- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. -function FAC_REPORTING:ReportDetected( Client, DetectedSets ) - self:F2( Client ) - - local DetectedMsg = {} - for DetectedUnitSetID, DetectedUnitSet in pairs( DetectedSets ) do - local UnitSet = DetectedUnitSet -- Set#SET_UNIT local MT = {} -- Message Text local UnitTypes = {} - for DetectedUnitID, DetectedUnitData in pairs( UnitSet:GetSet() ) do + + for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do local DetectedUnit = DetectedUnitData -- Unit#UNIT - local UnitType = DetectedUnit:GetTypeName() - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + if DetectedUnit:IsAlive() then + local UnitType = DetectedUnit:GetTypeName() + + if not UnitTypes[UnitType] then + UnitTypes[UnitType] = 1 + else + UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + end end end + for UnitTypeID, UnitType in pairs( UnitTypes ) do MT[#MT+1] = UnitType .. " of " .. UnitTypeID end - local MessageText = table.concat( MT, ", " ) - DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedUnitSetID .. ": " .. MessageText + + return table.concat( MT, ", " ) + end + + + + --- Reports the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_REPORTING self + -- @param Group#GROUP Group The @{Group} object to where the report needs to go. + -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. + -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. + function DETECTION_REPORTING:ProcessDetected( Group, Detection ) + self:F2( Group ) + + self:E( Group ) + local DetectedMsg = {} + for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do + local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) + end + local FACGroup = Detection:GetDetectionGroups() + FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group ) + + return true + end + +end + +do -- DETECTION_DISPATCHER + + --- DETECTION_DISPATCHER class. + -- @type DETECTION_DISPATCHER + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Mission#MISSION Mission + -- @field Group#GROUP CommandCenter + -- @extends DetectionManager#DETECTION_MANAGER + DETECTION_DISPATCHER = { + ClassName = "DETECTION_DISPATCHER", + Mission = nil, + CommandCenter = nil, + Detection = nil, + } + + + --- DETECTION_DISPATCHER constructor. + -- @param #DETECTION_DISPATCHER self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_DISPATCHER self + function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) + + -- Inherits from DETECTION_MANAGER + local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_DISPATCHER + + self.Detection = Detection + self.CommandCenter = CommandCenter + self.Mission = Mission + + self:Schedule( 30 ) + return self + end + + + --- Creates a SEAD task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Set#SET_UNIT TargetSetUnit: The target set of units. + -- @return #nil If there are no targets to be set. + function DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local RadarCount = DetectedSet:HasSEAD() + + if RadarCount > 0 then + + -- Here we're doing something advanced... We're copying the DetectedSet, but making a new Set only with SEADable Radar units in it. + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterHasSEAD() + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Creates a CAS task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local GroundUnitCount = DetectedSet:HasGroundUnits() + local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) + + if GroundUnitCount > 0 and FriendliesNearBy == true then + + -- Copy the Set + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Creates a BAI task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local GroundUnitCount = DetectedSet:HasGroundUnits() + local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) + + if GroundUnitCount > 0 and FriendliesNearBy == false then + + -- Copy the Set + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Evaluates the removal of the Task from the Mission. + -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". + -- @param #DETECTION_DISPATCHER self + -- @param Mission#MISSION Mission + -- @param Task#TASK_BASE Task + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) + + if Task then + if Task:IsStatePlanned() and DetectedArea.Changed == true then + Mission:RemoveTaskMenu( Task ) + Task = Mission:RemoveTask( Task ) + end + end + + return Task + end + + + --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. + -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. + function DETECTION_DISPATCHER:ProcessDetected( Detection ) + self:F2() + + local AreaMsg = {} + local TaskMsg = {} + local ChangeMsg = {} + + local Mission = self.Mission + + --- First we need to the detected targets. + for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do + + local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) + DetectedSet:Flush() + + local AreaID = DetectedArea.AreaID + + -- Evaluate SEAD Tasking + local SEADTask = Mission:GetTask( "SEAD." .. AreaID ) + SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedArea ) + if not SEADTask then + local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if SEADTask and SEADTask:IsStatePlanned() then + SEADTask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() + end + + -- Evaluate CAS Tasking + local CASTask = Mission:GetTask( "CAS." .. AreaID ) + CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedArea ) + if not CASTask then + local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + CASTask = Mission:AddTask( TASK_CAS:New( Mission, self.SetGroup, "CAS." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if CASTask and CASTask:IsStatePlanned() then + CASTask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() + end + + -- Evaluate BAI Tasking + local BAITask = Mission:GetTask( "BAI." .. AreaID ) + BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedArea ) + if not BAITask then + local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + BAITask = Mission:AddTask( TASK_BAI:New( Mission, self.SetGroup, "BAI." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if BAITask and BAITask:IsStatePlanned() then + BAITask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() + end + + if #TaskMsg > 0 then + + local ThreatLevel = Detection:GetTreatLevelA2G( DetectedArea ) + + local DetectedAreaVec3 = DetectedZone:GetPointVec3() + local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) + local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) + AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", + DetectedAreaID, + DetectedAreaPointLL, + string.rep( "â– ", ThreatLevel ), + ThreatLevel + ) + + -- Loop through the changes ... + local ChangeText = Detection:GetChangeText( DetectedArea ) + + if ChangeText ~= "" then + ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) + end + end + + -- OK, so the tasking has been done, now delete the changes reported for the area. + Detection:AcceptChanges( DetectedArea ) + + end + + if #AreaMsg > 0 then + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not TaskGroup:GetState( TaskGroup, "Assigned" ) then + self.CommandCenter:MessageToGroup( + string.format( "HQ Reporting - Target areas for mission '%s':\nAreas:\n%s\n\nTasks:\n%s\n\nChanges:\n%s ", + self.Mission:GetName(), + table.concat( AreaMsg, "\n" ), + table.concat( TaskMsg, "\n" ), + table.concat( ChangeMsg, "\n" ) + ), self:GetReportDisplayTime(), TaskGroup + ) + end + end + end + + return true + end + +end--- This module contains the STATEMACHINE class. +-- This development is based on a state machine implementation made by Conroy Kyle. +-- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine +-- +-- I've taken the development and enhanced it to make the state machine hierarchical... +-- It is a fantastic development, this module. +-- +-- === +-- +-- 1) @{Workflow#STATEMACHINE} class, extends @{Base#BASE} +-- ============================================== +-- +-- 1.1) Add or remove objects from the STATEMACHINE +-- -------------------------------------------- +-- @module StateMachine +-- @author FlightControl + + +--- STATEMACHINE class +-- @type STATEMACHINE +STATEMACHINE = { + ClassName = "STATEMACHINE", +} + +--- Creates a new STATEMACHINE object. +-- @param #STATEMACHINE self +-- @return #STATEMACHINE +function STATEMACHINE:New( options ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) + + + --local self = routines.utils.deepCopy( self ) -- Create a new self instance + + assert(options.events) + + --local MT = {} + --setmetatable( self, MT ) + --self.__index = self + + self.options = options + self.current = options.initial or 'none' + self.events = {} + self.subs = {} + self.endstates = {} + + for _, event in ipairs(options.events or {}) do + local name = event.name + self[name] = self[name] or self:_create_transition(name) + self.events[name] = self.events[name] or { map = {} } + self:_add_to_map(self.events[name].map, event) + end + + for name, callback in pairs(options.callbacks or {}) do + self[name] = callback + end + + for name, sub in pairs( options.subs or {} ) do + self:_submap( self.subs, sub, name ) + end + + for name, endstate in pairs( options.endstates or {} ) do + self.endstates[endstate] = endstate + end + + return self +end + + +function STATEMACHINE:_submap( subs, sub, name ) + self:E( { sub = sub, name = name } ) + subs[sub.onstateparent] = subs[sub.onstateparent] or {} + subs[sub.onstateparent][sub.oneventparent] = subs[sub.onstateparent][sub.oneventparent] or {} + local Index = #subs[sub.onstateparent][sub.oneventparent] + 1 + subs[sub.onstateparent][sub.oneventparent][Index] = {} + subs[sub.onstateparent][sub.oneventparent][Index].fsm = sub.fsm + subs[sub.onstateparent][sub.oneventparent][Index].event = sub.event + subs[sub.onstateparent][sub.oneventparent][Index].returnevents = sub.returnevents -- these events need to be given to find the correct continue event ... if none given, the processing will stop. + subs[sub.onstateparent][sub.oneventparent][Index].name = name + subs[sub.onstateparent][sub.oneventparent][Index].fsmparent = self +end + + +function STATEMACHINE:_call_handler(handler, params) + if handler then + return handler(unpack(params)) + end +end + +function STATEMACHINE:_create_transition(name) + self:E( { name = name } ) + return function(self, ...) + local can, to = self:can(name) + self:T( { name, can, to } ) + + if can then + local from = self.current + local params = { self, name, from, to, ... } + + if self:_call_handler(self["onbefore" .. name], params) == false + or self:_call_handler(self["onleave" .. from], params) == false then + return false + end + + self.current = to + + local execute = true + + local subtable = self:_gosub( to, name ) + for _, sub in pairs( subtable ) do + self:E( "calling sub: " .. sub.event ) + sub.fsm.fsmparent = self + sub.fsm.returnevents = sub.returnevents + sub.fsm[sub.event]( sub.fsm ) + execute = true + end + + local fsmparent, event = self:_isendstate( to ) + if fsmparent and event then + self:E( { "end state: ", fsmparent, event } ) + self:_call_handler(self["onenter" .. to] or self["on" .. to], params) + self:_call_handler(self["onafter" .. name] or self["on" .. name], params) + self:_call_handler(self["onstatechange"], params) + fsmparent[event]( fsmparent ) + execute = false + end + + if execute then + self:E( { "execute: " .. to, name } ) + self:_call_handler(self["onenter" .. to] or self["on" .. to], params) + self:_call_handler(self["onafter" .. name] or self["on" .. name], params) + self:_call_handler(self["onstatechange"], params) + end + + return true + end + + return false + end +end + +function STATEMACHINE:_gosub( parentstate, parentevent ) + local fsmtable = {} + if self.subs[parentstate] and self.subs[parentstate][parentevent] then + return self.subs[parentstate][parentevent] + else + return {} + end +end + +function STATEMACHINE:_isendstate( state ) + local fsmparent = self.fsmparent + if fsmparent and self.endstates[state] then + self:E( { state = state, endstates = self.endstates, endstate = self.endstates[state] } ) + local returnevent = nil + local fromstate = fsmparent.current + self:E( fromstate ) + self:E( self.returnevents ) + for _, eventname in pairs( self.returnevents ) do + local event = fsmparent.events[eventname] + self:E( event ) + local to = event and event.map[fromstate] or event.map['*'] + if to and to == state then + return fsmparent, eventname + else + self:E( { "could not find parent event name for state", fromstate, to } ) + end + end + end + + return nil +end + +function STATEMACHINE:_add_to_map(map, event) + if type(event.from) == 'string' then + map[event.from] = event.to + else + for _, from in ipairs(event.from) do + map[from] = event.to + end + end +end + +function STATEMACHINE:is(state) + return self.current == state +end + +function STATEMACHINE:can(e) + local event = self.events[e] + local to = event and event.map[self.current] or event.map['*'] + return to ~= nil, to +end + +function STATEMACHINE:cannot(e) + return not self:can(e) +end + +function STATEMACHINE:todot(filename) + local dotfile = io.open(filename,'w') + dotfile:write('digraph {\n') + local transition = function(event,from,to) + dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) + end + for _, event in pairs(self.options.events) do + if type(event.from) == 'table' then + for _, from in ipairs(event.from) do + transition(event.name,from,event.to) + end + else + transition(event.name,event.from,event.to) + end + end + dotfile:write('}\n') + dotfile:close() +end + +--- STATEMACHINE_PROCESS class +-- @type STATEMACHINE_PROCESS +-- @field Process#PROCESS Process +-- @extends StateMachine#STATEMACHINE +STATEMACHINE_PROCESS = { + ClassName = "STATEMACHINE_PROCESS", +} + +--- Creates a new STATEMACHINE_PROCESS object. +-- @param #STATEMACHINE_PROCESS self +-- @return #STATEMACHINE_PROCESS +function STATEMACHINE_PROCESS:New( Process, options ) + + local FsmProcess = routines.utils.deepCopy( self ) -- Create a new self instance + local Parent = STATEMACHINE:New(options) + + setmetatable( FsmProcess, Parent ) + FsmProcess.__index = FsmProcess + + FsmProcess["onstatechange"] = Process.OnStateChange + FsmProcess.Process = Process + + return FsmProcess +end + +function STATEMACHINE_PROCESS:_call_handler( handler, params ) + if handler then + return handler( self.Process, unpack( params ) ) + end +end + +--- STATEMACHINE_TASK class +-- @type STATEMACHINE_TASK +-- @field Task#TASK_BASE Task +-- @extends StateMachine#STATEMACHINE +STATEMACHINE_TASK = { + ClassName = "STATEMACHINE_TASK", +} + +--- Creates a new STATEMACHINE_TASK object. +-- @param #STATEMACHINE_TASK self +-- @return #STATEMACHINE_TASK +function STATEMACHINE_TASK:New( Task, TaskUnit, options ) + + local FsmTask = routines.utils.deepCopy( self ) -- Create a new self instance + local Parent = STATEMACHINE:New(options) + + setmetatable( FsmTask, Parent ) + FsmTask.__index = FsmTask + + FsmTask["onstatechange"] = Task.OnStateChange + FsmTask["onAssigned"] = Task.OnAssigned + FsmTask["onSuccess"] = Task.OnSuccess + FsmTask["onFailed"] = Task.OnFailed + + FsmTask.Task = Task + FsmTask.TaskUnit = TaskUnit + + return FsmTask +end + +function STATEMACHINE_TASK:_call_handler( handler, params ) + if handler then + return handler( self.Task, self.TaskUnit, unpack( params ) ) + end +end +--- @module Process + +--- The PROCESS class +-- @type PROCESS +-- @field Scheduler#SCHEDULER ProcessScheduler +-- @field Unit#UNIT ProcessUnit +-- @field Task#TASK Task +-- @field StateMachine#STATEMACHINE_TASK Fsm +-- @field #string ProcessName +-- @extends Base#BASE +PROCESS = { + ClassName = "TASK", + ProcessScheduler = nil, + NextEvent = nil, + Scores = {}, +} + +--- Instantiates a new TASK Base. Should never be used. Interface Class. +-- @param #PROCESS self +-- @param #string ProcessName +-- @param Task#TASK_BASE Task +-- @param Unit#UNIT ProcessUnit +-- @return #PROCESS self +function PROCESS:New( ProcessName, Task, ProcessUnit ) + local self = BASE:Inherit( self, BASE:New() ) + self:F() + + self.ProcessUnit = ProcessUnit + self.Task = Task + self.ProcessName = ProcessName + + self.AllowEvents = true + + return self +end + +--- @param #PROCESS self +function PROCESS:NextEvent( NextEvent, ... ) + self:F2( arg ) + if self.AllowEvents == true then + self.ProcessScheduler = SCHEDULER:New( self.Fsm, NextEvent, { self, self.ProcessUnit, unpack( arg ) }, 1 ) + end +end + +--- @param #PROCESS self +function PROCESS:StopEvents( ) + self:F2() + if self.ProcessScheduler then + self:E( "Stop" ) + self.ProcessScheduler:Stop() + self.ProcessScheduler = nil + self.AllowEvents = false + end +end + +--- Adds a score for the PROCESS to be achieved. +-- @param #PROCESS self +-- @param #string ProcessStatus is the status of the PROCESS when the score needs to be given. +-- @param #string ScoreText is a text describing the score that is given according the status. +-- @param #number Score is a number providing the score of the status. +-- @return #PROCESS self +function PROCESS:AddScore( ProcessStatus, ScoreText, Score ) + self:F2( { ProcessStatus, ScoreText, Score } ) + + self.Scores[ProcessStatus] = self.Scores[ProcessStatus] or {} + self.Scores[ProcessStatus].ScoreText = ScoreText + self.Scores[ProcessStatus].Score = Score + return self +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS:OnStateChange( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName } ) + + if self:IsTrace() then + MESSAGE:New( "Process " .. self.ProcessName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() + end + + if self.Scores[To] then + + local Scoring = self.Task:GetScoring() + if Scoring then + Scoring:_AddMissionTaskScore( self.Task.Mission, self.ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end +end + + +--- This module contains the TASK_ASSIGN classes. +-- +-- === +-- +-- 1) @{Task_Assign#TASK_ASSIGN_ACCEPT} class, extends @{Task#TASK_BASE} +-- ===================================================================== +-- The @{Task_Assign#TASK_ASSIGN_ACCEPT} class accepts by default a task for a player. No player intervention is allowed to reject the task. +-- +-- 2) @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class, extends @{Task#TASK_BASE} +-- ========================================================================== +-- The @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class accepts a task when the player accepts the task through an added menu option. +-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. +-- The assignment type also allows to reject the task. +-- +-- +-- +-- +-- +-- +-- @module Task_Assign +-- + + +do -- PROCESS_ASSIGN_ACCEPT + + --- PROCESS_ASSIGN_ACCEPT class + -- @type PROCESS_ASSIGN_ACCEPT + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_ASSIGN_ACCEPT = { + ClassName = "PROCESS_ASSIGN_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #PROCESS_ASSIGN_ACCEPT self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_ASSIGN_ACCEPT self + function PROCESS_ASSIGN_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_ACCEPT + + self.TaskBriefing = TaskBriefing + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnAssigned', + events = { + { name = 'Start', from = 'UnAssigned', to = 'Assigned' }, + { name = 'Fail', from = 'UnAssigned', to = 'Failed' }, + }, + callbacks = { + onAssign = self.OnAssign, + }, + endstates = { + 'Assigned', 'Failed' + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_ACCEPT:OnAssigned( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + end + +end + + +do -- PROCESS_ASSIGN_MENU_ACCEPT + + --- PROCESS_ASSIGN_MENU_ACCEPT class + -- @type PROCESS_ASSIGN_MENU_ACCEPT + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_ASSIGN_MENU_ACCEPT = { + ClassName = "PROCESS_ASSIGN_MENU_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_MENU_ACCEPT + + self.TaskBriefing = TaskBriefing + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnAssigned', + events = { + { name = 'Start', from = 'UnAssigned', to = 'AwaitAccept' }, + { name = 'Assign', from = 'AwaitAccept', to = 'Assigned' }, + { name = 'Reject', from = 'AwaitAccept', to = 'Rejected' }, + { name = 'Fail', from = 'AwaitAccept', to = 'Rejected' }, + }, + callbacks = { + onStart = self.OnStart, + onAssign = self.OnAssign, + onReject = self.OnReject, + }, + endstates = { + 'Assigned', 'Rejected' + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnStart( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + MESSAGE:New( self.TaskBriefing .. "\nAccess the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", 30, "Assignment" ):ToGroup( self.ProcessUnit:GetGroup() ) + self.MenuText = self.Task.TaskName + + local ProcessGroup = self.ProcessUnit:GetGroup() + self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.MenuText .. " acceptance" ) + self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.MenuText, self.Menu, self.MenuAssign, self ) + self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.MenuText, self.Menu, self.MenuReject, self ) + end + + --- Menu function. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() + self:E( ) + + self:NextEvent( self.Fsm.Assign ) + end + + --- Menu function. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() + self:E( ) + + self:NextEvent( self.Fsm.Reject ) + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnAssign( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.Menu:Remove() + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnReject( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.Menu:Remove() + self.Task:UnAssignFromUnit( self.ProcessUnit ) + self.ProcessUnit:Destroy() + end +end +--- @module Task_Route + +--- PROCESS_ROUTE class +-- @type PROCESS_ROUTE +-- @field Task#TASK TASK +-- @field Unit#UNIT ProcessUnit +-- @field Zone#ZONE_BASE TargetZone +-- @extends Task2#TASK2 +PROCESS_ROUTE = { + ClassName = "PROCESS_ROUTE", +} + + +--- Creates a new routing state machine. The task will route a CLIENT to a ZONE until the CLIENT is within that ZONE. +-- @param #PROCESS_ROUTE self +-- @param Task#TASK Task +-- @param Unit#UNIT Unit +-- @return #PROCESS_ROUTE self +function PROCESS_ROUTE:New( Task, ProcessUnit, TargetZone ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ROUTE", Task, ProcessUnit ) ) -- #PROCESS_ROUTE + + self.TargetZone = TargetZone + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Route is the default display category + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnArrived', + events = { + { name = 'Start', from = 'UnArrived', to = 'UnArrived' }, + { name = 'Fail', from = 'UnArrived', to = 'Failed' }, + }, + callbacks = { + onleaveUnArrived = self.OnLeaveUnArrived, + onFail = self.OnFail, + }, + endstates = { + 'Arrived', 'Failed' + }, + } ) + + return self +end + +--- Task Events + +--- StateMachine callback function for a TASK2 +-- @param #PROCESS_ROUTE self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_ROUTE:OnLeaveUnArrived( Fsm, Event, From, To ) + + if self.ProcessUnit:IsAlive() then + local IsInZone = self.ProcessUnit:IsInZone( self.TargetZone ) + + if self.DisplayCount >= self.DisplayInterval then + if not IsInZone then + local ZoneVec2 = self.TargetZone:GetVec2() + local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) + local TaskUnitVec2 = self.ProcessUnit:GetVec2() + local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) + local RouteText = self.ProcessUnit:GetCallSign() .. ": Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." + MESSAGE:New( RouteText, self.DisplayTime, self.DisplayCategory ):ToGroup( self.ProcessUnit:GetGroup() ) + end + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + --if not IsInZone then + self:NextEvent( Fsm.Start ) + --end + + return IsInZone -- if false, then the event will not be executed... + end + + return false + +end + +--- @module Process_Smoke + +do -- PROCESS_SMOKE_TARGETS + + --- PROCESS_SMOKE_TARGETS class + -- @type PROCESS_SMOKE_TARGETS + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Set#SET_UNIT TargetSetUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_SMOKE_TARGETS = { + ClassName = "PROCESS_SMOKE_TARGETS", + } + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #PROCESS_SMOKE_TARGETS self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_SMOKE_TARGETS self + function PROCESS_SMOKE_TARGETS:New( Task, ProcessUnit, TargetSetUnit, TargetZone ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_SMOKE_TARGETS + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'None', + events = { + { name = 'Start', from = 'None', to = 'AwaitSmoke' }, + { name = 'Next', from = 'AwaitSmoke', to = 'Smoking' }, + { name = 'Next', from = 'Smoking', to = 'AwaitSmoke' }, + { name = 'Fail', from = 'Smoking', to = 'Failed' }, + { name = 'Fail', from = 'AwaitSmoke', to = 'Failed' }, + { name = 'Fail', from = 'None', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onNext = self.OnNext, + onSmoking = self.OnSmoking, + }, + endstates = { + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_SMOKE_TARGETS self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_SMOKE_TARGETS:OnStart( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self:E("Set smoke menu") + + local ProcessGroup = self.ProcessUnit:GetGroup() + local MissionMenu = self.Task.Mission:GetMissionMenu( ProcessGroup ) + + local function MenuSmoke( MenuParam ) + self:E( MenuParam ) + local self = MenuParam.self + local SmokeColor = MenuParam.SmokeColor + self.SmokeColor = SmokeColor + self:NextEvent( self.Fsm.Next ) + end + + self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) + self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) + self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) + self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) + self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) + self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_SMOKE_TARGETS self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_SMOKE_TARGETS:OnSmoking( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.TargetSetUnit:ForEachUnit( + --- @param Unit#UNIT SmokeUnit + function( SmokeUnit ) + if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then + SCHEDULER:New( self, + function() + SmokeUnit:Smoke( self.SmokeColor, 150 ) + end, {}, math.random( 10, 60 ) + ) + end + end + ) + + end + +end--- @module Process_Destroy + +--- PROCESS_DESTROY class +-- @type PROCESS_DESTROY +-- @field Unit#UNIT ProcessUnit +-- @field Set#SET_UNIT TargetSetUnit +-- @extends Process#PROCESS +PROCESS_DESTROY = { + ClassName = "PROCESS_DESTROY", + Fsm = {}, + TargetSetUnit = nil, +} + + +--- Creates a new DESTROY process. +-- @param #PROCESS_DESTROY self +-- @param Task#TASK Task +-- @param Unit#UNIT ProcessUnit +-- @param Set#SET_UNIT TargetSetUnit +-- @return #PROCESS_DESTROY self +function PROCESS_DESTROY:New( Task, ProcessName, ProcessUnit, TargetSetUnit ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_DESTROY + + self.TargetSetUnit = TargetSetUnit + + self.DisplayInterval = 60 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Targets is the default display category + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'Assigned', + events = { + { name = 'Start', from = 'Assigned', to = 'Waiting' }, + { name = 'Start', from = 'Waiting', to = 'Waiting' }, + { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, + { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, + { name = 'Destroyed', from = 'Destroy', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Waiting', to = 'Failed' }, + { name = 'Fail', from = 'Destroy', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onWaiting = self.OnWaiting, + onHitTarget = self.OnHitTarget, + onMoreTargets = self.OnMoreTargets, + onDestroyed = self.OnDestroyed, + onKilled = self.OnKilled, + }, + endstates = { 'Success', 'Failed' } + } ) + + + _EVENTDISPATCHER:OnDead( self.EventDead, self ) + + return self +end + +--- Process Events + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnStart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Start ) +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnWaiting( Fsm, Event, From, To ) + + local TaskGroup = self.ProcessUnit:GetGroup() + if self.DisplayCount >= self.DisplayInterval then + MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup ) + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + return true -- Process always the event. + +end + + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function PROCESS_DESTROY:OnHitTarget( Fsm, Event, From, To, Event ) + + self.TargetSetUnit:Flush() + + if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then + self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName ) + end + + local TaskGroup = self.ProcessUnit:GetGroup() + MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup ) + + if self.TargetSetUnit:Count() > 0 then + self:NextEvent( Fsm.MoreTargets ) + else + self:NextEvent( Fsm.Destroyed ) + end +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnMoreTargets( Fsm, Event, From, To ) + + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA DCSEvent +function PROCESS_DESTROY:OnKilled( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Restart ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnRestart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Menu ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnDestroyed( Fsm, Event, From, To ) + +end + +--- DCS Events + +--- @param #PROCESS_DESTROY self +-- @param Event#EVENTDATA Event +function PROCESS_DESTROY:EventDead( Event ) + + if Event.IniDCSUnit then + self.TargetSetUnit:Remove( Event.IniDCSUnitName ) + self:NextEvent( self.Fsm.HitTarget, Event ) + end +end + + +--- This module contains the TASK_BASE class. +-- +-- 1) @{#TASK_BASE} class, extends @{Base#BASE} +-- ============================================ +-- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. +-- ---------------------------------------------------------------------------------------- +-- The class provides a couple of methods to: +-- +-- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). +-- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. +-- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. +-- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. +-- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. +-- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} +-- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. +-- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. +-- +-- 1.2) Set and enquire task status (beyond the task state machine processing). +-- ---------------------------------------------------------------------------- +-- A task needs to implement as a minimum the following task states: +-- +-- * **Success**: Expresses the successful execution and finalization of the task. +-- * **Failed**: Expresses the failure of a task. +-- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. +-- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. +-- +-- A task may also implement the following task states: +-- +-- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. +-- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. +-- +-- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. +-- +-- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. +-- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. +-- +-- 1.3) Add scoring when reaching a certain task status: +-- ----------------------------------------------------- +-- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +-- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. +-- +-- 1.4) Task briefing: +-- ------------------- +-- A task briefing can be given that is shown to the player when he is assigned to the task. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task + +--- The TASK_BASE class +-- @type TASK_BASE +-- @field Scheduler#SCHEDULER TaskScheduler +-- @field Mission#MISSION Mission +-- @field StateMachine#STATEMACHINE Fsm +-- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task +-- @extends Base#BASE +TASK_BASE = { + ClassName = "TASK_BASE", + TaskScheduler = nil, + Processes = {}, + Players = nil, + Scores = {}, + Menu = {}, + SetGroup = nil, +} + + +--- Instantiates a new TASK_BASE. Should never be used. Interface Class. +-- @param #TASK_BASE self +-- @param Mission#MISSION The mission wherein the Task is registered. +-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. +-- @param #string TaskName The name of the Task +-- @param #string TaskType The type of the Task +-- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) +-- @return #TASK_BASE self +function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) + + local self = BASE:Inherit( self, BASE:New() ) + self:E( "New TASK " .. TaskName ) + + self.Processes = {} + self.Fsm = {} + + self.Mission = Mission + self.SetGroup = SetGroup + + self:SetCategory( TaskCategory ) + self:SetType( TaskType ) + self:SetName( TaskName ) + self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. + + self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." + + return self +end + +--- Cleans all references of a TASK_BASE. +-- @param #TASK_BASE self +-- @return #nil +function TASK_BASE:CleanUp() + + _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) + _EVENTDISPATCHER:OnDeadRemove( self ) + _EVENTDISPATCHER:OnCrashRemove( self ) + _EVENTDISPATCHER:OnPilotDeadRemove( self ) + + return nil +end + + +--- Assign the @{Task}to a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +function TASK_BASE:AssignToGroup( TaskGroup ) + self:F2( TaskGroup:GetName() ) + + local TaskGroupName = TaskGroup:GetName() + + TaskGroup:SetState( TaskGroup, "Assigned", self ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:AssignToUnit( TaskUnit ) + end + end +end + +--- Send the briefng message of the @{Task} to the assigned @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:SendBriefingToAssignedGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + if self:IsAssignedToGroup( TaskGroup ) then + TaskGroup:Message( self.TaskBriefing, 60 ) + end + end +end + + +--- Assign the @{Task} from the @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:UnAssignFromGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:UnAssignFromUnit( TaskUnit ) + end + end + end +end + +--- Returns if the @{Task} is assigned to the Group. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #boolean +function TASK_BASE:IsAssignedToGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + if self:IsStateAssigned() then + if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then + return true + end + end + + return false +end + +--- Assign the @{Task}to an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + return nil +end + +--- UnAssign the @{Task} from an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:UnAssignFromUnit( TaskUnitName ) + self:F( TaskUnitName ) + + if self:HasStateMachine( TaskUnitName ) == true then + self:RemoveStateMachines( TaskUnitName ) + self:RemoveProcesses( TaskUnitName ) + end + + return self +end + +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenu() + + local MenuText = self:GetPlannedMenuText() + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not self:IsAssignedToGroup( TaskGroup ) then + self:SetPlannedMenuForGroup( TaskGroup, MenuText ) + end end - local FACGroup = self.Detection:GetFACGroup() - FACGroup:MessageToClient( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Client ) +end + +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if self:IsAssignedToGroup( TaskGroup ) then + self:SetAssignedMenuForGroup( TaskGroup ) + end + end +end + +--- Remove the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + end +end + +--- Set the planned menu option of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @param #string MenuText The menu text. +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) + self:E( TaskGroup:GetName() ) + + local TaskMission = self.Mission:GetName() + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + Mission.MenuCategory = Mission.MenuCategory or {} + local MenuCategory = Mission.MenuCategory + + Mission.MenuType = Mission.MenuType or {} + local MenuType = Mission.MenuType + + self.Menu = self.Menu or {} + local Menu = self.Menu + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + + MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} + MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) + + MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} + MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) + + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + end + Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Set the assigned menu options of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) + self:E( TaskGroup:GetName() ) + + local TaskMission = self.Mission:GetName() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + self.MenuStatus = self.MenuStatus or {} + local MenuStatus = self.MenuStatus + + + self.MenuAbort = self.MenuAbort or {} + local MenuAbort = self.MenuAbort + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) + MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Remove the menu option of the @{Task} for a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenuForGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + local Mission = self.Mission + local MenuMission = Mission.MenuMission + local MenuCategory = Mission.MenuCategory + local MenuType = Mission.MenuType + local MenuStatus = self.MenuStatus + local MenuAbort = self.MenuAbort + local Menu = self.Menu + + Menu = Menu or {} + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + Menu[TaskGroupName] = nil + end + + MenuType = MenuType or {} + if MenuType[TaskGroupName] then + for _, Menu in pairs( MenuType[TaskGroupName] ) do + Menu:Remove() + end + MenuType[TaskGroupName] = nil + end + + MenuCategory = MenuCategory or {} + if MenuCategory[TaskGroupName] then + for _, Menu in pairs( MenuCategory[TaskGroupName] ) do + Menu:Remove() + end + MenuCategory[TaskGroupName] = nil + end + + MenuStatus = MenuStatus or {} + if MenuStatus[TaskGroupName] then + MenuStatus[TaskGroupName]:Remove() + MenuStatus[TaskGroupName] = nil + end + + MenuAbort = MenuAbort or {} + if MenuAbort[TaskGroupName] then + MenuAbort[TaskGroupName]:Remove() + MenuAbort[TaskGroupName] = nil + end + +end + +function TASK_BASE.MenuAssignToGroup( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + self:AssignToGroup( TaskGroup ) +end + +function TASK_BASE.MenuTaskStatus( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + +function TASK_BASE.MenuTaskAbort( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + + + +--- Returns the @{Task} name. +-- @param #TASK_BASE self +-- @return #string TaskName +function TASK_BASE:GetTaskName() + return self.TaskName +end + + +--- Add Process to @{Task} with key @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddProcess( TaskUnit, Process ) + local TaskUnitName = TaskUnit:GetName() + self.Processes = self.Processes or {} + self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} + self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process + return Process +end + + +--- Remove Processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + Process:StopEvents() + Process = nil + self.Processes[TaskUnitName][ProcessID] = nil + self:E( self.Processes[TaskUnitName][ProcessID] ) + end + self.Processes[TaskUnitName] = nil +end + +--- Fail processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:FailProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + self:E( { "Failing process: ", Process } ) + Process.Fsm:Fail() + end +end + +--- Add a FiniteStateMachine to @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) + local TaskUnitName = TaskUnit:GetName() + self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} + self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm + return Fsm +end + +--- Remove FiniteStateMachines from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveStateMachines( TaskUnitName ) + + for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do + Fsm = nil + self.Fsm[TaskUnitName][_] = nil + self:E( self.Fsm[TaskUnitName][_] ) + end + self.Fsm[TaskUnitName] = nil +end + +--- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:HasStateMachine( TaskUnitName ) + + self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) + return ( self.Fsm[TaskUnitName] ~= nil ) +end + + + + + +--- Register a potential new assignment for a new spawned @{Unit}. +-- Tasks only get assigned if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventAssignUnit( Event ) + if Event.IniUnit then + self:F( Event ) + local TaskUnit = Event.IniUnit + if TaskUnit:IsAlive() then + local TaskPlayerName = TaskUnit:GetPlayerName() + if TaskPlayerName ~= nil then + if not self:HasStateMachine( TaskUnit ) then + -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. + local TaskGroup = TaskUnit:GetGroup() + if self:IsAssignedToGroup( TaskGroup ) then + self:AssignToUnit( TaskUnit ) + end + end + end + end + end + return nil +end + +--- Catches the "player leave unit" event for a @{Unit} .... +-- When a player is an air unit, and leaves the unit: +-- +-- * and he is not at an airbase runway on the ground, he will fail its task. +-- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. +-- This is important to model the change from plane types for a player during mission assignment. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventPlayerLeaveUnit( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + if TaskUnit:IsAir() then + if TaskUnit:IsAboveRunway() then + -- do nothing + else + self:E( "IsNotAboveRunway" ) + -- Player left airplane during an assigned task and was not at an airbase. + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + end + end + + end + return nil +end + +--- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... +-- There are only assignments if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventDead( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + + local TaskGroup = Event.IniUnit:GetGroup() + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + end + return nil +end + +--- Gets the Scoring of the task +-- @param #TASK_BASE self +-- @return Scoring#SCORING Scoring +function TASK_BASE:GetScoring() + return self.Mission:GetScoring() +end + + +--- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. +-- @param #TASK_BASE self +-- @return #string The Task ID +function TASK_BASE:GetTaskIndex() + + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + local TaskName = self:GetName() + + return TaskCategory .. "." ..TaskType .. "." .. TaskName +end + +--- Sets the Name of the Task +-- @param #TASK_BASE self +-- @param #string TaskName +function TASK_BASE:SetName( TaskName ) + self.TaskName = TaskName +end + +--- Gets the Name of the Task +-- @param #TASK_BASE self +-- @return #string The Task Name +function TASK_BASE:GetName() + return self.TaskName +end + +--- Sets the Type of the Task +-- @param #TASK_BASE self +-- @param #string TaskType +function TASK_BASE:SetType( TaskType ) + self.TaskType = TaskType +end + +--- Gets the Type of the Task +-- @param #TASK_BASE self +-- @return #string TaskType +function TASK_BASE:GetType() + return self.TaskType +end + +--- Sets the Category of the Task +-- @param #TASK_BASE self +-- @param #string TaskCategory +function TASK_BASE:SetCategory( TaskCategory ) + self.TaskCategory = TaskCategory +end + +--- Gets the Category of the Task +-- @param #TASK_BASE self +-- @return #string TaskCategory +function TASK_BASE:GetCategory() + return self.TaskCategory +end + +--- Sets the ID of the Task +-- @param #TASK_BASE self +-- @param #string TaskID +function TASK_BASE:SetID( TaskID ) + self.TaskID = TaskID +end + +--- Gets the ID of the Task +-- @param #TASK_BASE self +-- @return #string TaskID +function TASK_BASE:GetID() + return self.TaskID +end + + +--- Sets a @{Task} to status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:StateSuccess() + self:SetState( self, "State", "Success" ) + return self +end + +--- Is the @{Task} status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateSuccess() + return self:GetStateString() == "Success" +end + +--- Sets a @{Task} to status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:StateFailed() + self:SetState( self, "State", "Failed" ) + return self +end + +--- Is the @{Task} status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateFailed() + return self:GetStateString() == "Failed" +end + +--- Sets a @{Task} to status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:StatePlanned() + self:SetState( self, "State", "Planned" ) + return self +end + +--- Is the @{Task} status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStatePlanned() + return self:GetStateString() == "Planned" +end + +--- Sets a @{Task} to status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:StateAssigned() + self:SetState( self, "State", "Assigned" ) + return self +end + +--- Is the @{Task} status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateAssigned() + return self:GetStateString() == "Assigned" +end + +--- Sets a @{Task} to status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:StateHold() + self:SetState( self, "State", "Hold" ) + return self +end + +--- Is the @{Task} status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateHold() + return self:GetStateString() == "Hold" +end + +--- Sets a @{Task} to status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:StateReplanned() + self:SetState( self, "State", "Replanned" ) + return self +end + +--- Is the @{Task} status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateReplanned() + return self:GetStateString() == "Replanned" +end + +--- Gets the @{Task} status. +-- @param #TASK_BASE self +function TASK_BASE:GetStateString() + return self:GetState( self, "State" ) +end + +--- Sets a @{Task} briefing. +-- @param #TASK_BASE self +-- @param #string TaskBriefing +-- @return #TASK_BASE self +function TASK_BASE:SetBriefing( TaskBriefing ) + self.TaskBriefing = TaskBriefing + return self +end + + + +--- Adds a score for the TASK to be achieved. +-- @param #TASK_BASE self +-- @param #string TaskStatus is the status of the TASK when the score needs to be given. +-- @param #string ScoreText is a text describing the score that is given according the status. +-- @param #number Score is a number providing the score of the status. +-- @return #TASK_BASE self +function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) + self:F2( { TaskStatus, ScoreText, Score } ) + + self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} + self.Scores[TaskStatus].ScoreText = ScoreText + self.Scores[TaskStatus].Score = Score + return self +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) + + self:E("Assigned") + + local TaskGroup = TaskUnit:GetGroup() + + TaskGroup:Message( self.TaskBriefing, 20 ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + +end + + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) + + self:E("Success") + + self:UnAssignFromGroups() + + local TaskGroup = TaskUnit:GetGroup() + self.Mission:SetPlannedMenu() + + self:StateSuccess() + + -- The task has become successful, the event catchers can be cleaned. + self:CleanUp() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) + + self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) + + -- A task cannot be "failed", so a task will always be there waiting for players to join. + -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. + -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. + + self:UnAssignFromGroups() + self:StatePlanned() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) + + if self:IsTrace() then + MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() + end + + self:E( { Event, From, To } ) + self:SetState( self, "State", To ) + + if self.Scores[To] then + local Scoring = self:GetScoring() + if Scoring then + Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + +end + + +--- @param #TASK_BASE self +function TASK_BASE:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self +end + + +--- @param #TASK_BASE self +function TASK_BASE._Scheduler() + self:F2() return true end + + +--- This module contains the TASK_SEAD classes. +-- +-- 1) @{#TASK_SEAD} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_SEAD} class defines a new SEAD task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_SEAD is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_SEAD + + +do -- TASK_SEAD + + --- The TASK_SEAD class + -- @type TASK_SEAD + -- @field Set#SET_UNIT TargetSetUnit + -- @extends Task#TASK_BASE + TASK_SEAD = { + ClassName = "TASK_SEAD", + } + + --- Instantiates a new TASK_SEAD. + -- @param #TASK_SEAD self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_SEAD self + function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "SEAD", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_SEAD. + -- @param #TASK_SEAD self + -- @return #nil + function TASK_SEAD:CleanUp() + + self:GetParent(self):CleanUp() + + return nil + end + + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_SEAD self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_SEAD self + function TASK_SEAD:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "SEAD", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a radar", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a radar", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a radar", -100 ) + self:AddScore( "Success", "Destroyed all target radars", 250 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_SEAD self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_SEAD:OnNext( Fsm, Event, From, To ) + + self:SetState( self, "State", To ) + + end + + + --- @param #TASK_SEAD self + function TASK_SEAD:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + --- @param #TASK_SEAD self + function TASK_SEAD:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_SEAD self + function TASK_SEAD._Scheduler() + self:F2() + + return true + end + +end +--- This module contains the TASK_CAS classes. +-- +-- 1) @{#TASK_CAS} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_CAS} class defines a new CAS task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_CAS is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_CAS + + +do -- TASK_CAS + + --- The TASK_CAS class + -- @type TASK_CAS + -- @extends Task#TASK_BASE + TASK_CAS = { + ClassName = "TASK_CAS", + } + + --- Instantiates a new TASK_CAS. + -- @param #TASK_CAS self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_CAS self + function TASK_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "CAS", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_CAS. + -- @param #TASK_CAS self + -- @return #nil + function TASK_CAS:CleanUp() + + self:GetParent( self ):CleanUp() + + return nil + end + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_CAS self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_CAS self + function TASK_CAS:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "CAS", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a ground unit", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_CAS self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_CAS:OnNext( Fsm, Event, From, To, Event ) + + self:SetState( self, "State", To ) + + end + + --- @param #TASK_CAS self + function TASK_CAS:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + + --- @param #TASK_CAS self + function TASK_CAS:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_CAS self + function TASK_CAS._Scheduler() + self:F2() + + return true + end + +end + + + +--- This module contains the TASK_BAI classes. +-- +-- 1) @{#TASK_BAI} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_BAI} class defines a new BAI task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_BAI is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_BAI + + +do -- TASK_BAI + + --- The TASK_BAI class + -- @type TASK_BAI + -- @extends Task#TASK_BASE + TASK_BAI = { + ClassName = "TASK_BAI", + } + + --- Instantiates a new TASK_BAI. + -- @param #TASK_BAI self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_BAI self + function TASK_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "BAI", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_BAI. + -- @param #TASK_BAI self + -- @return #nil + function TASK_BAI:CleanUp() + + self:GetParent( self ):CleanUp() + + return nil + end + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_BAI self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_BAI self + function TASK_BAI:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "BAI", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a ground unit", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_BAI self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_BAI:OnNext( Fsm, Event, From, To, Event ) + + self:SetState( self, "State", To ) + + end + + --- @param #TASK_BAI self + function TASK_BAI:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + + --- @param #TASK_BAI self + function TASK_BAI:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_BAI self + function TASK_BAI._Scheduler() + self:F2() + + return true + end + +end + + + + BASE:TraceOnOff( false ) env.info( '*** MOOSE INCLUDE END *** ' ) diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index a28bfb295..c88217738 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20160712_0855' ) +env.info( 'Moose Generation Timestamp: 20160719_1818' ) local base = _G Include = {} @@ -254,22 +254,6 @@ end --- From http://lua-users.org/wiki/SimpleRound --- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place -routines.utils.round = function(num, idp) - local mult = 10^(idp or 0) - return math.floor(num * mult + 0.5) / mult -end - --- porting in Slmod's dostring -routines.utils.dostring = function(s) - local f, err = loadstring(s) - if f then - return true, f() - else - return false, err - end -end --3D Vector manipulation @@ -2511,6 +2495,288 @@ end env.info(( 'Init: Scripts Loaded v1.1' )) + +--- @type SMOKECOLOR +-- @field Green +-- @field Red +-- @field White +-- @field Orange +-- @field Blue + +SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR + +--- @type FLARECOLOR +-- @field Green +-- @field Red +-- @field White +-- @field Yellow + +FLARECOLOR = trigger.flareColor -- #FLARECOLOR + +--- Utilities static class. +-- @type UTILS +UTILS = {} + + +--from http://lua-users.org/wiki/CopyTable +UTILS.DeepCopy = function(object) + local lookup_table = {} + local function _copy(object) + if type(object) ~= "table" then + return object + elseif lookup_table[object] then + return lookup_table[object] + end + local new_table = {} + lookup_table[object] = new_table + for index, value in pairs(object) do + new_table[_copy(index)] = _copy(value) + end + return setmetatable(new_table, getmetatable(object)) + end + local objectreturn = _copy(object) + return objectreturn +end + + +-- porting in Slmod's serialize_slmod2 +UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function + + lookup_table = {} + + local function _Serialize( tbl ) + + if type(tbl) == 'table' then --function only works for tables! + + if lookup_table[tbl] then + return lookup_table[object] + end + + local tbl_str = {} + + lookup_table[tbl] = tbl_str + + tbl_str[#tbl_str + 1] = '{' + + for ind,val in pairs(tbl) do -- serialize its fields + local ind_str = {} + if type(ind) == "number" then + ind_str[#ind_str + 1] = '[' + ind_str[#ind_str + 1] = tostring(ind) + ind_str[#ind_str + 1] = ']=' + else --must be a string + ind_str[#ind_str + 1] = '[' + ind_str[#ind_str + 1] = routines.utils.basicSerialize(ind) + ind_str[#ind_str + 1] = ']=' + end + + local val_str = {} + if ((type(val) == 'number') or (type(val) == 'boolean')) then + val_str[#val_str + 1] = tostring(val) + val_str[#val_str + 1] = ',' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'string' then + val_str[#val_str + 1] = routines.utils.basicSerialize(val) + val_str[#val_str + 1] = ',' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'nil' then -- won't ever happen, right? + val_str[#val_str + 1] = 'nil,' + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + elseif type(val) == 'table' then + if ind == "__index" then + -- tbl_str[#tbl_str + 1] = "__index" + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + else + + val_str[#val_str + 1] = _Serialize(val) + val_str[#val_str + 1] = ',' --I think this is right, I just added it + tbl_str[#tbl_str + 1] = table.concat(ind_str) + tbl_str[#tbl_str + 1] = table.concat(val_str) + end + elseif type(val) == 'function' then + -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + else +-- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) +-- env.info( debug.traceback() ) + end + + end + tbl_str[#tbl_str + 1] = '}' + return table.concat(tbl_str) + else + return tostring(tbl) + end + end + + local objectreturn = _Serialize(tbl) + return objectreturn +end + +--porting in Slmod's "safestring" basic serialize +UTILS.BasicSerialize = function(s) + if s == nil then + return "\"\"" + else + if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'table') or (type(s) == 'userdata') ) then + return tostring(s) + elseif type(s) == 'string' then + s = string.format('%q', s) + return s + end + end +end + + +UTILS.ToDegree = function(angle) + return angle*180/math.pi +end + +UTILS.ToRadian = function(angle) + return angle*math.pi/180 +end + +UTILS.MetersToNM = function(meters) + return meters/1852 +end + +UTILS.MetersToFeet = function(meters) + return meters/0.3048 +end + +UTILS.NMToMeters = function(NM) + return NM*1852 +end + +UTILS.FeetToMeters = function(feet) + return feet*0.3048 +end + +UTILS.MpsToKnots = function(mps) + return mps*3600/1852 +end + +UTILS.MpsToKmph = function(mps) + return mps*3.6 +end + +UTILS.KnotsToMps = function(knots) + return knots*1852/3600 +end + +UTILS.KmphToMps = function(kmph) + return kmph/3.6 +end + +--[[acc: +in DM: decimal point of minutes. +In DMS: decimal point of seconds. +position after the decimal of the least significant digit: +So: +42.32 - acc of 2. +]] +UTILS.tostringLL = function( lat, lon, acc, DMS) + + local latHemi, lonHemi + if lat > 0 then + latHemi = 'N' + else + latHemi = 'S' + end + + if lon > 0 then + lonHemi = 'E' + else + lonHemi = 'W' + end + + lat = math.abs(lat) + lon = math.abs(lon) + + local latDeg = math.floor(lat) + local latMin = (lat - latDeg)*60 + + local lonDeg = math.floor(lon) + local lonMin = (lon - lonDeg)*60 + + if DMS then -- degrees, minutes, and seconds. + local oldLatMin = latMin + latMin = math.floor(latMin) + local latSec = UTILS.Round((oldLatMin - latMin)*60, acc) + + local oldLonMin = lonMin + lonMin = math.floor(lonMin) + local lonSec = UTILS.Round((oldLonMin - lonMin)*60, acc) + + if latSec == 60 then + latSec = 0 + latMin = latMin + 1 + end + + if lonSec == 60 then + lonSec = 0 + lonMin = lonMin + 1 + end + + local secFrmtStr -- create the formatting string for the seconds place + if acc <= 0 then -- no decimal place. + secFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end + + return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' + .. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi + + else -- degrees, decimal minutes. + latMin = UTILS.Round(latMin, acc) + lonMin = UTILS.Round(lonMin, acc) + + if latMin == 60 then + latMin = 0 + latDeg = latDeg + 1 + end + + if lonMin == 60 then + lonMin = 0 + lonDeg = lonDeg + 1 + end + + local minFrmtStr -- create the formatting string for the minutes place + if acc <= 0 then -- no decimal place. + minFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end + + return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' + .. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi + + end +end + + +--- From http://lua-users.org/wiki/SimpleRound +-- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place +function UTILS.Round( num, idp ) + local mult = 10 ^ ( idp or 0 ) + return math.floor( num * mult + 0.5 ) / mult +end + +-- porting in Slmod's dostring +function UTILS.DoString( s ) + local f, err = loadstring( s ) + if f then + return true, f() + else + return false, err + end +end --- This module contains the BASE class. -- -- 1) @{#BASE} class @@ -2568,8 +2834,9 @@ env.info(( 'Init: Scripts Loaded v1.1' )) -- -- ==== -- +-- ### Author: FlightControl +-- -- @module Base --- @author FlightControl @@ -2628,7 +2895,6 @@ function BASE:New() self.__index = self _ClassID = _ClassID + 1 self.ClassID = _ClassID - self.ClassNameAndID = string.format( '%s#%09d', self.ClassName, self.ClassID ) return self end @@ -2645,8 +2911,7 @@ function BASE:Inherit( Child, Parent ) setmetatable( Child, Parent ) Child.__index = Child end - --Child.ClassName = Child.ClassName .. '.' .. Child.ClassID - self:T( 'Inherited from ' .. Parent.ClassName ) + --self:T( 'Inherited from ' .. Parent.ClassName ) return Child end @@ -2654,7 +2919,7 @@ end -- @param #BASE self -- @param #BASE Child is the Child class from which the Parent class needs to be retrieved. -- @return #BASE -function BASE:Inherited( Child ) +function BASE:GetParent( Child ) local Parent = getmetatable( Child ) -- env.info('Inherited class of ' .. Child.ClassName .. ' is ' .. Parent.ClassName ) return Parent @@ -2665,7 +2930,7 @@ end -- @param #BASE self -- @return #string The ClassName + ClassID of the class instance. function BASE:GetClassNameAndID() - return self.ClassNameAndID + return string.format( '%s#%09d', self.ClassName, self.ClassID ) end --- Get the ClassName of the class instance. @@ -2852,11 +3117,9 @@ function BASE:SetState( Object, StateName, State ) local ClassNameAndID = Object:GetClassNameAndID() - if not self.States[ClassNameAndID] then - self.States[ClassNameAndID] = {} - end + self.States[ClassNameAndID] = self.States[ClassNameAndID] or {} self.States[ClassNameAndID][StateName] = State - self:F2( { ClassNameAndID, StateName, State } ) + self:T2( { ClassNameAndID, StateName, State } ) return self.States[ClassNameAndID][StateName] end @@ -2867,7 +3130,7 @@ function BASE:GetState( Object, StateName ) if self.States[ClassNameAndID] then local State = self.States[ClassNameAndID][StateName] - self:F2( { ClassNameAndID, StateName, State } ) + self:T2( { ClassNameAndID, StateName, State } ) return State end @@ -2892,7 +3155,7 @@ end -- When Moose is loaded statically, (as one file), tracing is switched off by default. -- So tracing must be switched on manually in your mission if you are using Moose statically. -- When moose is loading dynamically (for moose class development), tracing is switched on by default. --- @param BASE self +-- @param #BASE self -- @param #boolean TraceOnOff Switch the tracing on or off. -- @usage -- -- Switch the tracing On @@ -2904,6 +3167,19 @@ function BASE:TraceOnOff( TraceOnOff ) _TraceOnOff = TraceOnOff end + +--- Enquires if tracing is on (for the class). +-- @param #BASE self +-- @return #boolean +function BASE:IsTrace() + + if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + return true + else + return false + end +end + --- Set trace level -- @param #BASE self -- @param #number Level @@ -3490,7 +3766,7 @@ end -- @param Positionable#POSITIONABLE self -- @return DCSTypes#Vec2 The 2D point vector of the DCS Positionable. -- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetPointVec2() +function POSITIONABLE:GetVec2() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() @@ -3510,6 +3786,30 @@ function POSITIONABLE:GetPointVec2() end +--- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the DCS Positionable within the mission. +-- @param Positionable#POSITIONABLE self +-- @return DCSTypes#Vec3 The 3D point vector of the DCS Positionable. +-- @return #nil The DCS Positionable is not existing or alive. +function POSITIONABLE:GetRandomPointVec3( Radius ) + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableRandomPointVec3 = {} + local angle = math.random() * math.pi*2; + PositionableRandomPointVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; + PositionableRandomPointVec3.y = PositionablePointVec3.y + PositionableRandomPointVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; + + self:T3( PositionableRandomPointVec3 ) + return PositionableRandomPointVec3 + end + + return nil +end + --- Returns the @{DCSTypes#Vec3} vector indicating the point in 3D of the DCS Positionable within the mission. -- @param Positionable#POSITIONABLE self -- @return DCSTypes#Vec3 The 3D point vector of the DCS Positionable. @@ -3556,7 +3856,7 @@ function POSITIONABLE:IsAboveRunway() if DCSPositionable then - local PointVec2 = self:GetPointVec2() + local PointVec2 = self:GetVec2() local SurfaceType = land.getSurfaceType( PointVec2 ) local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY @@ -3628,6 +3928,27 @@ function POSITIONABLE:GetVelocity() return nil end +--- Returns the @{Unit#UNIT} velocity in km/h. +-- @param Positionable#POSITIONABLE self +-- @return #number The velocity in km/h +-- @return #nil The DCS Positionable is not existing or alive. +function POSITIONABLE:GetVelocityKMH() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local VelocityVec3 = self:GetVelocity() + local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec + local Velocity = Velocity * 3.6 -- now it is in km/h. + self:T3( Velocity ) + return Velocity + end + + return nil +end + + --- This module contains the CONTROLLABLE class. @@ -4232,7 +4553,7 @@ function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) local DCSControllable = self:GetDCSObject() if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) end @@ -4409,7 +4730,7 @@ function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) if RandomPoint then Point = Zone:GetRandomVec2() else - Point = Zone:GetPointVec2() + Point = Zone:GetVec2() end local DCSTask = self:TaskLandAtVec2( Point, Duration ) @@ -5009,7 +5330,7 @@ end function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) self:F2( { Point, Speed } ) - local ControllablePoint = self:GetUnit( 1 ):GetPointVec2() + local ControllablePoint = self:GetUnit( 1 ):GetVec2() local PointFrom = {} PointFrom.x = ControllablePoint.x @@ -5148,7 +5469,7 @@ function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() local PointFrom = {} PointFrom.x = ControllablePoint.x @@ -5164,7 +5485,7 @@ function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) if Randomize then ZonePoint = Zone:GetRandomVec2() else - ZonePoint = Zone:GetPointVec2() + ZonePoint = Zone:GetVec2() end PointTo.x = ZonePoint.x @@ -5246,7 +5567,7 @@ function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) if DCSControllable then - local ControllablePoint = self:GetPointVec2() + local ControllablePoint = self:GetVec2() local ControllableVelocity = self:GetMaxVelocity() local PointFrom = {} @@ -5258,7 +5579,7 @@ function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) local PointTo = {} - local AirbasePoint = ReturnAirbase:GetPointVec2() + local AirbasePoint = ReturnAirbase:GetVec2() PointTo.x = AirbasePoint.x PointTo.y = AirbasePoint.y @@ -5926,6 +6247,7 @@ function SCHEDULER:Stop() self.Repeat = false if self.ScheduleID then + self:E( "Stop Schedule" ) timer.removeFunction( self.ScheduleID ) end self.ScheduleID = nil @@ -6093,6 +6415,18 @@ function EVENT:Init( EventID, EventClass ) return self.Events[EventID][EventClass] end +--- Removes an Events entry +-- @param #EVENT self +-- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param DCSWorld#world.event EventID +-- @return #EVENT.Events +function EVENT:Remove( EventSelf, EventID ) + self:F3( { EventSelf, _EVENTCODES[EventID] } ) + + local EventClass = EventSelf:GetClassNameAndID() + self.Events[EventID][EventClass] = nil +end + --- Create an OnDead event handler for a group -- @param #EVENT self @@ -6146,328 +6480,533 @@ function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, Event return self end +do -- OnBirth ---- Create an OnBirth event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + --- Create an OnBirth event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnBirth( EventFunction, EventSelf ) - self:F2() + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + + return self + end - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirth( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end - return self -end + --- Set a new listener for an S_EVENT_BIRTH event. + -- @param #EVENT self + -- @param #string EventDCSUnitName The id of the unit for the event to be handled. + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end ---- Set a new listener for an S_EVENT_BIRTH event. --- @param #EVENT self --- @param #string EventDCSUnitName The id of the unit for the event to be handled. --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) + --- Stop listening to S_EVENT_BIRTH event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnBirthRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_BIRTH ) + + return self + end - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + +end + +do -- OnCrash + + --- Create an OnCrash event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Create an OnCrash event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) - - return self -end - ---- Set a new listener for an S_EVENT_CRASH event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnCrash( EventFunction, EventSelf ) - self:F2() + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + return self + end - return self -end - ---- Set a new listener for an S_EVENT_CRASH event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) + --- Set a new listener for an S_EVENT_CRASH event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnCrash( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + + return self + end - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param Group#GROUP EventGroup --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) + --- Set a new listener for an S_EVENT_CRASH event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) + return self + end + + --- Stop listening to S_EVENT_CRASH event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnCrashRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_CRASH ) + + return self + end - return self end ---- Set a new listener for an S_EVENT_DEAD event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf --- @return #EVENT -function EVENT:OnDead( EventFunction, EventSelf ) - self:F2() +do -- OnDead + + --- Create an OnDead event handler for a group + -- @param #EVENT self + -- @param Group#GROUP EventGroup + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + return self + end - return self -end - - ---- Set a new listener for an S_EVENT_DEAD event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + --- Set a new listener for an S_EVENT_DEAD event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnDead( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_PILOT_DEAD event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) - return self -end - ---- Set a new listener for an S_EVENT_LAND event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) - - return self -end - ---- Set a new listener for an S_EVENT_TAKEOFF event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) - - return self -end - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + --- Set a new listener for an S_EVENT_DEAD event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self -end - ---- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_ENGINE_STARTUP event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + --- Stop listening to S_EVENT_DEAD event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnDeadRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_DEAD ) + + return self + end - return self + end ---- Set a new listener for an S_EVENT_SHOT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnShot( EventFunction, EventSelf ) - self:F2() +do -- OnPilotDead - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + --- Set a new listener for an S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPilotDead( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_SHOT event for a unit. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + --- Set a new listener for an S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self -end - ---- Set a new listener for an S_EVENT_HIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnHit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) - return self + return self + end + + --- Stop listening to S_EVENT_PILOT_DEAD event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPilotDeadRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PILOT_DEAD ) + + return self + end + end ---- Set a new listener for an S_EVENT_HIT event. --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) +do -- OnLand + --- Create an OnLand event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) - return self -end - ---- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) + + return self + end - return self -end - ---- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @return #EVENT -function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + --- Set a new listener for an S_EVENT_LAND event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) - return self + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) + + return self + end + + --- Stop listening to S_EVENT_LAND event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnLandRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_LAND ) + + return self + end + + end +do -- OnTakeOff + --- Create an OnTakeOff event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) + + return self + end + + --- Set a new listener for an S_EVENT_TAKEOFF event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) + + return self + end + + --- Stop listening to S_EVENT_TAKEOFF event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnTakeOffRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_TAKEOFF ) + + return self + end + + +end + +do -- OnEngineShutDown + + --- Create an OnDead event handler for a group + -- @param #EVENT self + -- @param #table EventTemplate + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) + self:F2( EventTemplate.name ) + + self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + + return self + end + + --- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self + end + + --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnEngineShutDownRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self + end + +end + +do -- OnEngineStartUp + + --- Set a new listener for an S_EVENT_ENGINE_STARTUP event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + + return self + end + + --- Stop listening to S_EVENT_ENGINE_STARTUP event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnEngineStartUpRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + + return self + end + +end + +do -- OnShot + --- Set a new listener for an S_EVENT_SHOT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnShot( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + --- Set a new listener for an S_EVENT_SHOT event for a unit. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + --- Stop listening to S_EVENT_SHOT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnShotRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_SHOT ) + + return self + end + + +end + +do -- OnHit + + --- Set a new listener for an S_EVENT_HIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnHit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + + return self + end + + --- Set a new listener for an S_EVENT_HIT event. + -- @param #EVENT self + -- @param #string EventDCSUnitName + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) + self:F2( EventDCSUnitName ) + + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) + + return self + end + + --- Stop listening to S_EVENT_HIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnHitRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_HIT ) + + return self + end + +end + +do -- OnPlayerEnterUnit + + --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self + end + + --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPlayerEnterRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self + end + +end + +do -- OnPlayerLeaveUnit + --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. + -- @param #EVENT self + -- @param #function EventFunction The function to be called when the event occurs for the unit. + -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @return #EVENT + function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) + self:F2() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self + end + + --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. + -- @param #EVENT self + -- @param Base#BASE EventSelf + -- @return #EVENT + function EVENT:OnPlayerLeaveRemove( EventSelf ) + self:F2() + + self:Remove( EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self + end + +end + + --- @param #EVENT self -- @param #EVENTDATA Event function EVENT:onEvent( Event ) - self:F2( { _EVENTCODES[Event.id], Event } ) if self and self.Events and self.Events[Event.id] then if Event.initiator and Event.initiator:getCategory() == Object.Category.UNIT then @@ -6501,16 +7040,21 @@ function EVENT:onEvent( Event ) end self:E( { _EVENTCODES[Event.id], Event.IniUnitName, Event.TgtUnitName, Event.WeaponName } ) for ClassName, EventData in pairs( self.Events[Event.id] ) do + self:T( { "Evaluating class ", { EventData.EventSelf:GetClassNameAndID(), ClassName } } ) if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then - self:E( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) + self:T( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventSelf, Event ) else if Event.IniDCSUnit and not EventData.IniUnit then - self:E( { "Calling event function for class ", ClassName } ) - EventData.EventFunction( EventData.EventSelf, Event ) + if ClassName == EventData.EventSelf:GetClassNameAndID() then + self:T( { "Calling event function for class ", ClassName } ) + EventData.EventFunction( EventData.EventSelf, Event ) + end end end end + else + self:E( { _EVENTCODES[Event.id], Event } ) end end @@ -6586,180 +7130,365 @@ function SUBMENU:New( MenuText, ParentMenu ) return Child end --- This local variable is used to cache the menus registered under clients. --- Menus don't dissapear when clients are destroyed and restarted. --- So every menu for a client created must be tracked so that program logic accidentally does not create --- the same menus twice during initialization logic. --- These menu classes are handling this logic with this variable. -local _MENUCLIENTS = {} +do ---- The MENU_CLIENT class --- @type MENU_CLIENT --- @extends Menu#MENU -MENU_CLIENT = { - ClassName = "MENU_CLIENT" -} - ---- Creates a new menu item for a group --- @param self --- @param Client#CLIENT MenuClient The Client owning the menu. --- @param #string MenuText The text for the menu. --- @param #table ParentMenu The parent menu. --- @return #MENU_CLIENT self -function MENU_CLIENT:New( MenuClient, MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { MenuClient, MenuText, ParentMenu } ) - - self.MenuClient = MenuClient - self.MenuClientGroupID = MenuClient:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu + -- This local variable is used to cache the menus registered under clients. + -- Menus don't dissapear when clients are destroyed and restarted. + -- So every menu for a client created must be tracked so that program logic accidentally does not create + -- the same menus twice during initialization logic. + -- These menu classes are handling this logic with this variable. + local _MENUCLIENTS = {} - self.Menus = {} - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} + --- The MENU_CLIENT class + -- @type MENU_CLIENT + -- @extends Menu#MENU + MENU_CLIENT = { + ClassName = "MENU_CLIENT" + } + + --- Creates a new menu item for a group + -- @param self + -- @param Client#CLIENT MenuClient The Client owning the menu. + -- @param #string MenuText The text for the menu. + -- @param #table ParentMenu The parent menu. + -- @return #MENU_CLIENT self + function MENU_CLIENT:New( MenuClient, MenuText, ParentMenu ) + + -- Arrange meta tables + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + self:F( { MenuClient, MenuText, ParentMenu } ) + + self.MenuClient = MenuClient + self.MenuClientGroupID = MenuClient:GetClientGroupID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self.Menus = {} + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + end + + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) + MenuPath[MenuPathID] = self.MenuPath + + self:T( { MenuClient:GetClientGroupName(), self.MenuPath } ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end + return self end - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + --- Removes the sub menus recursively of this MENU_CLIENT. + -- @param #MENU_CLIENT self + -- @return #MENU_CLIENT self + function MENU_CLIENT:RemoveSubMenus() + self:F( self.MenuPath ) + + for MenuID, Menu in pairs( self.Menus ) do + Menu:Remove() + end + end + + --- Removes the sub menus recursively of this MENU_CLIENT. + -- @param #MENU_CLIENT self + -- @return #MENU_CLIENT self + function MENU_CLIENT:Remove() + self:F( self.MenuPath ) + + self:RemoveSubMenus() + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil + end + + + --- The MENU_CLIENT_COMMAND class + -- @type MENU_CLIENT_COMMAND + -- @extends Menu#MENU + MENU_CLIENT_COMMAND = { + ClassName = "MENU_CLIENT_COMMAND" + } + + --- Creates a new radio command item for a group + -- @param self + -- @param Client#CLIENT MenuClient The Client owning the menu. + -- @param MenuText The text for the menu. + -- @param ParentMenu The parent menu. + -- @param CommandMenuFunction A function that is called when the menu key is pressed. + -- @param CommandMenuArgument An argument for the function. + -- @return Menu#MENU_CLIENT_COMMAND self + function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) + + -- Arrange meta tables + + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + + self.MenuClient = MenuClient + self.MenuClientGroupID = MenuClient:GetClientGroupID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) + end + + self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) + MenuPath[MenuPathID] = self.MenuPath + + self.CommandMenuFunction = CommandMenuFunction + self.CommandMenuArgument = CommandMenuArgument + + ParentMenu.Menus[self.MenuPath] = self + + return self + end + + function MENU_CLIENT_COMMAND:Remove() + self:F( self.MenuPath ) + + if not _MENUCLIENTS[self.MenuClientGroupID] then + _MENUCLIENTS[self.MenuClientGroupID] = {} + end + + local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil + end +end - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath +--- MENU_GROUP - self:T( { MenuClient:GetClientGroupName(), self.MenuPath } ) +do + -- This local variable is used to cache the menus registered under clients. + -- Menus don't dissapear when clients are destroyed and restarted. + -- So every menu for a client created must be tracked so that program logic accidentally does not create + -- the same menus twice during initialization logic. + -- These menu classes are handling this logic with this variable. + local _MENUGROUPS = {} - if ParentMenu and ParentMenu.Menus then + --- The MENU_GROUP class + -- @type MENU_GROUP + -- @extends Menu#MENU + MENU_GROUP = { + ClassName = "MENU_GROUP" + } + + --- Creates a new menu item for a group + -- @param self + -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param #string MenuText The text for the menu. + -- @param #table ParentMenu The parent menu. + -- @return #MENU_GROUP self + function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) + + -- Arrange meta tables + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + self:F( { MenuGroup, MenuText, ParentMenu } ) + + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self.Menus = {} + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) + end + + self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, MenuParentPath ) + MenuPath[MenuPathID] = self.MenuPath + + self:T( { self.MenuGroupID, self.MenuPath } ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end + return self + end + + --- Removes the sub menus recursively of this MENU_GROUP. + -- @param #MENU_GROUP self + -- @return #MENU_GROUP self + function MENU_GROUP:RemoveSubMenus() + self:F( self.MenuPath ) + + for MenuID, Menu in pairs( self.Menus ) do + Menu:Remove() + end + + end + + --- Removes the sub menus recursively of this MENU_GROUP. + -- @param #MENU_GROUP self + -- @return #MENU_GROUP self + function MENU_GROUP:Remove() + self:F( self.MenuPath ) + + self:RemoveSubMenus() + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + return nil + end + + + --- The MENU_GROUP_COMMAND class + -- @type MENU_GROUP_COMMAND + -- @extends Menu#MENU + MENU_GROUP_COMMAND = { + ClassName = "MENU_GROUP_COMMAND" + } + + --- Creates a new radio command item for a group + -- @param #MENU_GROUP_COMMAND self + -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param MenuText The text for the menu. + -- @param ParentMenu The parent menu. + -- @param CommandMenuFunction A function that is called when the menu key is pressed. + -- @param CommandMenuArgument An argument for the function. + -- @return Menu#MENU_GROUP_COMMAND self + function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) + + -- Arrange meta tables + + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuParentPath = MenuParentPath + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] + + self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) + + local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText + if MenuPath[MenuPathID] then + missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) + end + + self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) + self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) + MenuPath[MenuPathID] = self.MenuPath + + self.CommandMenuFunction = CommandMenuFunction + self.CommandMenuArgument = CommandMenuArgument + ParentMenu.Menus[self.MenuPath] = self + + return self end - return self -end + + function MENU_GROUP_COMMAND:Remove() + self:F( self.MenuPath ) + + if not _MENUGROUPS[self.MenuGroupID] then + _MENUGROUPS[self.MenuGroupID] = {} + end + + local MenuPath = _MENUGROUPS[self.MenuGroupID] ---- Removes the sub menus recursively of this MENU_CLIENT. --- @param #MENU_CLIENT self --- @return #MENU_CLIENT self -function MENU_CLIENT:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() + + if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then + MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + end + + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + return nil end end ---- Removes the sub menus recursively of this MENU_CLIENT. --- @param #MENU_CLIENT self --- @return #MENU_CLIENT self -function MENU_CLIENT:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil -end - - ---- The MENU_CLIENT_COMMAND class --- @type MENU_CLIENT_COMMAND --- @extends Menu#MENU -MENU_CLIENT_COMMAND = { - ClassName = "MENU_CLIENT_COMMAND" -} - ---- Creates a new radio command item for a group --- @param self --- @param Client#CLIENT MenuClient The Client owning the menu. --- @param MenuText The text for the menu. --- @param ParentMenu The parent menu. --- @param CommandMenuFunction A function that is called when the menu key is pressed. --- @param CommandMenuArgument An argument for the function. --- @return Menu#MENU_CLIENT_COMMAND self -function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - - self.MenuClient = MenuClient - self.MenuClientGroupID = MenuClient:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) - MenuPath[MenuPathID] = self.MenuPath - - self.CommandMenuFunction = CommandMenuFunction - self.CommandMenuArgument = CommandMenuArgument - - ParentMenu.Menus[self.MenuPath] = self - - return self -end - -function MENU_CLIENT_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil -end - - --- The MENU_COALITION class -- @type MENU_COALITION -- @extends Menu#MENU @@ -6769,11 +7498,11 @@ MENU_COALITION = { --- Creates a new coalition menu item -- @param #MENU_COALITION self --- @param DCSCoalition#coalition.side MenuCoalition The coalition owning the menu. +-- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_COALITION self -function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) +function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) -- Arrange meta tables local MenuParentPath = {} @@ -6782,9 +7511,9 @@ function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) end local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { MenuCoalition, MenuText, ParentMenu } ) + self:F( { Coalition, MenuText, ParentMenu } ) - self.MenuCoalition = MenuCoalition + self.Coalition = Coalition self.MenuParentPath = MenuParentPath self.MenuText = MenuText self.ParentMenu = ParentMenu @@ -6793,7 +7522,7 @@ function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu ) self:T( { MenuParentPath, MenuText } ) - self.MenuPath = missionCommands.addSubMenuForCoalition( self.MenuCoalition, MenuText, MenuParentPath ) + self.MenuPath = missionCommands.addSubMenuForCoalition( self.Coalition, MenuText, MenuParentPath ) self:T( { self.MenuPath } ) @@ -7360,12 +8089,12 @@ end --- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. -- @param #GROUP self -- @return DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. -function GROUP:GetPointVec2() +function GROUP:GetVec2() self:F2( self.GroupName ) local UnitPoint = self:GetUnit(1) - UnitPoint:GetPointVec2() - local GroupPointVec2 = UnitPoint:GetPointVec2() + UnitPoint:GetVec2() + local GroupPointVec2 = UnitPoint:GetVec2() self:T3( GroupPointVec2 ) return GroupPointVec2 end @@ -7757,7 +8486,7 @@ end -- @param #string Message The message text -- @param DCSTypes#Duration Duration The duration of the message. -- @return Message#MESSAGE -function GROUP:Message( Message, Duration ) +function GROUP:GetMessage( Message, Duration ) self:F2( { Message, Duration } ) local DCSGroup = self:GetDCSObject() @@ -7778,7 +8507,7 @@ function GROUP:MessageToAll( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToAll() + self:GetMessage( Message, Duration ):ToAll() end return nil @@ -7794,7 +8523,7 @@ function GROUP:MessageToRed( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToRed() + self:GetMessage( Message, Duration ):ToRed() end return nil @@ -7810,7 +8539,7 @@ function GROUP:MessageToBlue( Message, Duration ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToBlue() + self:GetMessage( Message, Duration ):ToBlue() end return nil @@ -7827,7 +8556,42 @@ function GROUP:MessageToClient( Message, Duration, Client ) local DCSGroup = self:GetDCSObject() if DCSGroup then - self:Message( Message, Duration ):ToClient( Client ) + self:GetMessage( Message, Duration ):ToClient( Client ) + end + + return nil +end + +--- Send a message to a @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #GROUP self +-- @param #string Message The message text +-- @param DCSTypes#Duration Duration The duration of the message. +-- @param Group#GROUP MessageGroup The GROUP object receiving the message. +function GROUP:MessageToGroup( Message, Duration, MsgGroup ) + self:F2( { Message, Duration } ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + if DCSGroup:isExist() then + self:GetMessage( Message, Duration ):ToGroup( MsgGroup ) + end + end + + return nil +end + +--- Send a message to the players in the @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #GROUP self +-- @param #string Message The message text +-- @param DCSTypes#Duration Duration The duration of the message. +function GROUP:Message( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + self:GetMessage( Message, Duration ):ToGroup( self ) end return nil @@ -7883,7 +8647,7 @@ end -- ----------------------------- -- The UNIT class provides methods to obtain the current point or position of the DCS Unit. -- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetPointVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including oriëntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. +-- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. -- -- 1.5) Test if alive -- ------------------ @@ -7943,6 +8707,14 @@ UNIT = { -- @field Orange -- @field Blue +--- Unit.SensorType +-- @type Unit.SensorType +-- @field OPTIC +-- @field RADAR +-- @field IRST +-- @field RWR + + -- Registration. --- Create a new UNIT from DCSUnit. @@ -8013,6 +8785,24 @@ function UNIT:IsActive() return nil end +--- Destroys the @{Unit}. +-- @param Unit#UNIT self +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:Destroy() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:destroy() + end + + return nil +end + + + --- Returns the Unit's callsign - the localized string. -- @param Unit#UNIT self -- @return #string The Callsign of the Unit. @@ -8150,6 +8940,46 @@ end -- Need to add here a function per sensortype -- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) +--- Returns if the unit has sensors of a certain type. +-- @param Unit#UNIT self +-- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSensors( ... ) + self:F2( arg ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) + return HasSensors + end + + return nil +end + +--- Returns if the unit is SEADable. +-- @param Unit#UNIT self +-- @return #boolean returns true if the unit is SEADable. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSEAD() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSEADAttributes = DCSUnit:getDesc().attributes + + local HasSEAD = false + if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or + UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then + HasSEAD = true + end + return HasSEAD + end + + return nil +end + --- Returns two values: -- -- * First value indicates if at least one of the unit's radar(s) is on. @@ -8222,7 +9052,47 @@ function UNIT:GetLife0() return nil end +--- Returns the Unit's A2G threat level on a scale from 1 to 10 ... +-- The following threat levels are foreseen: +-- +-- * Threat level 0: Unit is unarmed. +-- * Threat level 1: Unit is infantry. +-- * Threat level 2: Unit is an infantry vehicle. +-- * Threat level 3: Unit is ground artillery. +-- * Threat level 4: Unit is a tank. +-- * Threat level 5: Unit is a modern tank or ifv with ATGM. +-- * Threat level 6: Unit is a AAA. +-- * Threat level 7: Unit is a SAM or manpad, IR guided. +-- * Threat level 8: Unit is a Short Range SAM, radar guided. +-- * Threat level 9: Unit is a Medium Range SAM, radar guided. +-- * Threat level 10: Unit is a Long Range SAM, radar guided. +function UNIT:GetThreatLevel() + local Attributes = self:GetDesc().attributes + local ThreatLevel = 0 + + self:T2( Attributes ) + + if Attributes["LR SAM"] then ThreatLevel = 10 + elseif Attributes["MR SAM"] then ThreatLevel = 9 + elseif Attributes["SR SAM"] and + not Attributes["IR Guided SAM"] then ThreatLevel = 8 + elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and + Attributes["IR Guided SAM"] then ThreatLevel = 7 + elseif Attributes["AAA"] then ThreatLevel = 6 + elseif Attributes["Modern Tanks"] then ThreatLevel = 5 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + Attributes["ATGM"] then ThreatLevel = 4 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + not Attributes["ATGM"] then ThreatLevel = 3 + elseif Attributes["Old Tanks"] or Attributes["APC"] then ThreatLevel = 2 + elseif Attributes["Infantry"] then ThreatLevel = 1 + end + + self:T2( ThreatLevel ) + return ThreatLevel + +end -- Is functions @@ -8239,9 +9109,9 @@ function UNIT:IsInZone( Zone ) self:T( { IsInZone } ) return IsInZone - else - return false end + + return false end --- Returns true if the unit is not within a @{Zone}. @@ -8323,14 +9193,22 @@ end -- @param #UNIT self function UNIT:FlareRed() self:F2() - trigger.action.signalFlare( self:GetPointVec3(), trigger.flareColor.Red, 0 ) + local Vec3 = self:GetPointVec3() + if Vec3 then + trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) + end end --- Smoke the UNIT. -- @param #UNIT self -function UNIT:Smoke( SmokeColor ) +function UNIT:Smoke( SmokeColor, Range ) self:F2() - trigger.action.smoke( self:GetPointVec3(), SmokeColor ) + if Range then + trigger.action.smoke( self:GetRandomPointVec3( Range ), SmokeColor ) + else + trigger.action.smoke( self:GetPointVec3(), SmokeColor ) + end + end --- Smoke the UNIT Green. @@ -8377,13 +9255,84 @@ end function UNIT:IsAir() self:F2() - local UnitDescriptor = self.DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + local DCSUnit = self:GetDCSObject() - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + + local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) + + self:T3( IsAirResult ) + return IsAirResult + end + + return nil +end - self:T3( IsAirResult ) - return IsAirResult +--- Returns if the unit is of an ground category. +-- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ground category evaluation result. +function UNIT:IsGround() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) + + local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) + + self:T3( IsGroundResult ) + return IsGroundResult + end + + return nil +end + +--- Returns if the unit is a friendly unit. +-- @param #UNIT self +-- @return #boolean IsFriendly evaluation result. +function UNIT:IsFriendly( FriendlyCoalition ) + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCoalition = DCSUnit:getCoalition() + self:T3( { UnitCoalition, FriendlyCoalition } ) + + local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) + + self:E( IsFriendlyResult ) + return IsFriendlyResult + end + + return nil +end + +--- Returns if the unit is of a ship category. +-- If the unit is a ship, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ship category evaluation result. +function UNIT:IsShip() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) + + local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) + + self:T3( IsShipResult ) + return IsShipResult + end + + return nil end --- This module contains the ZONE classes, inherited from @{Zone#ZONE_BASE}. @@ -8489,10 +9438,10 @@ end --- Returns if a location is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_BASE:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_BASE:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) return false end @@ -8509,18 +9458,27 @@ function ZONE_BASE:IsPointVec3InZone( PointVec3 ) return InZone end +--- Returns the Vec2 coordinate of the zone. +-- @param #ZONE_BASE self +-- @return #nil. +function ZONE_BASE:GetVec2() + self:F2( self.ZoneName ) + + return nil +end --- Define a random @{DCSTypes#Vec2} within the zone. -- @param #ZONE_BASE self --- @return DCSTypes#Vec2 The Vec2 coordinates. +-- @return #nil The Vec2 coordinates. function ZONE_BASE:GetRandomVec2() - return { x = 0, y = 0 } + return nil end --- Get the bounding square the zone. -- @param #ZONE_BASE self --- @return #ZONE_BASE.BoundingSquare The bounding square. +-- @return #nil The bounding square. function ZONE_BASE:GetBoundingSquare() - return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } + --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } + return nil end @@ -8535,7 +9493,7 @@ end --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS --- @field DCSTypes#Vec2 PointVec2 The current location of the zone. +-- @field DCSTypes#Vec2 Vec2 The current location of the zone. -- @field DCSTypes#Distance Radius The radius of the zone. -- @extends Zone#ZONE_BASE ZONE_RADIUS = { @@ -8545,15 +9503,15 @@ ZONE_RADIUS = { --- Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self -- @param #string ZoneName Name of the zone. --- @param DCSTypes#Vec2 PointVec2 The location of the zone. +-- @param DCSTypes#Vec2 Vec2 The location of the zone. -- @param DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self -function ZONE_RADIUS:New( ZoneName, PointVec2, Radius ) +function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, PointVec2, Radius } ) + self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius - self.PointVec2 = PointVec2 + self.Vec2 = Vec2 return self end @@ -8567,7 +9525,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) self:F2( SmokeColor ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() Points = Points and Points or 360 @@ -8576,8 +9534,8 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 - Point.x = PointVec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = PointVec2.y + math.sin( Radial ) * self:GetRadius() + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() + Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() POINT_VEC2:New( Point.x, Point.y ):Smoke( SmokeColor ) end @@ -8595,7 +9553,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) self:F2( { FlareColor, Azimuth } ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() Points = Points and Points or 360 @@ -8604,8 +9562,8 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 - Point.x = PointVec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = PointVec2.y + math.sin( Radial ) * self:GetRadius() + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() + Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() POINT_VEC2:New( Point.x, Point.y ):Flare( FlareColor, Azimuth ) end @@ -8639,26 +9597,26 @@ end --- Returns the location of the zone. -- @param #ZONE_RADIUS self -- @return DCSTypes#Vec2 The location of the zone. -function ZONE_RADIUS:GetPointVec2() +function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) - self:T2( { self.PointVec2 } ) + self:T2( { self.Vec2 } ) - return self.PointVec2 + return self.Vec2 end --- Sets the location of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 PointVec2 The new location of the zone. +-- @param DCSTypes#Vec2 Vec2 The new location of the zone. -- @return DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetPointVec2( PointVec2 ) +function ZONE_RADIUS:SetPointVec2( Vec2 ) self:F2( self.ZoneName ) - self.PointVec2 = PointVec2 + self.Vec2 = Vec2 - self:T2( { self.PointVec2 } ) + self:T2( { self.Vec2 } ) - return self.PointVec2 + return self.Vec2 end --- Returns the point of the zone. @@ -8668,9 +9626,9 @@ end function ZONE_RADIUS:GetPointVec3( Height ) self:F2( self.ZoneName ) - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() - local PointVec3 = { x = PointVec2.x, y = land.getHeight( self:GetPointVec2() ) + Height, z = PointVec2.y } + local PointVec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { PointVec3 } ) @@ -8680,15 +9638,17 @@ end --- Returns if a location is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_RADIUS:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_RADIUS:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) - local ZonePointVec2 = self:GetPointVec2() - - if (( PointVec2.x - ZonePointVec2.x )^2 + ( PointVec2.y - ZonePointVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then - return true + local ZoneVec2 = self:GetVec2() + + if ZoneVec2 then + if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then + return true + end end return false @@ -8698,10 +9658,10 @@ end -- @param #ZONE_RADIUS self -- @param DCSTypes#Vec3 PointVec3 The point to test. -- @return #boolean true if the point is within the zone. -function ZONE_RADIUS:IsPointVec3InZone( PointVec3 ) - self:F2( PointVec3 ) +function ZONE_RADIUS:IsPointVec3InZone( Vec3 ) + self:F2( Vec3 ) - local InZone = self:IsPointVec2InZone( { x = PointVec3.x, y = PointVec3.z } ) + local InZone = self:IsPointVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone end @@ -8713,11 +9673,11 @@ function ZONE_RADIUS:GetRandomVec2() self:F( self.ZoneName ) local Point = {} - local PointVec2 = self:GetPointVec2() + local Vec2 = self:GetVec2() local angle = math.random() * math.pi*2; - Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = PointVec2.y + math.sin( angle ) * math.random() * self:GetRadius(); + Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); + Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); self:T( { Point } ) @@ -8771,10 +9731,11 @@ ZONE_UNIT = { -- @param DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetPointVec2(), Radius ) ) - self:F( { ZoneName, ZoneUNIT:GetPointVec2(), Radius } ) + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) + self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT + self.LastVec2 = ZoneUNIT:GetVec2() return self end @@ -8783,14 +9744,20 @@ end --- Returns the current location of the @{Unit#UNIT}. -- @param #ZONE_UNIT self -- @return DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. -function ZONE_UNIT:GetPointVec2() +function ZONE_UNIT:GetVec2() self:F( self.ZoneName ) - local ZonePointVec2 = self.ZoneUNIT:GetPointVec2() + local ZoneVec2 = self.ZoneUNIT:GetVec2() + if ZoneVec2 then + self.LastVec2 = ZoneVec2 + return ZoneVec2 + else + return self.LastVec2 + end - self:T( { ZonePointVec2 } ) - - return ZonePointVec2 + self:T( { ZoneVec2 } ) + + return nil end --- Returns a random location within the zone. @@ -8801,6 +9768,9 @@ function ZONE_UNIT:GetRandomVec2() local Point = {} local PointVec2 = self.ZoneUNIT:GetPointVec2() + if not PointVec2 then + PointVec2 = self.LastVec2 + end local angle = math.random() * math.pi*2; Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); @@ -8811,6 +9781,24 @@ function ZONE_UNIT:GetRandomVec2() return Point end +--- Returns the point of the zone. +-- @param #ZONE_RADIUS self +-- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return DCSTypes#Vec3 The point of the zone. +function ZONE_UNIT:GetPointVec3( Height ) + self:F2( self.ZoneName ) + + Height = Height or 0 + + local Vec2 = self:GetVec2() + + local PointVec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } + + self:T2( { PointVec3 } ) + + return PointVec3 +end + --- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. -- @type ZONE_GROUP -- @field Group#GROUP ZoneGROUP @@ -8955,10 +9943,10 @@ end --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self --- @param DCSTypes#Vec2 PointVec2 The location to test. +-- @param DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. -function ZONE_POLYGON_BASE:IsPointVec2InZone( PointVec2 ) - self:F2( PointVec2 ) +function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) + self:F2( Vec2 ) local Next local Prev @@ -8969,8 +9957,8 @@ function ZONE_POLYGON_BASE:IsPointVec2InZone( PointVec2 ) while Next <= #self.Polygon do self:T( { Next, Prev, self.Polygon[Next], self.Polygon[Prev] } ) - if ( ( ( self.Polygon[Next].y > PointVec2.y ) ~= ( self.Polygon[Prev].y > PointVec2.y ) ) and - ( PointVec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( PointVec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) + if ( ( ( self.Polygon[Next].y > Vec2.y ) ~= ( self.Polygon[Prev].y > Vec2.y ) ) and + ( Vec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( Vec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) ) then InPolygon = not InPolygon end @@ -9838,7 +10826,6 @@ function DATABASE:AddUnit( DCSUnitName ) if not self.UNITS[DCSUnitName] then local UnitRegister = UNIT:Register( DCSUnitName ) - self:E( UnitRegister.UnitName ) self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) end @@ -10709,10 +11696,15 @@ end --- SET_BASE class -- @type SET_BASE +-- @field #table Filter +-- @field #table Set +-- @field #table List -- @extends Base#BASE SET_BASE = { ClassName = "SET_BASE", + Filter = {}, Set = {}, + List = {}, } --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. @@ -10731,6 +11723,10 @@ function SET_BASE:New( Database ) self.YieldInterval = 10 self.TimeInterval = 0.001 + self.List = {} + self.List.__index = self.List + self.List = setmetatable( { Count = 0 }, self.List ) + return self end @@ -10760,18 +11756,90 @@ end -- @param Base#BASE Object -- @return Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) + self:F2( ObjectName ) - self.Set[ObjectName] = Object + local t = { _ = Object } + + if self.List.last then + self.List.last._next = t + t._prev = self.List.last + self.List.last = t + else + -- this is the first node + self.List.first = t + self.List.last = t + end + + self.List.Count = self.List.Count + 1 + + self.Set[ObjectName] = t._ + end --- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName function SET_BASE:Remove( ObjectName ) + self:E( ObjectName ) - self.Set[ObjectName] = nil + local t = self.Set[ObjectName] + + if t then + if t._next then + if t._prev then + t._next._prev = t._prev + t._prev._next = t._next + else + -- this was the first node + t._next._prev = nil + self.List._first = t._next + end + elseif t._prev then + -- this was the last node + t._prev._next = nil + self.List._last = t._prev + else + -- this was the only node + self.List._first = nil + self.List._last = nil + end + + t._next = nil + t._prev = nil + self.List.Count = self.List.Count - 1 + + self.Set[ObjectName] = nil + end + end +--- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. +-- @param #SET_BASE self +-- @return #number Count +function SET_BASE:Count() + + return self.List.Count +end + + + +--- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). +-- @param #SET_BASE self +-- @param #SET_BASE BaseSet +-- @return #SET_BASE +function SET_BASE:SetDatabase( BaseSet ) + + -- Copy the filter criteria of the BaseSet + local OtherFilter = routines.utils.deepCopy( BaseSet.Filter ) + self.Filter = OtherFilter + + -- Now base the new Set on the BaseSet + self.Database = BaseSet:GetSet() + return self +end + + + --- Define the SET iterator **"yield interval"** and the **"time interval"**. -- @param #SET_BASE self -- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. @@ -10786,6 +11854,20 @@ function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) end +--- Filters for the defined collection. +-- @param #SET_BASE self +-- @return #SET_BASE self +function SET_BASE:FilterOnce() + + for ObjectName, Object in pairs( self.Database ) do + + if self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + end + end + + return self +end --- Starts the filtering for the defined collection. -- @param #SET_BASE self @@ -10805,13 +11887,25 @@ function SET_BASE:_FilterStart() _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) -- Follow alive players and clients --- _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) --- _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) return self end +--- Stops the filtering for the defined collection. +-- @param #SET_BASE self +-- @return #SET_BASE self +function SET_BASE:FilterStop() + + _EVENTDISPATCHER:OnBirthRemove( self ) + _EVENTDISPATCHER:OnDeadRemove( self ) + _EVENTDISPATCHER:OnCrashRemove( self ) + + return self +end + --- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. -- @param #SET_BASE self -- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. @@ -10825,9 +11919,9 @@ function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) for ObjectID, ObjectData in pairs( self.Set ) do if NearestObject == nil then NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetPointVec2() ) + ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) else - local Distance = PointVec2:DistanceFromVec2( ObjectData:GetPointVec2() ) + local Distance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) if Distance < ClosestDistance then NearestObject = ObjectData ClosestDistance = Distance @@ -10884,49 +11978,58 @@ end -- @param #SET_BASE self -- @param Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) - self:F3( { Event } ) + self:E( { Event } ) if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) - if ObjectName and Object then + self:E({ObjectName, Object}) + if ObjectName and Object ~= nil then self:Remove( ObjectName ) end end end ------ Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). ----- @param #SET_BASE self ----- @param Event#EVENTDATA Event ---function SET_BASE:_EventOnPlayerEnterUnit( Event ) --- self:F3( { Event } ) --- --- if Event.IniDCSUnit then --- if self:IsIncludeObject( Event.IniDCSUnit ) then --- if not self.PlayersAlive[Event.IniDCSUnitName] then --- self:E( { "Add player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) --- self.PlayersAlive[Event.IniDCSUnitName] = Event.IniDCSUnit:getPlayerName() --- self.ClientsAlive[Event.IniDCSUnitName] = _DATABASE.Clients[ Event.IniDCSUnitName ] --- end --- end --- end ---end --- ------ Handles the OnPlayerLeaveUnit event to clean the active players table. ----- @param #SET_BASE self ----- @param Event#EVENTDATA Event ---function SET_BASE:_EventOnPlayerLeaveUnit( Event ) --- self:F3( { Event } ) --- --- if Event.IniDCSUnit then --- if self:IsIncludeObject( Event.IniDCSUnit ) then --- if self.PlayersAlive[Event.IniDCSUnitName] then --- self:E( { "Cleaning player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) --- self.PlayersAlive[Event.IniDCSUnitName] = nil --- self.ClientsAlive[Event.IniDCSUnitName] = nil --- end --- end --- end ---end +--- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). +-- @param #SET_BASE self +-- @param Event#EVENTDATA Event +function SET_BASE:_EventOnPlayerEnterUnit( Event ) + self:F3( { Event } ) + + if Event.IniDCSUnit then + local ObjectName, Object = self:AddInDatabase( Event ) + self:T3( ObjectName, Object ) + if self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + --self:_EventOnPlayerEnterUnit( Event ) + end + end +end + +--- Handles the OnPlayerLeaveUnit event to clean the active players table. +-- @param #SET_BASE self +-- @param Event#EVENTDATA Event +function SET_BASE:_EventOnPlayerLeaveUnit( Event ) + self:F3( { Event } ) + + local ObjectName = Event.IniDCSUnit + if Event.IniDCSUnit then + if Event.IniDCSGroup then + local GroupUnits = Event.IniDCSGroup:getUnits() + local PlayerCount = 0 + for _, DCSUnit in pairs( GroupUnits ) do + if DCSUnit ~= Event.IniDCSUnit then + if DCSUnit:getPlayer() ~= nil then + PlayerCount = PlayerCount + 1 + end + end + end + self:E(PlayerCount) + if PlayerCount == 0 then + self:Remove( Event.IniDCSGroupName ) + end + end + end +end -- Iterators @@ -10939,7 +12042,8 @@ function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArgumen local function CoRoutine() local Count = 0 - for ObjectID, Object in pairs( Set ) do + for ObjectID, ObjectData in pairs( Set ) do + local Object = ObjectData self:T3( Object ) if Function then if Function( unpack( FunctionArguments ), Object ) == true then @@ -11039,7 +12143,7 @@ function SET_BASE:Flush() for ObjectName, Object in pairs( self.Set ) do ObjectNames = ObjectNames .. ObjectName .. ", " end - self:T( { "Objects in Set:", ObjectNames } ) + self:E( { "Objects in Set:", ObjectNames } ) return ObjectNames end @@ -11217,6 +12321,8 @@ function SET_GROUP:FilterStart() self:_FilterStart() end + + return self end @@ -11451,10 +12557,6 @@ function SET_UNIT:New() -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) - _EVENTDISPATCHER:OnBirth( self._EventOnBirth, self ) - _EVENTDISPATCHER:OnDead( self._EventOnDeadOrCrash, self ) - _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) - return self end @@ -11609,6 +12711,31 @@ function SET_UNIT:FilterPrefixes( Prefixes ) return self end +--- Builds a set of units having a radar of give types. +-- All the units having a radar of a given type will be included within the set. +-- @param #SET_UNIT self +-- @param #table RadarTypes The radar types. +-- @return #SET_UNIT self +function SET_UNIT:FilterHasRadar( RadarTypes ) + + self.Filter.RadarTypes = self.Filter.RadarTypes or {} + if type( RadarTypes ) ~= "table" then + RadarTypes = { RadarTypes } + end + for RadarTypeID, RadarType in pairs( RadarTypes ) do + self.Filter.RadarTypes[RadarType] = RadarType + end + return self +end + +--- Builds a set of SEADable units. +-- @param #SET_UNIT self +-- @return #SET_UNIT self +function SET_UNIT:FilterHasSEAD() + + self.Filter.SEAD = true + return self +end @@ -11648,9 +12775,10 @@ end -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:FindInDatabase( Event ) - self:F3( { Event } ) + self:E( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] + + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. @@ -11709,6 +12837,119 @@ function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) return self end +--- Returns a comma separated string of the unit types with a count in the @{Set}. +-- @param #SET_UNIT self +-- @return #string The unit types string +function SET_UNIT:GetUnitTypesText() + self:F2() + + local MT = {} -- Message Text + local UnitTypes = {} + + for UnitID, UnitData in pairs( self:GetSet() ) do + local TextUnit = UnitData -- Unit#UNIT + if TextUnit:IsAlive() then + local UnitType = TextUnit:GetTypeName() + + if not UnitTypes[UnitType] then + UnitTypes[UnitType] = 1 + else + UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + end + end + end + + for UnitTypeID, UnitType in pairs( UnitTypes ) do + MT[#MT+1] = UnitType .. " of " .. UnitTypeID + end + + return table.concat( MT, ", " ) +end + + +--- Returns if the @{Set} has targets having a radar (of a given type). +-- @param #SET_UNIT self +-- @param DCSUnit#Unit.RadarType RadarType +-- @return #number The amount of radars in the Set with the given type +function SET_UNIT:HasRadar( RadarType ) + self:F2( RadarType ) + + local RadarCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitSensorTest = UnitData -- Unit#UNIT + local HasSensors + if RadarType then + HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) + else + HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) + end + self:T3(HasSensors) + if HasSensors then + RadarCount = RadarCount + 1 + end + end + + return RadarCount +end + +--- Returns if the @{Set} has targets that can be SEADed. +-- @param #SET_UNIT self +-- @return #number The amount of SEADable units in the Set +function SET_UNIT:HasSEAD() + self:F2() + + local SEADCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitSEAD = UnitData -- Unit#UNIT + if UnitSEAD:IsAlive() then + local UnitSEADAttributes = UnitSEAD:GetDesc().attributes + + local HasSEAD = UnitSEAD:HasSEAD() + + self:T3(HasSEAD) + if HasSEAD then + SEADCount = SEADCount + 1 + end + end + end + + return SEADCount +end + +--- Returns if the @{Set} has ground targets. +-- @param #SET_UNIT self +-- @return #number The amount of ground targets in the Set. +function SET_UNIT:HasGroundUnits() + self:F2() + + local GroundUnitCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitTest = UnitData -- Unit#UNIT + if UnitTest:IsGround() then + GroundUnitCount = GroundUnitCount + 1 + end + end + + return GroundUnitCount +end + +--- Returns if the @{Set} has friendly ground units. +-- @param #SET_UNIT self +-- @return #number The amount of ground targets in the Set. +function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) + self:F2() + + local FriendlyUnitCount = 0 + for UnitID, UnitData in pairs( self:GetSet()) do + local UnitTest = UnitData -- Unit#UNIT + if UnitTest:IsFriendly( FriendlyCoalition ) then + FriendlyUnitCount = FriendlyUnitCount + 1 + end + end + + return FriendlyUnitCount +end + ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. @@ -11800,6 +13041,29 @@ function SET_UNIT:IsIncludeObject( MUnit ) MUnitInclude = MUnitInclude and MUnitPrefix end + if self.Filter.RadarTypes then + local MUnitRadar = false + for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do + self:T3( { "Radar:", RadarType } ) + if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then + if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. + self:T3( "RADAR Found" ) + end + MUnitRadar = true + end + end + MUnitInclude = MUnitInclude and MUnitRadar + end + + if self.Filter.SEAD then + local MUnitSEAD = false + if MUnit:HasSEAD() == true then + self:T3( "SEAD Found" ) + MUnitSEAD = true + end + MUnitInclude = MUnitInclude and MUnitSEAD + end + self:T2( MUnitInclude ) return MUnitInclude end @@ -12391,6 +13655,9 @@ end -- =============================================== -- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. -- +-- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. +-- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. +-- -- 1.1) POINT_VEC3 constructor -- --------------------------- -- @@ -12415,6 +13682,7 @@ end --- The POINT_VEC3 class -- @type POINT_VEC3 -- @extends Base#BASE +-- @field DCSTypes#Vec3 PointVec3 -- @field #POINT_VEC3.SmokeColor SmokeColor -- @field #POINT_VEC3.FlareColor FlareColor -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType @@ -12435,6 +13703,7 @@ POINT_VEC3 = { White = trigger.flareColor.White, Yellow = trigger.flareColor.Yellow }, + Metric = true, RoutePointAltType = { BARO = "BARO", }, @@ -12501,6 +13770,160 @@ function POINT_VEC3:New( x, y, z ) end +--- Return the coordinates of the POINT_VEC3 in Vec3 format. +-- @param #POINT_VEC3 self +-- @return DCSTypes#Vec3 The Vec3 coodinate. +function POINT_VEC3:GetVec3() + return self.PointVec3 +end + + +--- Return the x coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The x coodinate. +function POINT_VEC3:GetX() + return self.PointVec3.x +end + +--- Return the y coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The y coodinate. +function POINT_VEC3:GetY() + return self.PointVec3.y +end + +--- Return the z coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The z coodinate. +function POINT_VEC3:GetZ() + return self.PointVec3.z +end + + +--- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) + return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } +end + +--- Get a correction in radians of the real magnetic north of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number CorrectionRadians The correction in radians. +function POINT_VEC3:GetNorthCorrectionRadians() + local TargetVec3 = self:GetVec3() + local lat, lon = coord.LOtoLL(TargetVec3) + local north_posit = coord.LLtoLO(lat + 1, lon) + return math.atan2( north_posit.z - TargetVec3.z, north_posit.x - TargetVec3.x ) +end + + +--- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. +-- @param #POINT_VEC3 self +-- @param DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @return #number DirectionRadians The direction in radians. +function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) + local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) + --DirectionRadians = DirectionRadians + self:GetNorthCorrectionRadians() + if DirectionRadians < 0 then + DirectionRadians = DirectionRadians + 2 * math.pi -- put dir in range of 0 to 2*pi ( the full circle ) + end + return DirectionRadians +end + +--- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Distance Distance The distance in meters. +function POINT_VEC3:Get2DDistance( TargetPointVec3 ) + local TargetVec3 = TargetPointVec3:GetVec3() + local SourceVec3 = self:GetVec3() + return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 +end + +--- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return DCSTypes#Distance Distance The distance in meters. +function POINT_VEC3:Get3DDistance( TargetPointVec3 ) + local TargetVec3 = TargetPointVec3:GetVec3() + local SourceVec3 = self:GetVec3() + return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 +end + +--- Provides a Bearing / Range string +-- @param #POINT_VEC3 self +-- @param #number AngleRadians The angle in randians +-- @param #number Distance The distance +-- @return #string The BR Text +function POINT_VEC3:ToStringBR( AngleRadians, Distance ) + + AngleRadians = UTILS.Round( UTILS.ToDegree( AngleRadians ), 0 ) + if self:IsMetric() then + Distance = UTILS.Round( Distance / 1000, 2 ) + else + Distance = UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) + end + + local s = string.format( '%03d', AngleRadians ) .. ' for ' .. Distance + + s = s .. self:GetAltitudeText() -- When the POINT is a VEC2, there will be no altitude shown. + + return s +end + +--- Provides a Bearing / Range string +-- @param #POINT_VEC3 self +-- @param #number AngleRadians The angle in randians +-- @param #number Distance The distance +-- @return #string The BR Text +function POINT_VEC3:ToStringLL( acc, DMS ) + + acc = acc or 3 + local lat, lon = coord.LOtoLL( self.PointVec3 ) + return UTILS.tostringLL(lat, lon, acc, DMS) +end + +--- Return the altitude text of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #string Altitude text. +function POINT_VEC3:GetAltitudeText() + if self:IsMetric() then + return ' at ' .. UTILS.Round( self:GetY(), 0 ) + else + return ' at ' .. UTILS.Round( UTILS.MetersToFeet( self:GetY() ), 0 ) + end +end + +--- Return a BR string from a POINT_VEC3 to the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @return #string The BR text. +function POINT_VEC3:GetBRText( TargetPointVec3 ) + local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 ) + local AngleRadians = self:GetDirectionRadians( DirectionVec3 ) + local Distance = self:Get2DDistance( TargetPointVec3 ) + return self:ToStringBR( AngleRadians, Distance ) +end + +--- Sets the POINT_VEC3 metric or NM. +-- @param #POINT_VEC3 self +-- @param #boolean Metric true means metric, false means NM. +function POINT_VEC3:SetMetric( Metric ) + self.Metric = Metric +end + +--- Gets if the POINT_VEC3 is metric or NM. +-- @param #POINT_VEC3 self +-- @return #boolean Metric true means metric, false means NM. +function POINT_VEC3:IsMetric() + return self.Metric +end + + + + --- Build an air type route point. -- @param #POINT_VEC3 self -- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. @@ -12686,9 +14109,17 @@ function POINT_VEC2:DistanceFromVec2( Vec2Reference ) end +--- Return no text for the altitude of the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #string Empty string. +function POINT_VEC2:GetAltitudeText() + return '' +end + --- The main include file for the MOOSE system. Include.File( "Routines" ) +Include.File( "Utils" ) Include.File( "Base" ) Include.File( "Object" ) Include.File( "Identifiable" ) @@ -12731,13 +14162,27 @@ Include.File( "MissileTrainer" ) Include.File( "PatrolZone" ) Include.File( "AIBalancer" ) Include.File( "AirbasePolice" ) + Include.File( "Detection" ) -Include.File( "FAC" ) +Include.File( "DetectionManager" ) + +Include.File( "StateMachine" ) + +Include.File( "Process" ) +Include.File( "Process_Assign" ) +Include.File( "Process_Route" ) +Include.File( "Process_Smoke" ) +Include.File( "Process_Destroy" ) + +Include.File( "Task" ) +Include.File( "Task_SEAD" ) +Include.File( "Task_CAS" ) +Include.File( "Task_BAI" ) -- The order of the declarations is important here. Don't touch it. --- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- #EVENT +_EVENTDISPATCHER = EVENT:New() -- Event#EVENT --- Declare the main database object, which is used internally by the MOOSE classes. _DATABASE = DATABASE:New() -- Database#DATABASE @@ -12803,6 +14248,8 @@ function SCORING:New( GameName ) self.SchedulerId = SCHEDULER:New( self, self._FollowPlayersScheduled, {}, 0, 5 ) self:ScoreMenu() + + self:OpenCSV( GameName) return self @@ -13006,10 +14453,17 @@ end --- Registers Scores the players completing a Mission Task. -function SCORING:_AddMissionTaskScore( PlayerUnit, MissionName, Score ) - self:F( { PlayerUnit, MissionName, Score } ) +-- @param #SCORING self +-- @param Mission#MISSION Mission +-- @param Unit#UNIT PlayerUnit +-- @param #string Text +-- @param #number Score +function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) - local PlayerName = PlayerUnit:getPlayerName() + local PlayerName = PlayerUnit:GetPlayerName() + local MissionName = Mission:GetName() + + self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) if not self.Players[PlayerName].Mission[MissionName] then self.Players[PlayerName].Mission[MissionName] = {} @@ -13023,26 +14477,37 @@ function SCORING:_AddMissionTaskScore( PlayerUnit, MissionName, Score ) self.Players[PlayerName].Score = self.Players[PlayerName].Score + Score self.Players[PlayerName].Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score - MESSAGE:New( "Player '" .. PlayerName .. "' has finished another Task in Mission '" .. MissionName .. "'. " .. - Score .. " Score points added.", - 20 ):ToAll() + MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. + Score .. " task score!", + 30 ):ToAll() - self:ScoreCSV( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:getName() ) + self:ScoreCSV( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() ) end --- Registers Mission Scores for possible multiple players that contributed in the Mission. -function SCORING:_AddMissionScore( MissionName, Score ) - self:F( { MissionName, Score } ) +-- @param #SCORING self +-- @param Mission#MISSION Mission +-- @param Unit#UNIT PlayerUnit +-- @param #string Text +-- @param #number Score +function SCORING:_AddMissionScore( Mission, Text, Score ) + + local MissionName = Mission:GetName() + + self:F( { Mission, Text, Score } ) for PlayerName, PlayerData in pairs( self.Players ) do if PlayerData.Mission[MissionName] then + PlayerData.Score = PlayerData.Score + Score PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score - MESSAGE:New( "Player '" .. PlayerName .. "' has finished Mission '" .. MissionName .. "'. " .. - Score .. " Score points added.", - 20 ):ToAll() + + MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. + Score .. " mission score!", + 60 ):ToAll() + self:ScoreCSV( PlayerName, "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score ) end end @@ -14683,7 +16148,7 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) self.MessageCategory = "" end - self.MessageDuration = MessageDuration + self.MessageDuration = MessageDuration or 5 self.MessageTime = timer.getTime() self.MessageText = MessageText @@ -14726,6 +16191,21 @@ function MESSAGE:ToClient( Client ) return self end +--- Sends a MESSAGE to a Group. +-- @param #MESSAGE self +-- @param Group#GROUP Group is the Group. +-- @return #MESSAGE +function MESSAGE:ToGroup( Group ) + self:F( Group.GroupName ) + + if Group then + + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end --- Sends a MESSAGE to the Blue coalition. -- @param #MESSAGE self -- @return #MESSAGE @@ -15850,471 +17330,871 @@ _TransportStageAction = { NONE = 0, ONCE = 1 } ---- The TASK Classes define major end-to-end activities within a MISSION. The TASK Class is the Master Class to orchestrate these activities. From this class, many concrete TASK classes are inherited. --- @module TASK +--- This module contains the TASK_BASE class. +-- +-- 1) @{#TASK_BASE} class, extends @{Base#BASE} +-- ============================================ +-- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. +-- ---------------------------------------------------------------------------------------- +-- The class provides a couple of methods to: +-- +-- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). +-- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. +-- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. +-- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. +-- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. +-- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} +-- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. +-- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. +-- +-- 1.2) Set and enquire task status (beyond the task state machine processing). +-- ---------------------------------------------------------------------------- +-- A task needs to implement as a minimum the following task states: +-- +-- * **Success**: Expresses the successful execution and finalization of the task. +-- * **Failed**: Expresses the failure of a task. +-- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. +-- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. +-- +-- A task may also implement the following task states: +-- +-- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. +-- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. +-- +-- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. +-- +-- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. +-- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. +-- +-- 1.3) Add scoring when reaching a certain task status: +-- ----------------------------------------------------- +-- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +-- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. +-- +-- 1.4) Task briefing: +-- ------------------- +-- A task briefing can be given that is shown to the player when he is assigned to the task. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task - - - - - - ---- The TASK class --- @type TASK +--- The TASK_BASE class +-- @type TASK_BASE +-- @field Scheduler#SCHEDULER TaskScheduler +-- @field Mission#MISSION Mission +-- @field StateMachine#STATEMACHINE Fsm +-- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task -- @extends Base#BASE -TASK = { - - -- Defines the different signal types with a Task. - SIGNAL = { - COLOR = { - RED = { ID = 1, COLOR = trigger.smokeColor.Red, TEXT = "A red" }, - GREEN = { ID = 2, COLOR = trigger.smokeColor.Green, TEXT = "A green" }, - BLUE = { ID = 3, COLOR = trigger.smokeColor.Blue, TEXT = "A blue" }, - WHITE = { ID = 4, COLOR = trigger.smokeColor.White, TEXT = "A white" }, - ORANGE = { ID = 5, COLOR = trigger.smokeColor.Orange, TEXT = "An orange" } - }, - TYPE = { - SMOKE = { ID = 1, TEXT = "smoke" }, - FLARE = { ID = 2, TEXT = "flare" } - } - }, - ClassName = "TASK", - Mission = {}, -- Owning mission of the Task - Name = '', - Stages = {}, - Stage = {}, - Cargos = { - InitCargos = {}, - LoadCargos = {} - }, - LandingZones = { - LandingZoneNames = {}, - LandingZones = {} - }, - ActiveStage = 0, - TaskDone = false, - TaskFailed = false, - GoalTasks = {} +TASK_BASE = { + ClassName = "TASK_BASE", + TaskScheduler = nil, + Processes = {}, + Players = nil, + Scores = {}, + Menu = {}, + SetGroup = nil, } ---- Instantiates a new TASK Base. Should never be used. Interface Class. --- @return TASK -function TASK:New() + +--- Instantiates a new TASK_BASE. Should never be used. Interface Class. +-- @param #TASK_BASE self +-- @param Mission#MISSION The mission wherein the Task is registered. +-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. +-- @param #string TaskName The name of the Task +-- @param #string TaskType The type of the Task +-- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) +-- @return #TASK_BASE self +function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) + local self = BASE:Inherit( self, BASE:New() ) - self:F() + self:E( "New TASK " .. TaskName ) + + self.Processes = {} + self.Fsm = {} + + self.Mission = Mission + self.SetGroup = SetGroup + + self:SetCategory( TaskCategory ) + self:SetType( TaskType ) + self:SetName( TaskName ) + self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. + + self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - -- assign Task default values during construction - self.TaskBriefing = "Task: No Task." - self.Time = timer.getTime() - self.ExecuteStage = _TransportExecuteStage.NONE + return self +end + +--- Cleans all references of a TASK_BASE. +-- @param #TASK_BASE self +-- @return #nil +function TASK_BASE:CleanUp() + + _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) + _EVENTDISPATCHER:OnDeadRemove( self ) + _EVENTDISPATCHER:OnCrashRemove( self ) + _EVENTDISPATCHER:OnPilotDeadRemove( self ) + + return nil +end + + +--- Assign the @{Task}to a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +function TASK_BASE:AssignToGroup( TaskGroup ) + self:F2( TaskGroup:GetName() ) + + local TaskGroupName = TaskGroup:GetName() + + TaskGroup:SetState( TaskGroup, "Assigned", self ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:AssignToUnit( TaskUnit ) + end + end +end + +--- Send the briefng message of the @{Task} to the assigned @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:SendBriefingToAssignedGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + if self:IsAssignedToGroup( TaskGroup ) then + TaskGroup:Message( self.TaskBriefing, 60 ) + end + end +end + + +--- Assign the @{Task} from the @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:UnAssignFromGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:UnAssignFromUnit( TaskUnit ) + end + end + end +end + +--- Returns if the @{Task} is assigned to the Group. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #boolean +function TASK_BASE:IsAssignedToGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + if self:IsStateAssigned() then + if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then + return true + end + end + + return false +end + +--- Assign the @{Task}to an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + return nil +end + +--- UnAssign the @{Task} from an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:UnAssignFromUnit( TaskUnitName ) + self:F( TaskUnitName ) + + if self:HasStateMachine( TaskUnitName ) == true then + self:RemoveStateMachines( TaskUnitName ) + self:RemoveProcesses( TaskUnitName ) + end return self end -function TASK:SetStage( StageSequenceIncrement ) - self:F( { StageSequenceIncrement } ) +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenu() - local Valid = false - if StageSequenceIncrement ~= 0 then - self.ActiveStage = self.ActiveStage + StageSequenceIncrement - if 1 <= self.ActiveStage and self.ActiveStage <= #self.Stages then - self.Stage = self.Stages[self.ActiveStage] - self:T( { self.Stage.Name } ) - self.Frequency = self.Stage.Frequency - Valid = true - else - Valid = false - env.info( "TASK:SetStage() self.ActiveStage is smaller or larger than self.Stages array. self.ActiveStage = " .. self.ActiveStage ) - end - end - self.Time = timer.getTime() - return Valid + local MenuText = self:GetPlannedMenuText() + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not self:IsAssignedToGroup( TaskGroup ) then + self:SetPlannedMenuForGroup( TaskGroup, MenuText ) + end + end end -function TASK:Init() - self:F() - self.ActiveStage = 0 - self:SetStage(1) - self.TaskDone = false - self.TaskFailed = false +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if self:IsAssignedToGroup( TaskGroup ) then + self:SetAssignedMenuForGroup( TaskGroup ) + end + end end +--- Remove the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenu() ---- Get progress of a TASK. --- @return string GoalsText -function TASK:GetGoalProgress() - self:F2() - - local GoalsText = "" - for GoalVerb, GoalVerbData in pairs( self.GoalTasks ) do - local Goals = self:GetGoalCompletion( GoalVerb ) - if Goals and Goals ~= "" then - Goals = '(' .. Goals .. ')' - else - Goals = '( - )' - end - GoalsText = GoalsText .. GoalVerb .. ': ' .. self:GetGoalCount(GoalVerb) .. ' goals ' .. Goals .. ' of ' .. self:GetGoalTotal(GoalVerb) .. ' goals completed (' .. self:GetGoalPercentage(GoalVerb) .. '%); ' - end - - if GoalsText == "" then - GoalsText = "( - )" - end - - return GoalsText + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + end end ---- Show progress of a TASK. --- @param MISSION Mission Group structure describing the Mission. --- @param CLIENT Client Group structure describing the Client. -function TASK:ShowGoalProgress( Mission, Client ) - self:F2() +--- Set the planned menu option of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @param #string MenuText The menu text. +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) + self:E( TaskGroup:GetName() ) - local GoalsText = "" - for GoalVerb, GoalVerbData in pairs( self.GoalTasks ) do - if Mission:IsCompleted() then - else - local Goals = self:GetGoalCompletion( GoalVerb ) - if Goals and Goals ~= "" then - else - Goals = "-" - end - GoalsText = GoalsText .. self:GetGoalProgress() - end - end - - if Mission.MissionReportFlash or Mission.MissionReportShow then - Client:Message( GoalsText, 10, "Mission Command: Task Status", 30, "Task status" ) - end + local TaskMission = self.Mission:GetName() + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + Mission.MenuCategory = Mission.MenuCategory or {} + local MenuCategory = Mission.MenuCategory + + Mission.MenuType = Mission.MenuType or {} + local MenuType = Mission.MenuType + + self.Menu = self.Menu or {} + local Menu = self.Menu + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + + MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} + MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) + + MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} + MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) + + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + end + Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) + + return self end ---- Sets a TASK to status Done. -function TASK:Done() - self:F2() - self.TaskDone = true +--- Set the assigned menu options of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) + self:E( TaskGroup:GetName() ) + + local TaskMission = self.Mission:GetName() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + self.MenuStatus = self.MenuStatus or {} + local MenuStatus = self.MenuStatus + + + self.MenuAbort = self.MenuAbort or {} + local MenuAbort = self.MenuAbort + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) + MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) + + return self end ---- Returns if a TASK is done. --- @return bool -function TASK:IsDone() - self:F2( self.TaskDone ) - return self.TaskDone -end +--- Remove the menu option of the @{Task} for a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenuForGroup( TaskGroup ) ---- Sets a TASK to status failed. -function TASK:Failed() - self:F() - self.TaskFailed = true -end + local TaskGroupName = TaskGroup:GetName() + + local Mission = self.Mission + local MenuMission = Mission.MenuMission + local MenuCategory = Mission.MenuCategory + local MenuType = Mission.MenuType + local MenuStatus = self.MenuStatus + local MenuAbort = self.MenuAbort + local Menu = self.Menu ---- Returns if a TASk has failed. --- @return bool -function TASK:IsFailed() - self:F2( self.TaskFailed ) - return self.TaskFailed -end + Menu = Menu or {} + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + Menu[TaskGroupName] = nil + end -function TASK:Reset( Mission, Client ) - self:F2() - self.ExecuteStage = _TransportExecuteStage.NONE -end + MenuType = MenuType or {} + if MenuType[TaskGroupName] then + for _, Menu in pairs( MenuType[TaskGroupName] ) do + Menu:Remove() + end + MenuType[TaskGroupName] = nil + end ---- Returns the Goals of a TASK --- @return @table Goals -function TASK:GetGoals() - return self.GoalTasks -end - ---- Returns if a TASK has Goal(s). --- @param #TASK self --- @param #string GoalVerb is the name of the Goal of the TASK. --- @return bool -function TASK:Goal( GoalVerb ) - self:F2( { GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - self:T2( {self.GoalTasks[GoalVerb] } ) - if self.GoalTasks[GoalVerb] and self.GoalTasks[GoalVerb].GoalTotal > 0 then - return true - else - return false - end -end - ---- Sets the total Goals to be achieved of the Goal Name --- @param number GoalTotal is the number of times the GoalVerb needs to be achieved. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. -function TASK:SetGoalTotal( GoalTotal, GoalVerb ) - self:F2( { GoalTotal, GoalVerb } ) - - if not GoalVerb then - GoalVerb = self.GoalVerb - end - self.GoalTasks[GoalVerb] = {} - self.GoalTasks[GoalVerb].Goals = {} - self.GoalTasks[GoalVerb].GoalTotal = GoalTotal - self.GoalTasks[GoalVerb].GoalCount = 0 - return self -end - ---- Gets the total of Goals to be achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. -function TASK:GetGoalTotal( GoalVerb ) - self:F2( { GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return self.GoalTasks[GoalVerb].GoalTotal - else - return 0 - end -end - ---- Sets the total of Goals currently achieved within the TASK of the GoalVerb. --- @param number GoalCount is the total number of Goals achieved within the TASK. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:SetGoalCount( GoalCount, GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb) then - self.GoalTasks[GoalVerb].GoalCount = GoalCount - end - return self -end - ---- Increments the total of Goals currently achieved within the TASK of the GoalVerb, with the given GoalCountIncrease. --- @param number GoalCountIncrease is the number of new Goals achieved within the TASK. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:IncreaseGoalCount( GoalCountIncrease, GoalVerb ) - self:F2( { GoalCountIncrease, GoalVerb } ) - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb) then - self.GoalTasks[GoalVerb].GoalCount = self.GoalTasks[GoalVerb].GoalCount + GoalCountIncrease - end - return self -end - ---- Gets the total of Goals currently achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:GetGoalCount( GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return self.GoalTasks[GoalVerb].GoalCount - else - return 0 - end -end - ---- Gets the percentage of Goals currently achieved within the TASK of the GoalVerb. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return TASK -function TASK:GetGoalPercentage( GoalVerb ) - self:F2() - if not GoalVerb then - GoalVerb = self.GoalVerb - end - if self:Goal( GoalVerb ) then - return math.floor( self:GetGoalCount( GoalVerb ) / self:GetGoalTotal( GoalVerb ) * 100 + .5 ) - else - return 100 - end -end - ---- Returns if all the Goals of the TASK were achieved. --- @return bool -function TASK:IsGoalReached() - self:F2() - - local GoalReached = true - - for GoalVerb, Goals in pairs( self.GoalTasks ) do - self:T2( { "GoalVerb", GoalVerb } ) - if self:Goal( GoalVerb ) then - local GoalToDo = self:GetGoalTotal( GoalVerb ) - self:GetGoalCount( GoalVerb ) - self:T2( "GoalToDo = " .. GoalToDo ) - if GoalToDo <= 0 then - else - GoalReached = false - break - end - else - break - end - end - - self:T( { GoalReached, self.GoalTasks } ) - return GoalReached -end - ---- Adds an Additional Goal for the TASK to be achieved. --- @param string GoalVerb is the name of the Goal of the TASK. --- @param string GoalTask is a text describing the Goal of the TASK to be achieved. --- @param number GoalIncrease is a number by which the Goal achievement is increasing. -function TASK:AddGoalCompletion( GoalVerb, GoalTask, GoalIncrease ) - self:F2( { GoalVerb, GoalTask, GoalIncrease } ) - - if self:Goal( GoalVerb ) then - self.GoalTasks[GoalVerb].Goals[#self.GoalTasks[GoalVerb].Goals+1] = GoalTask - self.GoalTasks[GoalVerb].GoalCount = self.GoalTasks[GoalVerb].GoalCount + GoalIncrease - end - return self -end - ---- Returns if the additional Goal for the TASK was completed. --- @param ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used. --- @return string Goals -function TASK:GetGoalCompletion( GoalVerb ) - self:F2( { GoalVerb } ) - - if self:Goal( GoalVerb ) then - local Goals = "" - for GoalID, GoalName in pairs( self.GoalTasks[GoalVerb].Goals ) do Goals = Goals .. GoalName .. " + " end - return Goals:gsub(" + $", ""), self.GoalTasks[GoalVerb].GoalCount - end -end - -function TASK.MenuAction( Parameter ) - Parameter.ReferenceTask.ExecuteStage = _TransportExecuteStage.EXECUTING - Parameter.ReferenceTask.Cargo = Parameter.CargoTask -end - -function TASK:StageExecute() - self:F() - - local Execute = false - - if self.Frequency == STAGE.FREQUENCY.REPEAT then - Execute = true - elseif self.Frequency == STAGE.FREQUENCY.NONE then - Execute = false - elseif self.Frequency >= 0 then - Execute = true - self.Frequency = self.Frequency - 1 + MenuCategory = MenuCategory or {} + if MenuCategory[TaskGroupName] then + for _, Menu in pairs( MenuCategory[TaskGroupName] ) do + Menu:Remove() + end + MenuCategory[TaskGroupName] = nil end - return Execute - -end - ---- Work function to set signal events within a TASK. -function TASK:AddSignal( SignalUnitNames, SignalType, SignalColor, SignalHeight ) - self:F() + MenuStatus = MenuStatus or {} + if MenuStatus[TaskGroupName] then + MenuStatus[TaskGroupName]:Remove() + MenuStatus[TaskGroupName] = nil + end - local Valid = true - - if Valid then - if type( SignalUnitNames ) == "table" then - self.LandingZoneSignalUnitNames = SignalUnitNames - else - self.LandingZoneSignalUnitNames = { SignalUnitNames } - end - self.LandingZoneSignalType = SignalType - self.LandingZoneSignalColor = SignalColor - self.Signalled = false - if SignalHeight ~= nil then - self.LandingZoneSignalHeight = SignalHeight - else - self.LandingZoneSignalHeight = 0 - end - - if self.TaskBriefing then - self.TaskBriefing = self.TaskBriefing .. " " .. SignalColor.TEXT .. " " .. SignalType.TEXT .. " will be fired when entering the landing zone." - end - end - - return Valid + MenuAbort = MenuAbort or {} + if MenuAbort[TaskGroupName] then + MenuAbort[TaskGroupName]:Remove() + MenuAbort[TaskGroupName] = nil + end + end ---- When the CLIENT is approaching the landing zone, a RED SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeRed( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.RED, SignalHeight ) +function TASK_BASE.MenuAssignToGroup( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, a GREEN SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeGreen( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.GREEN, SignalHeight ) -end - ---- When the CLIENT is approaching the landing zone, a BLUE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeBlue( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.BLUE, SignalHeight ) +function TASK_BASE.MenuTaskStatus( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, a WHITE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeWhite( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.WHITE, SignalHeight ) +function TASK_BASE.MenuTaskAbort( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) end ---- When the CLIENT is approaching the landing zone, an ORANGE SMOKE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddSmokeOrange( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.ORANGE, SignalHeight ) + + +--- Returns the @{Task} name. +-- @param #TASK_BASE self +-- @return #string TaskName +function TASK_BASE:GetTaskName() + return self.TaskName end ---- When the CLIENT is approaching the landing zone, a RED FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareRed( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.RED, SignalHeight ) + +--- Add Process to @{Task} with key @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddProcess( TaskUnit, Process ) + local TaskUnitName = TaskUnit:GetName() + self.Processes = self.Processes or {} + self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} + self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process + return Process end ---- When the CLIENT is approaching the landing zone, a GREEN FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareGreen( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.GREEN, SignalHeight ) -end - ---- When the CLIENT is approaching the landing zone, a BLUE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareBlue( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.BLUE, SignalHeight ) + +--- Remove Processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + Process:StopEvents() + Process = nil + self.Processes[TaskUnitName][ProcessID] = nil + self:E( self.Processes[TaskUnitName][ProcessID] ) + end + self.Processes[TaskUnitName] = nil end ---- When the CLIENT is approaching the landing zone, a WHITE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareWhite( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.WHITE, SignalHeight ) +--- Fail processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:FailProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + self:E( { "Failing process: ", Process } ) + Process.Fsm:Fail() + end end ---- When the CLIENT is approaching the landing zone, an ORANGE FLARE will be fired by an optional SignalUnitNames. --- @param table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone. --- @param number SignalHeight Altitude that the Signal should be fired... -function TASK:AddFlareOrange( SignalUnitNames, SignalHeight ) - self:F() - self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.ORANGE, SignalHeight ) +--- Add a FiniteStateMachine to @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) + local TaskUnitName = TaskUnit:GetName() + self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} + self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm + return Fsm end + +--- Remove FiniteStateMachines from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveStateMachines( TaskUnitName ) + + for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do + Fsm = nil + self.Fsm[TaskUnitName][_] = nil + self:E( self.Fsm[TaskUnitName][_] ) + end + self.Fsm[TaskUnitName] = nil +end + +--- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:HasStateMachine( TaskUnitName ) + + self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) + return ( self.Fsm[TaskUnitName] ~= nil ) +end + + + + + +--- Register a potential new assignment for a new spawned @{Unit}. +-- Tasks only get assigned if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventAssignUnit( Event ) + if Event.IniUnit then + self:F( Event ) + local TaskUnit = Event.IniUnit + if TaskUnit:IsAlive() then + local TaskPlayerName = TaskUnit:GetPlayerName() + if TaskPlayerName ~= nil then + if not self:HasStateMachine( TaskUnit ) then + -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. + local TaskGroup = TaskUnit:GetGroup() + if self:IsAssignedToGroup( TaskGroup ) then + self:AssignToUnit( TaskUnit ) + end + end + end + end + end + return nil +end + +--- Catches the "player leave unit" event for a @{Unit} .... +-- When a player is an air unit, and leaves the unit: +-- +-- * and he is not at an airbase runway on the ground, he will fail its task. +-- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. +-- This is important to model the change from plane types for a player during mission assignment. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventPlayerLeaveUnit( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + if TaskUnit:IsAir() then + if TaskUnit:IsAboveRunway() then + -- do nothing + else + self:E( "IsNotAboveRunway" ) + -- Player left airplane during an assigned task and was not at an airbase. + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + end + end + + end + return nil +end + +--- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... +-- There are only assignments if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventDead( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + + local TaskGroup = Event.IniUnit:GetGroup() + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + end + return nil +end + +--- Gets the Scoring of the task +-- @param #TASK_BASE self +-- @return Scoring#SCORING Scoring +function TASK_BASE:GetScoring() + return self.Mission:GetScoring() +end + + +--- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. +-- @param #TASK_BASE self +-- @return #string The Task ID +function TASK_BASE:GetTaskIndex() + + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + local TaskName = self:GetName() + + return TaskCategory .. "." ..TaskType .. "." .. TaskName +end + +--- Sets the Name of the Task +-- @param #TASK_BASE self +-- @param #string TaskName +function TASK_BASE:SetName( TaskName ) + self.TaskName = TaskName +end + +--- Gets the Name of the Task +-- @param #TASK_BASE self +-- @return #string The Task Name +function TASK_BASE:GetName() + return self.TaskName +end + +--- Sets the Type of the Task +-- @param #TASK_BASE self +-- @param #string TaskType +function TASK_BASE:SetType( TaskType ) + self.TaskType = TaskType +end + +--- Gets the Type of the Task +-- @param #TASK_BASE self +-- @return #string TaskType +function TASK_BASE:GetType() + return self.TaskType +end + +--- Sets the Category of the Task +-- @param #TASK_BASE self +-- @param #string TaskCategory +function TASK_BASE:SetCategory( TaskCategory ) + self.TaskCategory = TaskCategory +end + +--- Gets the Category of the Task +-- @param #TASK_BASE self +-- @return #string TaskCategory +function TASK_BASE:GetCategory() + return self.TaskCategory +end + +--- Sets the ID of the Task +-- @param #TASK_BASE self +-- @param #string TaskID +function TASK_BASE:SetID( TaskID ) + self.TaskID = TaskID +end + +--- Gets the ID of the Task +-- @param #TASK_BASE self +-- @return #string TaskID +function TASK_BASE:GetID() + return self.TaskID +end + + +--- Sets a @{Task} to status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:StateSuccess() + self:SetState( self, "State", "Success" ) + return self +end + +--- Is the @{Task} status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateSuccess() + return self:GetStateString() == "Success" +end + +--- Sets a @{Task} to status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:StateFailed() + self:SetState( self, "State", "Failed" ) + return self +end + +--- Is the @{Task} status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateFailed() + return self:GetStateString() == "Failed" +end + +--- Sets a @{Task} to status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:StatePlanned() + self:SetState( self, "State", "Planned" ) + return self +end + +--- Is the @{Task} status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStatePlanned() + return self:GetStateString() == "Planned" +end + +--- Sets a @{Task} to status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:StateAssigned() + self:SetState( self, "State", "Assigned" ) + return self +end + +--- Is the @{Task} status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateAssigned() + return self:GetStateString() == "Assigned" +end + +--- Sets a @{Task} to status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:StateHold() + self:SetState( self, "State", "Hold" ) + return self +end + +--- Is the @{Task} status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateHold() + return self:GetStateString() == "Hold" +end + +--- Sets a @{Task} to status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:StateReplanned() + self:SetState( self, "State", "Replanned" ) + return self +end + +--- Is the @{Task} status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateReplanned() + return self:GetStateString() == "Replanned" +end + +--- Gets the @{Task} status. +-- @param #TASK_BASE self +function TASK_BASE:GetStateString() + return self:GetState( self, "State" ) +end + +--- Sets a @{Task} briefing. +-- @param #TASK_BASE self +-- @param #string TaskBriefing +-- @return #TASK_BASE self +function TASK_BASE:SetBriefing( TaskBriefing ) + self.TaskBriefing = TaskBriefing + return self +end + + + +--- Adds a score for the TASK to be achieved. +-- @param #TASK_BASE self +-- @param #string TaskStatus is the status of the TASK when the score needs to be given. +-- @param #string ScoreText is a text describing the score that is given according the status. +-- @param #number Score is a number providing the score of the status. +-- @return #TASK_BASE self +function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) + self:F2( { TaskStatus, ScoreText, Score } ) + + self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} + self.Scores[TaskStatus].ScoreText = ScoreText + self.Scores[TaskStatus].Score = Score + return self +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) + + self:E("Assigned") + + local TaskGroup = TaskUnit:GetGroup() + + TaskGroup:Message( self.TaskBriefing, 20 ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + +end + + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) + + self:E("Success") + + self:UnAssignFromGroups() + + local TaskGroup = TaskUnit:GetGroup() + self.Mission:SetPlannedMenu() + + self:StateSuccess() + + -- The task has become successful, the event catchers can be cleaned. + self:CleanUp() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) + + self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) + + -- A task cannot be "failed", so a task will always be there waiting for players to join. + -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. + -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. + + self:UnAssignFromGroups() + self:StatePlanned() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) + + if self:IsTrace() then + MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() + end + + self:E( { Event, From, To } ) + self:SetState( self, "State", To ) + + if self.Scores[To] then + local Scoring = self:GetScoring() + if Scoring then + Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + +end + + +--- @param #TASK_BASE self +function TASK_BASE:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self +end + + +--- @param #TASK_BASE self +function TASK_BASE._Scheduler() + self:F2() + + return true +end + + + + --- A GOHOMETASK orchestrates the travel back to the home base, which is a specific zone defined within the ME. -- @module GOHOMETASK @@ -16964,13 +18844,17 @@ end -- @type MISSION -- @extends Base#BASE -- @field #MISSION.Clients _Clients +-- @field Menu#MENU_COALITION MissionMenu -- @field #string MissionBriefing MISSION = { ClassName = "MISSION", Name = "", MissionStatus = "PENDING", _Clients = {}, - _Tasks = {}, + Tasks = {}, + TaskMenus = {}, + TaskCategoryMenus = {}, + TaskTypeMenus = {}, _ActiveTasks = {}, GoalFunction = nil, MissionReportTrigger = 0, @@ -16991,49 +18875,177 @@ MISSION = { function MISSION:Meta() local self = BASE:Inherit( self, BASE:New() ) - self:F() return self end --- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. --- @param string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param string MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... --- @return MISSION --- @usage --- -- Declare a few missions. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Patriots', 'Primary', 'Our intelligence reports that 3 Patriot SAM defense batteries are located near Ruisi, Kvarhiti and Gori.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Package Delivery', 'Operational', 'In order to be in full control of the situation, we need you to deliver a very important package at a secret location. Fly undetected through the NATO defenses and deliver the secret package. The secret agent is located at waypoint 4.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Rescue General', 'Tactical', 'Our intelligence has received a remote signal behind Gori. We believe it is a very important Russian General that was captured by Georgia. Go out there and rescue him! Ensure you stay out of the battle zone, keep south. Waypoint 4 is the location of our Russian General.', 'Russia' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'SA-6 SAMs', 'Primary', 'Our intelligence reports that 3 SA-6 SAM defense batteries are located near Didmukha, Khetagurov and Berula. Eliminate the Russian SAMs.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Sling Load', 'Operational', 'Fly to the cargo pickup zone at Dzegvi or Kaspi, and sling the cargo to Soganlug airbase.', 'NATO' ) --- local Mission = MISSIONSCHEDULER.AddMission( 'Rescue secret agent', 'Tactical', 'In order to be in full control of the situation, we need you to rescue a secret agent from the woods behind enemy lines. Avoid the Russian defenses and rescue the agent. Keep south until Khasuri, and keep your eyes open for any SAM presence. The agent is located at waypoint 4 on your kneeboard.', 'NATO' ) +-- @param #MISSION self +-- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. +-- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. +-- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. +-- @param DCSCoalitionObject#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... +-- @return #MISSION self function MISSION:New( MissionName, MissionPriority, MissionBriefing, MissionCoalition ) self = MISSION:Meta() - self:T({ MissionName, MissionPriority, MissionBriefing, MissionCoalition }) + self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - local Valid = true - - Valid = routines.ValidateString( MissionName, "MissionName", Valid ) - Valid = routines.ValidateString( MissionPriority, "MissionPriority", Valid ) - Valid = routines.ValidateString( MissionBriefing, "MissionBriefing", Valid ) - Valid = routines.ValidateString( MissionCoalition, "MissionCoalition", Valid ) - - if Valid then - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - end + self.Name = MissionName + self.MissionPriority = MissionPriority + self.MissionBriefing = MissionBriefing + self.MissionCoalition = MissionCoalition return self end +--- Gets the mission name. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:GetName() + return self.Name +end + +--- Add a scoring to the mission. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:AddScoring( Scoring ) + self.Scoring = Scoring + return self +end + +--- Get the scoring object of a mission. +-- @param #MISSION self +-- @return #SCORING Scoring +function MISSION:GetScoring() + return self.Scoring +end + + +--- Sets the Planned Task menu. +-- @param #MISSION self +function MISSION:SetPlannedMenu() + + for _, Task in pairs( self.Tasks ) do + local Task = Task -- Task#TASK_BASE + Task:RemoveMenu() + Task:SetPlannedMenu() + end + +end + +--- Sets the Assigned Task menu. +-- @param #MISSION self +-- @param Task#TASK_BASE Task +-- @param #string MenuText The menu text. +-- @return #MISSION self +function MISSION:SetAssignedMenu( Task ) + + for _, Task in pairs( self.Tasks ) do + local Task = Task -- Task#TASK_BASE + Task:RemoveMenu() + Task:SetAssignedMenu() + end + +end + +--- Removes a Task menu. +-- @param #MISSION self +-- @param Task#TASK_BASE Task +-- @return #MISSION self +function MISSION:RemoveTaskMenu( Task ) + + Task:RemoveMenu() +end + + +--- Gets the mission menu for the coalition. +-- @param #MISSION self +-- @param Group#GROUP TaskGroup +-- @return Menu#MENU_COALITION self +function MISSION:GetMissionMenu( TaskGroup ) + local TaskGroupName = TaskGroup:GetName() + return self.MenuMission[TaskGroupName] +end + + +--- Clears the mission menu for the coalition. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:ClearMissionMenu() + self.MissionMenu:Remove() + self.MissionMenu = nil +end + +--- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. +-- @param #string TaskIndex is the Index of the @{Task} within the @{Mission}. +-- @param #number TaskID is the ID of the @{Task} within the @{Mission}. +-- @return Task#TASK_BASE The Task +-- @return #nil Returns nil if no task was found. +function MISSION:GetTask( TaskName ) + self:F( { TaskName } ) + + return self.Tasks[TaskName] +end + + +--- Register a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return Task#TASK_BASE The task added. +function MISSION:AddTask( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName] = Task + + return Task +end + +--- Removes a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return #nil The cleaned Task reference. +function MISSION:RemoveTask( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + Task:CleanUp() -- Cleans all events and sets task to nil to get Garbage Collected + + -- Ensure everything gets garbarge collected. + self.Tasks[TaskName] = nil + Task = nil + + return nil +end + +--- Return the next @{Task} ID to be completed within the @{Mission}. +-- @param #MISSION self +-- @param Task#TASK_BASE Task is the @{Task} object. +-- @return Task#TASK_BASE The task added. +function MISSION:GetNextTaskID( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 + + return self.Tasks[TaskName].n +end + + + +--- old stuff + --- Returns if a Mission has completed. -- @return bool function MISSION:IsCompleted() @@ -17222,66 +19234,6 @@ function MISSION:FindClient( ClientName ) end ---- Register a @{TASK} to be completed within the @{MISSION}. Note that there can be multiple @{TASK}s registered to be completed. Each TASK can be set a certain Goal. The MISSION will not be completed until all Goals are reached. --- @param TASK Task is the @{TASK} object. The object must have been instantiated with @{TASK:New} or any of its inherited @{TASK}s. --- @param number TaskNumber is the sequence number of the TASK within the MISSION. This number does have to be chronological. --- @return TASK --- @usage --- -- Define a few tasks for the Mission. --- PickupZones = { "NATO Gold Pickup Zone", "NATO Titan Pickup Zone" } --- PickupSignalUnits = { "NATO Gold Coordination Center", "NATO Titan Coordination Center" } --- --- -- Assign the Pickup Task --- local PickupTask = PICKUPTASK:New( PickupZones, CARGO_TYPE.ENGINEERS, CLIENT.ONBOARDSIDE.LEFT ) --- PickupTask:AddSmokeBlue( PickupSignalUnits ) --- PickupTask:SetGoalTotal( 3 ) --- Mission:AddTask( PickupTask, 1 ) --- --- -- Assign the Deploy Task --- local PatriotActivationZones = { "US Patriot Battery 1 Activation", "US Patriot Battery 2 Activation", "US Patriot Battery 3 Activation" } --- local PatriotActivationZonesSmokeUnits = { "US SAM Patriot - Battery 1 Control", "US SAM Patriot - Battery 2 Control", "US SAM Patriot - Battery 3 Control" } --- local DeployTask = DEPLOYTASK:New( PatriotActivationZones, CARGO_TYPE.ENGINEERS ) --- --DeployTask:SetCargoTargetZoneName( 'US Troops Attack ' .. math.random(2) ) --- DeployTask:AddSmokeBlue( PatriotActivationZonesSmokeUnits ) --- DeployTask:SetGoalTotal( 3 ) --- DeployTask:SetGoalTotal( 3, "Patriots activated" ) --- Mission:AddTask( DeployTask, 2 ) - -function MISSION:AddTask( Task, TaskNumber ) - self:F() - - self._Tasks[TaskNumber] = Task - self._Tasks[TaskNumber]:EnableEvents() - self._Tasks[TaskNumber].ID = TaskNumber - - return Task - end - ---- Get the TASK idenified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param number TaskNumber is the number of the @{TASK} within the @{MISSION}. --- @return TASK --- @usage --- -- Get Task 2 from the Mission. --- Task2 = Mission:GetTask( 2 ) - -function MISSION:GetTask( TaskNumber ) - self:F() - - local Valid = true - - local Task = nil - - if type(TaskNumber) ~= "number" then - Valid = false - end - - if Valid then - Task = self._Tasks[TaskNumber] - end - - return Task -end - --- Get all the TASKs from the Mission. This function is useful in GoalFunctions. -- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. -- @usage @@ -18509,7 +20461,7 @@ function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex ) if SpawnTemplate then - local UnitPoint = HostUnit:GetPointVec2() + local UnitPoint = HostUnit:GetVec2() self:T( { "Current point of ", self.SpawnTemplatePrefix, UnitPoint } ) @@ -18592,7 +20544,7 @@ function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex ) if ZoneRandomize == true then ZonePoint = Zone:GetRandomVec2() else - ZonePoint = Zone:GetPointVec2() + ZonePoint = Zone:GetVec2() end SpawnTemplate.route.points[1].x = ZonePoint.x @@ -20133,7 +22085,7 @@ function ESCORT._HoldPosition( MenuParam ) PointFrom.alt = GroupPoint.y PointFrom.alt_type = AI.Task.AltitudeType.BARO - local OrbitPoint = OrbitUnit:GetPointVec2() + local OrbitPoint = OrbitUnit:GetVec2() local PointTo = {} PointTo.x = OrbitPoint.x PointTo.y = OrbitPoint.y @@ -20326,7 +22278,7 @@ function ESCORT._AttackTarget( MenuParam ) SCHEDULER:New( EscortGroup, EscortGroup.PushTask, { EscortGroup:TaskCombo( - { EscortGroup:TaskFireAtPoint( AttackUnit:GetPointVec2(), 50 ) + { EscortGroup:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) } ) }, 10 @@ -20366,7 +22318,7 @@ function ESCORT._AssistTarget( MenuParam ) SCHEDULER:New( EscortGroupAttack, EscortGroupAttack.PushTask, { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetPointVec2(), 50 ) + { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) } ) }, 10 @@ -21569,7 +23521,7 @@ function PATROLZONE:NewPatrolRoute() -- If not, make a waypoint within the to that the PatrolGroup will fly at maximum speed to that point. -- --- Calculate the current route point. --- local CurrentVec2 = self.PatrolGroup:GetPointVec2() +-- local CurrentVec2 = self.PatrolGroup:GetVec2() -- local CurrentAltitude = self.PatrolGroup:GetUnit(1):GetAltitude() -- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) -- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( @@ -21848,7 +23800,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetPointVec2(), self.ReturnTresholdRange ) + local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) self:E( RangeZone ) @@ -21878,7 +23830,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() else -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetPointVec2().x, AIGroup:GetPointVec2().y ) + local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) self:T( ClosestAirbase.AirbaseName ) AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) @@ -22093,6 +24045,7 @@ function AIRBASEPOLICE_BASE:_AirbaseMonitor() self:SetState( self, "Taxi", true ) end + -- TODO: GetVelocityKMH function usage local VelocityVec3 = Client:GetVelocity() local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec local Velocity = Velocity * 3.6 -- now it is in km/h. @@ -23133,6 +25086,7 @@ end -- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} -- ========================================================== -- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. +-- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). -- -- 1.1) DETECTION_BASE constructor -- ------------------------------- @@ -23161,16 +25115,16 @@ end -- -- === -- --- 2) @{Detection#DETECTION_UNITGROUPS} class, extends @{Detection#DETECTION_BASE} +-- 2) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} -- =============================================================================== --- The @{Detection#DETECTION_UNITGROUPS} class will detect units within the battle zone for a FAC group, +-- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), -- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. -- -- 2.1) Retrieve the Detected Unit sets and Detected Zones -- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_UNITGROUPS}. +-- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. -- -- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. -- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). @@ -23182,53 +25136,59 @@ end -- -- 1.4) Flare or Smoke detected units -- ---------------------------------- --- Use the methods @{Detection#DETECTION_UNITGROUPS.FlareDetectedUnits}() or @{Detection#DETECTION_UNITGROUPS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. +-- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. -- -- 1.5) Flare or Smoke detected zones -- ---------------------------------- --- Use the methods @{Detection#DETECTION_UNITGROUPS.FlareDetectedZones}() or @{Detection#DETECTION_UNITGROUPS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. +-- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. -- -- === -- +-- ### Contributions: Mechanic - Concept & Testing +-- ### Authors: FlightControl : Design & Programming +-- -- @module Detection --- @author Mechanic : Concept & Testing --- @author FlightControl : Design & Programming --- DETECTION_BASE class -- @type DETECTION_BASE --- @field Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +-- @field Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @field DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @field #DETECTION_BASE.DetectedSets DetectedSets A list of @{Set#SET_BASE}s containing the objects in each set that were detected. The base class will not build the detected sets, but will leave that to the derived classes. +-- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. +-- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. +-- @field #number DetectionRun -- @extends Base#BASE DETECTION_BASE = { ClassName = "DETECTION_BASE", - DetectedSets = {}, - DetectedObjects = {}, - FACGroup = nil, + DetectionSetGroup = nil, DetectionRange = nil, + DetectedObjects = {}, + DetectionRun = 0, + DetectedObjectsIdentified = {}, } ---- @type DETECTION_BASE.DetectedSets --- @list - - ---- @type DETECTION_BASE.DetectedZones --- @list +--- @type DETECTION_BASE.DetectedObjects +-- @list <#DETECTION_BASE.DetectedObject> +--- @type DETECTION_BASE.DetectedObject +-- @field #string Name +-- @field #boolean Visible +-- @field #string Type +-- @field #number Distance +-- @field #boolean Identified --- DETECTION constructor. -- @param #DETECTION_BASE self --- @param Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +-- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @return #DETECTION_BASE self -function DETECTION_BASE:New( FACGroup, DetectionRange ) +function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) - self.FACGroup = FACGroup + self.DetectionSetGroup = DetectionSetGroup self.DetectionRange = DetectionRange self:InitDetectVisual( false ) @@ -23300,13 +25260,65 @@ function DETECTION_BASE:InitDetectDLINK( DetectDLINK ) self.DetectDLINK = DetectDLINK end ---- Gets the FAC group. +--- Determines if a detected object has already been identified during detection processing. -- @param #DETECTION_BASE self --- @return Group#GROUP self -function DETECTION_BASE:GetFACGroup() - self:F2() +-- @param #DETECTION_BASE.DetectedObject DetectedObject +-- @return #boolean true if already identified. +function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) + self:F3( DetectedObject.Name ) - return self.FACGroup + local DetectedObjectName = DetectedObject.Name + local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true + self:T3( DetectedObjectIdentified ) + return DetectedObjectIdentified +end + +--- Identifies a detected object during detection processing. +-- @param #DETECTION_BASE self +-- @param #DETECTION_BASE.DetectedObject DetectedObject +function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) + self:F( DetectedObject.Name ) + + local DetectedObjectName = DetectedObject.Name + self.DetectedObjectsIdentified[DetectedObjectName] = true +end + +--- UnIdentify a detected object during detection processing. +-- @param #DETECTION_BASE self +-- @param #DETECTION_BASE.DetectedObject DetectedObject +function DETECTION_BASE:UnIdentifyDetectedObject( DetectedObject ) + + local DetectedObjectName = DetectedObject.Name + self.DetectedObjectsIdentified[DetectedObjectName] = false +end + +--- UnIdentify all detected objects during detection processing. +-- @param #DETECTION_BASE self +function DETECTION_BASE:UnIdentifyAllDetectedObjects() + + self.DetectedObjectsIdentified = {} -- Table will be garbage collected. +end + +--- Gets a detected object with a given name. +-- @param #DETECTION_BASE self +-- @param #string ObjectName +-- @return #DETECTION_BASE.DetectedObject +function DETECTION_BASE:GetDetectedObject( ObjectName ) + self:F3( ObjectName ) + + if ObjectName then + local DetectedObject = self.DetectedObjects[ObjectName] + + -- Only return detected objects that are alive! + local DetectedUnit = UNIT:FindByName( ObjectName ) + if DetectedUnit and DetectedUnit:IsAlive() then + if self:IsDetectedObjectIdentified( DetectedObject ) == false then + return DetectedObject + end + end + end + + return nil end --- Get the detected @{Set#SET_BASE}s. @@ -23341,6 +25353,14 @@ function DETECTION_BASE:GetDetectedSet( Index ) return nil end +--- Get the detection Groups. +-- @param #DETECTION_BASE self +-- @return Group#GROUP +function DETECTION_BASE:GetDetectionSetGroup() + + local DetectionSetGroup = self.DetectionSetGroup + return DetectionSetGroup +end --- Make a DetectionSet table. This function will be overridden in the derived clsses. -- @param #DETECTION_BASE self @@ -23352,6 +25372,7 @@ function DETECTION_BASE:CreateDetectionSets() end + --- Schedule the DETECTION construction. -- @param #DETECTION_BASE self -- @param #number DelayTime The delay in seconds to wait the reporting. @@ -23373,141 +25394,195 @@ end function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:F2( { SchedulerName } ) - self.DetectedObjects = {} - self.DetectedSets = {} - self.DetectedZones = {} + self.DetectionRun = self.DetectionRun + 1 - if self.FACGroup:IsAlive() then - local FACGroupName = self.FACGroup:GetName() - - local FACDetectedTargets = self.FACGroup:GetDetectedTargets( - self.DetectVisual, - self.DetectOptical, - self.DetectRadar, - self.DetectIRST, - self.DetectRWR, - self.DetectDLINK - ) - - for FACDetectedTargetID, FACDetectedTarget in pairs( FACDetectedTargets ) do - local FACObject = FACDetectedTarget.object -- DCSObject#Object - self:T2( FACObject ) + self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table + + for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do + local DetectionGroup = DetectionGroupData -- Group#GROUP + + if DetectionGroup:IsAlive() then + + local DetectionGroupName = DetectionGroup:GetName() - if FACObject and FACObject:isExist() and FACObject.id_ < 50000000 then - - local FACDetectedObjectName = FACObject:getName() - - local FACDetectedObjectPositionVec3 = FACObject:getPoint() - local FACGroupPositionVec3 = self.FACGroup:GetPointVec3() - - local Distance = ( ( FACDetectedObjectPositionVec3.x - FACGroupPositionVec3.x )^2 + - ( FACDetectedObjectPositionVec3.y - FACGroupPositionVec3.y )^2 + - ( FACDetectedObjectPositionVec3.z - FACGroupPositionVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T( { FACGroupName, FACDetectedObjectName, Distance } ) - - if Distance <= self.DetectionRange then - - if not self.DetectedObjects[FACDetectedObjectName] then - self.DetectedObjects[FACDetectedObjectName] = {} - end - self.DetectedObjects[FACDetectedObjectName].Name = FACDetectedObjectName - self.DetectedObjects[FACDetectedObjectName].Visible = FACDetectedTarget.visible - self.DetectedObjects[FACDetectedObjectName].Type = FACDetectedTarget.type - self.DetectedObjects[FACDetectedObjectName].Distance = FACDetectedTarget.distance - else - -- if beyond the DetectionRange then nullify... - if self.DetectedObjects[FACDetectedObjectName] then - self.DetectedObjects[FACDetectedObjectName] = nil + local DetectionDetectedTargets = DetectionGroup:GetDetectedTargets( + self.DetectVisual, + self.DetectOptical, + self.DetectRadar, + self.DetectIRST, + self.DetectRWR, + self.DetectDLINK + ) + + for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do + local DetectionObject = DetectionDetectedTarget.object -- DCSObject#Object + self:T2( DetectionObject ) + + if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then + + local DetectionDetectedObjectName = DetectionObject:getName() + + local DetectionDetectedObjectPositionVec3 = DetectionObject:getPoint() + local DetectionGroupPositionVec3 = DetectionGroup:GetPointVec3() + + local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupPositionVec3.x )^2 + + ( DetectionDetectedObjectPositionVec3.y - DetectionGroupPositionVec3.y )^2 + + ( DetectionDetectedObjectPositionVec3.z - DetectionGroupPositionVec3.z )^2 + ) ^ 0.5 / 1000 + + self:T2( { DetectionGroupName, DetectionDetectedObjectName, Distance } ) + + if Distance <= self.DetectionRange then + + if not self.DetectedObjects[DetectionDetectedObjectName] then + self.DetectedObjects[DetectionDetectedObjectName] = {} + end + self.DetectedObjects[DetectionDetectedObjectName].Name = DetectionDetectedObjectName + self.DetectedObjects[DetectionDetectedObjectName].Visible = DetectionDetectedTarget.visible + self.DetectedObjects[DetectionDetectedObjectName].Type = DetectionDetectedTarget.type + self.DetectedObjects[DetectionDetectedObjectName].Distance = DetectionDetectedTarget.distance + else + -- if beyond the DetectionRange then nullify... + if self.DetectedObjects[DetectionDetectedObjectName] then + self.DetectedObjects[DetectionDetectedObjectName] = nil + end end end end + + self:T2( self.DetectedObjects ) + + -- okay, now we have a list of detected object names ... + -- Sort the table based on distance ... + table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) end - - self:T2( self.DetectedObjects ) - - -- okay, now we have a list of detected object names ... - -- Sort the table based on distance ... - self:T( { "Sorting DetectedObjects table:", self.DetectedObjects } ) - table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) - self:T( { "Sorted Targets Table:", self.DetectedObjects } ) - - -- Now group the DetectedObjects table into SET_BASEs, evaluating the DetectionZoneRange. - - if self.DetectedObjects then - self:CreateDetectionSets() - end - - end + + if self.DetectedObjects then + self:CreateDetectionSets() + end + + return true end ---- @type DETECTION_UNITGROUPS.DetectedSets --- @list --- - - ---- @type DETECTION_UNITGROUPS.DetectedZones --- @list --- ---- DETECTION_UNITGROUPS class --- @type DETECTION_UNITGROUPS --- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @field #DETECTION_UNITGROUPS.DetectedSets DetectedSets A list of @{Set#SET_UNIT}s containing the units in each set that were detected within a DetectionZoneRange. --- @field #DETECTION_UNITGROUPS.DetectedZones DetectedZones A list of @{Zone#ZONE_UNIT}s containing the zones of the reference detected units. +--- DETECTION_AREAS class +-- @type DETECTION_AREAS +-- @field DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @field #DETECTION_AREAS.DetectedAreas DetectedAreas A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. -- @extends Detection#DETECTION_BASE -DETECTION_UNITGROUPS = { - ClassName = "DETECTION_UNITGROUPS", - DetectedZones = {}, +DETECTION_AREAS = { + ClassName = "DETECTION_AREAS", + DetectedAreas = { n = 0 }, + DetectionZoneRange = nil, } +--- @type DETECTION_AREAS.DetectedAreas +-- @list <#DETECTION_AREAS.DetectedArea> + +--- @type DETECTION_AREAS.DetectedArea +-- @field Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @field Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @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 #number AreaID -- The identifier of the detected area. +-- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. ---- DETECTION_UNITGROUPS constructor. --- @param Detection#DETECTION_UNITGROUPS self --- @param Group#GROUP FACGroup The GROUP in the Forward Air Controller role. +--- DETECTION_AREAS constructor. +-- @param Detection#DETECTION_AREAS self +-- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Detection#DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:New( FACGroup, DetectionRange, DetectionZoneRange ) +-- @return Detection#DETECTION_AREAS self +function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( FACGroup, DetectionRange ) ) + local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) ) + self.DetectionZoneRange = DetectionZoneRange - self:Schedule( 10, 30 ) + self._SmokeDetectedUnits = false + self._FlareDetectedUnits = false + self._SmokeDetectedZones = false + self._FlareDetectedZones = false + + self:Schedule( 0, 15 ) return self end ---- Get the detected @{Zone#ZONE_UNIT}s. --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS.DetectedZones DetectedZones -function DETECTION_UNITGROUPS:GetDetectedZones() - - local DetectedZones = self.DetectedZones - return DetectedZones +--- Add a detected @{#DETECTION_AREAS.DetectedArea}. +-- @param Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @param Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @return #DETECTION_AREAS.DetectedArea DetectedArea +function DETECTION_AREAS:AddDetectedArea( Set, Zone ) + local DetectedAreas = self:GetDetectedAreas() + DetectedAreas.n = self:GetDetectedAreaCount() + 1 + DetectedAreas[DetectedAreas.n] = {} + local DetectedArea = DetectedAreas[DetectedAreas.n] + DetectedArea.Set = Set + DetectedArea.Zone = Zone + DetectedArea.Removed = false + DetectedArea.AreaID = DetectedAreas.n + + return DetectedArea end ---- Get the amount of @{Zone#ZONE_UNIT}s with detected units. --- @param #DETECTION_UNITGROUPS self --- @return #number Count -function DETECTION_UNITGROUPS:GetDetectedZoneCount() - - local DetectedZoneCount = #self.DetectedZones - return DetectedZoneCount +--- Remove a detected @{#DETECTION_AREAS.DetectedArea} with a given Index. +-- @param #DETECTION_AREAS self +-- @param #number Index The Index of the detection are to be removed. +-- @return #nil +function DETECTION_AREAS:RemoveDetectedArea( Index ) + local DetectedAreas = self:GetDetectedAreas() + local DetectedAreaCount = self:GetDetectedAreaCount() + local DetectedArea = DetectedAreas[Index] + local DetectedAreaSet = DetectedArea.Set + DetectedArea[Index] = nil + return nil end ---- Get a SET of detected objects using a given numeric index. --- @param #DETECTION_UNITGROUPS self + +--- Get the detected @{#DETECTION_AREAS.DetectedAreas}. +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS.DetectedAreas DetectedAreas +function DETECTION_AREAS:GetDetectedAreas() + + local DetectedAreas = self.DetectedAreas + return DetectedAreas +end + +--- Get the amount of @{#DETECTION_AREAS.DetectedAreas}. +-- @param #DETECTION_AREAS self +-- @return #number DetectedAreaCount +function DETECTION_AREAS:GetDetectedAreaCount() + + local DetectedAreaCount = self.DetectedAreas.n + return DetectedAreaCount +end + +--- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. +-- @param #DETECTION_AREAS self -- @param #number Index --- @return Zone#ZONE_UNIT -function DETECTION_UNITGROUPS:GetDetectedZone( Index ) +-- @return Set#SET_UNIT DetectedSet +function DETECTION_AREAS:GetDetectedSet( Index ) - local DetectedZone = self.DetectedZones[Index] + local DetectedSetUnit = self.DetectedAreas[Index].Set + if DetectedSetUnit then + return DetectedSetUnit + end + + return nil +end + +--- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. +-- @param #DETECTION_AREAS self +-- @param #number Index +-- @return Zone#ZONE_UNIT DetectedZone +function DETECTION_AREAS:GetDetectedZone( Index ) + + local DetectedZone = self.DetectedAreas[Index].Zone if DetectedZone then return DetectedZone end @@ -23515,10 +25590,107 @@ function DETECTION_UNITGROUPS:GetDetectedZone( Index ) return nil end +--- Background worker function to determine if there are friendlies nearby ... +-- @param #DETECTION_AREAS self +-- @param Unit#UNIT ReportUnit +function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) + self:F2() + + local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT + + DetectedArea.FriendliesNearBy = false + + local SphereSearch = { + id = world.VolumeType.SPHERE, + params = { + point = DetectedZoneUnit:GetPointVec3(), + radius = 6000, + } + + } + + --- @param DCSUnit#Unit FoundDCSUnit + -- @param Group#GROUP ReportGroup + -- @param Set#SET_GROUP ReportSetGroup + local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) + + local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Unit#UNIT + local ReportSetGroup = ReportGroupData.ReportSetGroup + + local EnemyCoalition = DetectedZoneUnit:GetCoalition() + + local FoundUnitCoalition = FoundDCSUnit:getCoalition() + local FoundUnitName = FoundDCSUnit:getName() + local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() + local EnemyUnitName = DetectedZoneUnit:GetName() + local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil + + self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + + if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then + DetectedArea.FriendliesNearBy = true + return false + end + + return true + end + + world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) + +end + + + +--- Returns if there are friendlies nearby the FAC units ... +-- @param #DETECTION_AREAS self +-- @return #boolean trhe if there are friendlies nearby +function DETECTION_AREAS:IsFriendliesNearBy( DetectedArea ) + + self:T3( DetectedArea.FriendliesNearBy ) + return DetectedArea.FriendliesNearBy or false +end + +--- Calculate the maxium A2G threat level of the DetectedArea. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) + + local MaxThreatLevelA2G = 0 + for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do + local ThreatUnit = UnitData -- Unit#UNIT + local ThreatLevelA2G = ThreatUnit:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + end + end + + self:T3( MaxThreatLevelA2G ) + DetectedArea.MaxThreatLevelA2G = MaxThreatLevelA2G + +end + +--- Returns the A2G threat level of the units in the DetectedArea +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #number a scale from 0 to 10. +function DETECTION_AREAS:GetTreatLevelA2G( DetectedArea ) + + self:T3( DetectedArea.MaxThreatLevelA2G ) + return DetectedArea.MaxThreatLevelA2G +end + + + --- Smoke the detected units --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:SmokeDetectedUnits() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:SmokeDetectedUnits() self:F2() self._SmokeDetectedUnits = true @@ -23526,9 +25698,9 @@ function DETECTION_UNITGROUPS:SmokeDetectedUnits() end --- Flare the detected units --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:FlareDetectedUnits() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:FlareDetectedUnits() self:F2() self._FlareDetectedUnits = true @@ -23536,9 +25708,9 @@ function DETECTION_UNITGROUPS:FlareDetectedUnits() end --- Smoke the detected zones --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:SmokeDetectedZones() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:SmokeDetectedZones() self:F2() self._SmokeDetectedZones = true @@ -23546,77 +25718,310 @@ function DETECTION_UNITGROUPS:SmokeDetectedZones() end --- Flare the detected zones --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:FlareDetectedZones() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:FlareDetectedZones() self:F2() self._FlareDetectedZones = true return self end +--- Add a change to the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @param #string ChangeCode +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AddChangeArea( DetectedArea, ChangeCode, AreaUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode].AreaID = AreaID + DetectedArea.Changes[ChangeCode].AreaUnitType = AreaUnitType + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, AreaUnitType } ) + + return self +end + + +--- Add a change to the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @param #string ChangeCode +-- @param #string ChangeUnitType +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AddChangeUnit( DetectedArea, ChangeCode, ChangeUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] or 0 + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] + 1 + DetectedArea.Changes[ChangeCode].AreaID = AreaID + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, ChangeUnitType } ) + + return self +end + +--- Make text documenting the changes of the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #string The Changes text +function DETECTION_AREAS:GetChangeText( DetectedArea ) + self:F( DetectedArea ) + + local MT = {} + + for ChangeCode, ChangeData in pairs( DetectedArea.Changes ) do + + if ChangeCode == "AA" then + MT[#MT+1] = "Detected new area " .. ChangeData.AreaID .. ". The center target is a " .. ChangeData.AreaUnitType .. "." + end + + if ChangeCode == "RAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". Removed the center target " .. ChangeData.AreaUnitType "." + end + + if ChangeCode == "AAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". The new center target is a " .. ChangeData.AreaUnitType "." + end + + if ChangeCode == "RA" then + MT[#MT+1] = "Removed old area " .. ChangeData.AreaID .. ". No more targets in this area." + end + + if ChangeCode == "AU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Detected for area " .. ChangeData.AreaID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + if ChangeCode == "RU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Removed for area " .. ChangeData.AreaID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + end + + return table.concat( MT, "\n" ) + +end + + +--- Accepts changes from the detected zone. +-- @param #DETECTION_AREAS self +-- @param #DETECTION_AREAS.DetectedArea DetectedArea +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:AcceptChanges( DetectedArea ) + + DetectedArea.Changed = false + DetectedArea.Changes = {} + + return self +end + --- Make a DetectionSet table. This function will be overridden in the derived clsses. --- @param #DETECTION_UNITGROUPS self --- @return #DETECTION_UNITGROUPS self -function DETECTION_UNITGROUPS:CreateDetectionSets() +-- @param #DETECTION_AREAS self +-- @return #DETECTION_AREAS self +function DETECTION_AREAS:CreateDetectionSets() self:F2() - for DetectedUnitName, DetectedUnitData in pairs( self.DetectedObjects ) do - self:T( DetectedUnitData.Name ) - local DetectedUnit = UNIT:FindByName( DetectedUnitData.Name ) -- Unit#UNIT - if DetectedUnit and DetectedUnit:IsAlive() then - self:T( DetectedUnit:GetName() ) - if #self.DetectedSets == 0 then - self:T( { "Adding Unit Set #", 1 } ) - self.DetectedZones[1] = ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - self.DetectedSets[1] = SET_UNIT:New() - self.DetectedSets[1]:AddUnit( DetectedUnit ) + -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. + -- Regroup when needed, split groups when needed. + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + + local DetectedSet = DetectedArea.Set + + local AreaExists = false -- This flag will determine of the detected area is still existing. + + -- First test if the center unit is detected in the detection area. + self:T3( DetectedArea.Zone.ZoneUNIT.UnitName ) + local DetectedZoneObject = self:GetDetectedObject( DetectedArea.Zone.ZoneUNIT.UnitName ) + self:T3( { "Detecting Zone Object", DetectedArea.AreaID, DetectedArea.Zone, DetectedZoneObject } ) + + if DetectedZoneObject then + + --self:IdentifyDetectedObject( DetectedZoneObject ) + AreaExists = true + + + else - local AddedToSet = false - for DetectedZoneIndex = 1, #self.DetectedZones do - self:T( "Detected Unit Set #" .. DetectedZoneIndex ) - local DetectedUnitSet = self.DetectedSets[DetectedZoneIndex] -- Set#SET_BASE - local DetectedZone = self.DetectedZones[DetectedZoneIndex] -- Zone#ZONE_UNIT - if DetectedUnit:IsInZone( DetectedZone ) then - self:T( "Adding to Unit Set #" .. DetectedZoneIndex ) - DetectedUnitSet:AddUnit( DetectedUnit ) - AddedToSet = true + -- The center object of the detected area has not been detected. Find an other unit of the set to become the center of the area. + -- First remove the center unit from the set. + DetectedSet:RemoveUnitsByName( DetectedArea.Zone.ZoneUNIT.UnitName ) + + self:AddChangeArea( DetectedArea, 'RAU', DetectedArea.Zone.ZoneUNIT:GetTypeName() ) + + -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. + for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do + + local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) + + -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. + -- If the DetectedUnit was already identified, DetectedObject will be nil. + if DetectedObject then + self:IdentifyDetectedObject( DetectedObject ) + AreaExists = true + + -- Assign the Unit as the new center unit of the detected area. + DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) + + self:AddChangeArea( DetectedArea, "AAU", DetectedArea.Zone.ZoneUNIT:GetTypeName() ) + + -- We don't need to add the DetectedObject to the area set, because it is already there ... + break end end - if AddedToSet == false then - local DetectedZoneIndex = #self.DetectedZones + 1 - self:T( "Adding new zone #" .. DetectedZoneIndex ) - self.DetectedZones[DetectedZoneIndex] = ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - self.DetectedSets[DetectedZoneIndex] = SET_UNIT:New() - self.DetectedSets[DetectedZoneIndex]:AddUnit( DetectedUnit ) - end + end + + -- Now we've determined the center unit of the area, now we can iterate the units in the detected area. + -- Note that the position of the area may have moved due to the center unit repositioning. + -- If no center unit was identified, then the detected area does not exist anymore and should be deleted, as there are no valid units that can be the center unit. + if AreaExists then + + -- ok, we found the center unit of the area, now iterate through the detected area set and see which units are still within the center unit zone ... + -- Those units within the zone are flagged as Identified. + -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. + for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do + + local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedObject = nil + if DetectedUnit:IsAlive() then + --self:E(DetectedUnit:GetName()) + DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) + end + if DetectedObject then + + -- Check if the DetectedUnit is within the DetectedArea.Zone + if DetectedUnit:IsInZone( DetectedArea.Zone ) then + + -- Yes, the DetectedUnit is within the DetectedArea.Zone, no changes, DetectedUnit can be kept within the Set. + self:IdentifyDetectedObject( DetectedObject ) + + else + -- No, the DetectedUnit is not within the DetectedArea.Zone, remove DetectedUnit from the Set. + DetectedSet:Remove( DetectedUnitName ) + self:AddChangeUnit( DetectedArea, "RU", DetectedUnit:GetTypeName() ) + end + + else + -- There was no DetectedObject, remove DetectedUnit from the Set. + self:AddChangeUnit( DetectedArea, "RU", "destroyed target" ) + DetectedSet:Remove( DetectedUnitName ) + + -- The DetectedObject has been identified, because it does not exist ... + -- self:IdentifyDetectedObject( DetectedObject ) + end + end + else + self:RemoveDetectedArea( DetectedAreaID ) + self:AddChangeArea( DetectedArea, "RA" ) end end end - -- Now all the tests should have been build, now make some smoke and flares... + -- We iterated through the existing detection areas and: + -- - We checked which units are still detected in each detection area. Those units were flagged as Identified. + -- - We recentered the detection area to new center units where it was needed. + -- + -- Now we need to loop through the unidentified detected units and see where they belong: + -- - They can be added to a new detection area and become the new center unit. + -- - They can be added to a new detection area. + for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do + + local DetectedObject = self:GetDetectedObject( DetectedUnitName ) + + if DetectedObject then + + -- We found an unidentified unit outside of any existing detection area. + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Unit#UNIT + + local AddedToDetectionArea = false + + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + self:T( "Detection Area #" .. DetectedArea.AreaID ) + local DetectedSet = DetectedArea.Set + if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedArea.Zone ) then + self:IdentifyDetectedObject( DetectedObject ) + DetectedSet:AddUnit( DetectedUnit ) + AddedToDetectionArea = true + self:AddChangeUnit( DetectedArea, "AU", DetectedUnit:GetTypeName() ) + end + end + end + + if AddedToDetectionArea == false then + + -- New detection area + local DetectedArea = self:AddDetectedArea( + SET_UNIT:New(), + ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) + ) + --self:E( DetectedArea.Zone.ZoneUNIT.UnitName ) + DetectedArea.Set:AddUnit( DetectedUnit ) + self:AddChangeArea( DetectedArea, "AA", DetectedUnit:GetTypeName() ) + end + end + end - for DetectedZoneIndex = 1, #self.DetectedZones do - local DetectedUnitSet = self.DetectedSets[DetectedZoneIndex] -- Set#SET_BASE - local DetectedZone = self.DetectedZones[DetectedZoneIndex] -- Zone#ZONE_UNIT - self:T( "Detected Set #" .. DetectedZoneIndex ) - DetectedUnitSet:ForEachUnit( + -- Now all the tests should have been build, now make some smoke and flares... + -- We also report here the friendlies within the detected areas. + + for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + self:ReportFriendliesNearBy( { DetectedArea = DetectedArea, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table + self:CalculateThreatLevelA2G( DetectedArea ) -- Calculate A2G threat level + + if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then + DetectedZone.ZoneUNIT:SmokeRed() + end + DetectedSet:ForEachUnit( --- @param Unit#UNIT DetectedUnit function( DetectedUnit ) - self:T( DetectedUnit:GetName() ) - if self._FlareDetectedUnits then - DetectedUnit:FlareRed() - end - if self._SmokeDetectedUnits then - DetectedUnit:SmokeRed() + if DetectedUnit:IsAlive() then + self:T( "Detected Set #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) + if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then + DetectedUnit:FlareGreen() + end + if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then + DetectedUnit:SmokeGreen() + end end end ) - if self._FlareDetectedZones then + if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then DetectedZone:FlareZone( POINT_VEC3.SmokeColor.White, 30, math.random( 0,90 ) ) end - if self._SmokeDetectedZones then + if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then DetectedZone:SmokeZone( POINT_VEC3.SmokeColor.White, 30 ) end end @@ -23624,216 +26029,2741 @@ function DETECTION_UNITGROUPS:CreateDetectionSets() end ---- This module contains the FAC classes. +--- This module contains the DETECTION_MANAGER class and derived classes. -- -- === -- --- 1) @{Fac#FAC_BASE} class, extends @{Base#BASE} --- ============================================== --- The @{Fac#FAC_BASE} class defines the core functions to report detected objects to clients. --- Reportings can be done in several manners, and it is up to the derived classes if FAC_BASE to model the reporting behaviour. +-- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} +-- ==================================================================== +-- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. +-- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. -- --- 1.1) FAC_BASE constructor: --- ---------------------------- --- * @{Fac#FAC_BASE.New}(): Create a new FAC_BASE instance. +-- 1.1) DETECTION_MANAGER constructor: +-- ----------------------------------- +-- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. -- --- 1.2) FAC_BASE reporting: --- ------------------------ --- Derived FAC_BASE classes will reports detected units using the method @{Fac#FAC_BASE.ReportDetected}(). This method implements polymorphic behaviour. +-- 1.2) DETECTION_MANAGER reporting: +-- --------------------------------- +-- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. -- --- The time interval in seconds of the reporting can be changed using the methods @{Fac#FAC_BASE.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{Fac#FAC_BASE.SetReportDisplayTime}(). --- Derived classes need to implement the method @{Fac#FAC_BASE.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. +-- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). +-- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). +-- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. -- --- Reporting can be started and stopped using the methods @{Fac#FAC_BASE.StartReporting}() and @{Fac#FAC_BASE.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{Fac#FAC_BASE#ReportNow}(). +-- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. +-- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). -- -- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. -- -- === -- --- 2) @{Fac#FAC_REPORTING} class, extends @{Fac#FAC_BASE} --- ====================================================== --- The @{Fac#FAC_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Fac#FAC_BASE} class. +-- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} +-- ========================================================================================= +-- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. -- --- 2.1) FAC_REPORTING constructor: +-- 2.1) DETECTION_REPORTING constructor: -- ------------------------------- --- The @{Fac#FAC_REPORTING.New}() method creates a new FAC_REPORTING instance. +-- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- -- === -- --- @module Fac --- @author Mechanic, Prof_Hilactic, FlightControl : Concept & Testing --- @author FlightControl : Design & Programming +-- 3) @{#DETECTION_DISPATCHER} class, extends @{#DETECTION_MANAGER} +-- ================================================================ +-- The @{#DETECTION_DISPATCHER} class implements the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of FAC (groups). +-- The FAC will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. +-- Find a summary below describing for which situation a task type is created: +-- +-- * **CAS Task**: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter. +-- * **BAI Task**: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter. +-- * **SEAD Task**: Is created when there are enemy ground units wihtin range of the FAC, with air search radars. +-- +-- Other task types will follow... +-- +-- 3.1) DETECTION_DISPATCHER constructor: +-- -------------------------------------- +-- The @{#DETECTION_DISPATCHER.New}() method creates a new DETECTION_DISPATCHER instance. +-- +-- === +-- +-- ### Contributions: Mechanic, Prof_Hilactic, FlightControl - Concept & Testing +-- ### Author: FlightControl - Framework Design & Programming +-- +-- @module DetectionManager - - ---- FAC_BASE class. --- @type FAC_BASE --- @field Set#SET_CLIENT ClientSet The clients to which the FAC will report to. --- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. --- @extends Base#BASE -FAC_BASE = { - ClassName = "FAC_BASE", - ClientSet = nil, - Detection = nil, -} - ---- FAC constructor. --- @param #FAC_BASE self --- @param Set#SET_CLIENT ClientSet --- @param Detection#DETECTION_BASE Detection --- @return #FAC_BASE self -function FAC_BASE:New( ClientSet, Detection ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Fac#FAC_BASE +do -- DETECTION MANAGER - self.ClientSet = ClientSet - self.Detection = Detection + --- DETECTION_MANAGER class. + -- @type DETECTION_MANAGER + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @extends Base#BASE + DETECTION_MANAGER = { + ClassName = "DETECTION_MANAGER", + SetGroup = nil, + Detection = nil, + } - self:SetReportInterval( 60 ) - self:SetReportDisplayTime( 15 ) + --- FAC constructor. + -- @param #DETECTION_MANAGER self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:New( SetGroup, Detection ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) -- Detection#DETECTION_MANAGER + + self.SetGroup = SetGroup + self.Detection = Detection + + self:SetReportInterval( 30 ) + self:SetReportDisplayTime( 25 ) + + return self + end + + --- Set the reporting time interval. + -- @param #DETECTION_MANAGER self + -- @param #number ReportInterval The interval in seconds when a report needs to be done. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:SetReportInterval( ReportInterval ) + self:F2() + + self._ReportInterval = ReportInterval + end + + + --- Set the reporting message display time. + -- @param #DETECTION_MANAGER self + -- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime ) + self:F2() + + self._ReportDisplayTime = ReportDisplayTime + end + + --- Get the reporting message display time. + -- @param #DETECTION_MANAGER self + -- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. + function DETECTION_MANAGER:GetReportDisplayTime() + self:F2() + + return self._ReportDisplayTime + end + + + + --- Reports the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_MANAGER self + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:ReportDetected( Detection ) + self:F2() + + end + + --- Schedule the FAC reporting. + -- @param #DETECTION_MANAGER self + -- @param #number DelayTime The delay in seconds to wait the reporting. + -- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. + -- @return #DETECTION_MANAGER self + function DETECTION_MANAGER:Schedule( DelayTime, ReportInterval ) + self:F2() + + self._ScheduleDelayTime = DelayTime + + self:SetReportInterval( ReportInterval ) + + self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "DetectionManager" }, self._ScheduleDelayTime, self._ReportInterval ) + return self + end + + --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. + -- @param #DETECTION_MANAGER self + function DETECTION_MANAGER:_FacScheduler( SchedulerName ) + self:F2( { SchedulerName } ) + + return self:ProcessDetected( self.Detection ) + +-- self.SetGroup:ForEachGroup( +-- --- @param Group#GROUP Group +-- function( Group ) +-- if Group:IsAlive() then +-- return self:ProcessDetected( self.Detection ) +-- end +-- end +-- ) + +-- return true + end - return self -end - ---- Set the reporting time interval. --- @param #FAC_BASE self --- @param #number ReportInterval The interval in seconds when a report needs to be done. --- @return #FAC_BASE self -function FAC_BASE:SetReportInterval( ReportInterval ) - self:F2() - - self._ReportInterval = ReportInterval end ---- Set the reporting message display time. --- @param #FAC_BASE self --- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. --- @return #FAC_BASE self -function FAC_BASE:SetReportDisplayTime( ReportDisplayTime ) - self:F2() - - self._ReportDisplayTime = ReportDisplayTime -end - ---- Get the reporting message display time. --- @param #FAC_BASE self --- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. -function FAC_BASE:GetReportDisplayTime() - self:F2() - - return self._ReportDisplayTime -end - ---- Reports the detected items to the @{Set#SET_CLIENT}. --- @param #FAC_BASE self --- @param Set#SET_BASE DetectedSets The detected Sets created by the @{Detection#DETECTION_BASE} object. --- @return #FAC_BASE self -function FAC_BASE:ReportDetected( DetectedSets ) - self:F2() +do -- DETECTION_REPORTING + --- DETECTION_REPORTING class. + -- @type DETECTION_REPORTING + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @extends #DETECTION_MANAGER + DETECTION_REPORTING = { + ClassName = "DETECTION_REPORTING", + } - -end - ---- Schedule the FAC reporting. --- @param #FAC_BASE self --- @param #number DelayTime The delay in seconds to wait the reporting. --- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. --- @return #FAC_BASE self -function FAC_BASE:Schedule( DelayTime, ReportInterval ) - self:F2() - - self._ScheduleDelayTime = DelayTime - self:SetReportInterval( ReportInterval ) + --- DETECTION_REPORTING constructor. + -- @param #DETECTION_REPORTING self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_AREAS Detection + -- @return #DETECTION_REPORTING self + function DETECTION_REPORTING:New( SetGroup, Detection ) - self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "Fac" }, self._ScheduleDelayTime, self._ReportInterval ) - return self -end - ---- Report the detected @{Unit#UNIT}s detected within the @{DetectION#DETECTION_BASE} object to the @{Set#SET_CLIENT}s. --- @param #FAC_BASE self -function FAC_BASE:_FacScheduler( SchedulerName ) - self:F2( { SchedulerName } ) + -- Inherits from DETECTION_MANAGER + local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING + + self:Schedule( 1, 30 ) + return self + end - self.ClientSet:ForEachClient( - --- @param Client#CLIENT Client - function( Client ) - if Client:IsAlive() then - local DetectedSets = self.Detection:GetDetectedSets() - return self:ReportDetected( Client, DetectedSets ) - end - end - ) + --- Creates a string of the detected items in a @{Detection}. + -- @param #DETECTION_MANAGER self + -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. + -- @return #DETECTION_MANAGER self + function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) + self:F2() - return true -end - --- FAC_REPORTING - ---- FAC_REPORTING class. --- @type FAC_REPORTING --- @field Set#SET_CLIENT ClientSet The clients to which the FAC will report to. --- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. --- @extends #FAC_BASE -FAC_REPORTING = { - ClassName = "FAC_REPORTING", -} - - ---- FAC_REPORTING constructor. --- @param #FAC_REPORTING self --- @param Set#SET_CLIENT ClientSet --- @param Detection#DETECTION_BASE Detection --- @return #FAC_REPORTING self -function FAC_REPORTING:New( ClientSet, Detection ) - - -- Inherits from FAC_BASE - local self = BASE:Inherit( self, FAC_BASE:New( ClientSet, Detection ) ) -- #FAC_REPORTING - - self:Schedule( 5, 60 ) - return self -end - - ---- Reports the detected items to the @{Set#SET_CLIENT}. --- @param #FAC_REPORTING self --- @param Client#CLIENT Client The @{Client} object to where the report needs to go. --- @param Set#SET_BASE DetectedSets The detected Sets created by the @{Detection#DETECTION_BASE} object. --- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. -function FAC_REPORTING:ReportDetected( Client, DetectedSets ) - self:F2( Client ) - - local DetectedMsg = {} - for DetectedUnitSetID, DetectedUnitSet in pairs( DetectedSets ) do - local UnitSet = DetectedUnitSet -- Set#SET_UNIT local MT = {} -- Message Text local UnitTypes = {} - for DetectedUnitID, DetectedUnitData in pairs( UnitSet:GetSet() ) do + + for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do local DetectedUnit = DetectedUnitData -- Unit#UNIT - local UnitType = DetectedUnit:GetTypeName() - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + if DetectedUnit:IsAlive() then + local UnitType = DetectedUnit:GetTypeName() + + if not UnitTypes[UnitType] then + UnitTypes[UnitType] = 1 + else + UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + end end end + for UnitTypeID, UnitType in pairs( UnitTypes ) do MT[#MT+1] = UnitType .. " of " .. UnitTypeID end - local MessageText = table.concat( MT, ", " ) - DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedUnitSetID .. ": " .. MessageText + + return table.concat( MT, ", " ) + end + + + + --- Reports the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_REPORTING self + -- @param Group#GROUP Group The @{Group} object to where the report needs to go. + -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. + -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. + function DETECTION_REPORTING:ProcessDetected( Group, Detection ) + self:F2( Group ) + + self:E( Group ) + local DetectedMsg = {} + for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do + local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) + end + local FACGroup = Detection:GetDetectionGroups() + FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group ) + + return true + end + +end + +do -- DETECTION_DISPATCHER + + --- DETECTION_DISPATCHER class. + -- @type DETECTION_DISPATCHER + -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. + -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Mission#MISSION Mission + -- @field Group#GROUP CommandCenter + -- @extends DetectionManager#DETECTION_MANAGER + DETECTION_DISPATCHER = { + ClassName = "DETECTION_DISPATCHER", + Mission = nil, + CommandCenter = nil, + Detection = nil, + } + + + --- DETECTION_DISPATCHER constructor. + -- @param #DETECTION_DISPATCHER self + -- @param Set#SET_GROUP SetGroup + -- @param Detection#DETECTION_BASE Detection + -- @return #DETECTION_DISPATCHER self + function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) + + -- Inherits from DETECTION_MANAGER + local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_DISPATCHER + + self.Detection = Detection + self.CommandCenter = CommandCenter + self.Mission = Mission + + self:Schedule( 30 ) + return self + end + + + --- Creates a SEAD task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Set#SET_UNIT TargetSetUnit: The target set of units. + -- @return #nil If there are no targets to be set. + function DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local RadarCount = DetectedSet:HasSEAD() + + if RadarCount > 0 then + + -- Here we're doing something advanced... We're copying the DetectedSet, but making a new Set only with SEADable Radar units in it. + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterHasSEAD() + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Creates a CAS task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local GroundUnitCount = DetectedSet:HasGroundUnits() + local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) + + if GroundUnitCount > 0 and FriendliesNearBy == true then + + -- Copy the Set + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Creates a BAI task when there are targets for it. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local GroundUnitCount = DetectedSet:HasGroundUnits() + local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) + + if GroundUnitCount > 0 and FriendliesNearBy == false then + + -- Copy the Set + local TargetSetUnit = SET_UNIT:New() + TargetSetUnit:SetDatabase( DetectedSet ) + TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. + + return TargetSetUnit + end + + return nil + end + + --- Evaluates the removal of the Task from the Mission. + -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". + -- @param #DETECTION_DISPATCHER self + -- @param Mission#MISSION Mission + -- @param Task#TASK_BASE Task + -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Task#TASK_BASE + function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) + + if Task then + if Task:IsStatePlanned() and DetectedArea.Changed == true then + Mission:RemoveTaskMenu( Task ) + Task = Mission:RemoveTask( Task ) + end + end + + return Task + end + + + --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. + -- @param #DETECTION_DISPATCHER self + -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. + -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. + function DETECTION_DISPATCHER:ProcessDetected( Detection ) + self:F2() + + local AreaMsg = {} + local TaskMsg = {} + local ChangeMsg = {} + + local Mission = self.Mission + + --- First we need to the detected targets. + for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do + + local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) + DetectedSet:Flush() + + local AreaID = DetectedArea.AreaID + + -- Evaluate SEAD Tasking + local SEADTask = Mission:GetTask( "SEAD." .. AreaID ) + SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedArea ) + if not SEADTask then + local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if SEADTask and SEADTask:IsStatePlanned() then + SEADTask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() + end + + -- Evaluate CAS Tasking + local CASTask = Mission:GetTask( "CAS." .. AreaID ) + CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedArea ) + if not CASTask then + local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + CASTask = Mission:AddTask( TASK_CAS:New( Mission, self.SetGroup, "CAS." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if CASTask and CASTask:IsStatePlanned() then + CASTask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() + end + + -- Evaluate BAI Tasking + local BAITask = Mission:GetTask( "BAI." .. AreaID ) + BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedArea ) + if not BAITask then + local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... + if TargetSetUnit then + BAITask = Mission:AddTask( TASK_BAI:New( Mission, self.SetGroup, "BAI." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + end + end + if BAITask and BAITask:IsStatePlanned() then + BAITask:SetPlannedMenu() + TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() + end + + if #TaskMsg > 0 then + + local ThreatLevel = Detection:GetTreatLevelA2G( DetectedArea ) + + local DetectedAreaVec3 = DetectedZone:GetPointVec3() + local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) + local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) + AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", + DetectedAreaID, + DetectedAreaPointLL, + string.rep( "â– ", ThreatLevel ), + ThreatLevel + ) + + -- Loop through the changes ... + local ChangeText = Detection:GetChangeText( DetectedArea ) + + if ChangeText ~= "" then + ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) + end + end + + -- OK, so the tasking has been done, now delete the changes reported for the area. + Detection:AcceptChanges( DetectedArea ) + + end + + if #AreaMsg > 0 then + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not TaskGroup:GetState( TaskGroup, "Assigned" ) then + self.CommandCenter:MessageToGroup( + string.format( "HQ Reporting - Target areas for mission '%s':\nAreas:\n%s\n\nTasks:\n%s\n\nChanges:\n%s ", + self.Mission:GetName(), + table.concat( AreaMsg, "\n" ), + table.concat( TaskMsg, "\n" ), + table.concat( ChangeMsg, "\n" ) + ), self:GetReportDisplayTime(), TaskGroup + ) + end + end + end + + return true + end + +end--- This module contains the STATEMACHINE class. +-- This development is based on a state machine implementation made by Conroy Kyle. +-- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine +-- +-- I've taken the development and enhanced it to make the state machine hierarchical... +-- It is a fantastic development, this module. +-- +-- === +-- +-- 1) @{Workflow#STATEMACHINE} class, extends @{Base#BASE} +-- ============================================== +-- +-- 1.1) Add or remove objects from the STATEMACHINE +-- -------------------------------------------- +-- @module StateMachine +-- @author FlightControl + + +--- STATEMACHINE class +-- @type STATEMACHINE +STATEMACHINE = { + ClassName = "STATEMACHINE", +} + +--- Creates a new STATEMACHINE object. +-- @param #STATEMACHINE self +-- @return #STATEMACHINE +function STATEMACHINE:New( options ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) + + + --local self = routines.utils.deepCopy( self ) -- Create a new self instance + + assert(options.events) + + --local MT = {} + --setmetatable( self, MT ) + --self.__index = self + + self.options = options + self.current = options.initial or 'none' + self.events = {} + self.subs = {} + self.endstates = {} + + for _, event in ipairs(options.events or {}) do + local name = event.name + self[name] = self[name] or self:_create_transition(name) + self.events[name] = self.events[name] or { map = {} } + self:_add_to_map(self.events[name].map, event) + end + + for name, callback in pairs(options.callbacks or {}) do + self[name] = callback + end + + for name, sub in pairs( options.subs or {} ) do + self:_submap( self.subs, sub, name ) + end + + for name, endstate in pairs( options.endstates or {} ) do + self.endstates[endstate] = endstate + end + + return self +end + + +function STATEMACHINE:_submap( subs, sub, name ) + self:E( { sub = sub, name = name } ) + subs[sub.onstateparent] = subs[sub.onstateparent] or {} + subs[sub.onstateparent][sub.oneventparent] = subs[sub.onstateparent][sub.oneventparent] or {} + local Index = #subs[sub.onstateparent][sub.oneventparent] + 1 + subs[sub.onstateparent][sub.oneventparent][Index] = {} + subs[sub.onstateparent][sub.oneventparent][Index].fsm = sub.fsm + subs[sub.onstateparent][sub.oneventparent][Index].event = sub.event + subs[sub.onstateparent][sub.oneventparent][Index].returnevents = sub.returnevents -- these events need to be given to find the correct continue event ... if none given, the processing will stop. + subs[sub.onstateparent][sub.oneventparent][Index].name = name + subs[sub.onstateparent][sub.oneventparent][Index].fsmparent = self +end + + +function STATEMACHINE:_call_handler(handler, params) + if handler then + return handler(unpack(params)) + end +end + +function STATEMACHINE:_create_transition(name) + self:E( { name = name } ) + return function(self, ...) + local can, to = self:can(name) + self:T( { name, can, to } ) + + if can then + local from = self.current + local params = { self, name, from, to, ... } + + if self:_call_handler(self["onbefore" .. name], params) == false + or self:_call_handler(self["onleave" .. from], params) == false then + return false + end + + self.current = to + + local execute = true + + local subtable = self:_gosub( to, name ) + for _, sub in pairs( subtable ) do + self:E( "calling sub: " .. sub.event ) + sub.fsm.fsmparent = self + sub.fsm.returnevents = sub.returnevents + sub.fsm[sub.event]( sub.fsm ) + execute = true + end + + local fsmparent, event = self:_isendstate( to ) + if fsmparent and event then + self:E( { "end state: ", fsmparent, event } ) + self:_call_handler(self["onenter" .. to] or self["on" .. to], params) + self:_call_handler(self["onafter" .. name] or self["on" .. name], params) + self:_call_handler(self["onstatechange"], params) + fsmparent[event]( fsmparent ) + execute = false + end + + if execute then + self:E( { "execute: " .. to, name } ) + self:_call_handler(self["onenter" .. to] or self["on" .. to], params) + self:_call_handler(self["onafter" .. name] or self["on" .. name], params) + self:_call_handler(self["onstatechange"], params) + end + + return true + end + + return false + end +end + +function STATEMACHINE:_gosub( parentstate, parentevent ) + local fsmtable = {} + if self.subs[parentstate] and self.subs[parentstate][parentevent] then + return self.subs[parentstate][parentevent] + else + return {} + end +end + +function STATEMACHINE:_isendstate( state ) + local fsmparent = self.fsmparent + if fsmparent and self.endstates[state] then + self:E( { state = state, endstates = self.endstates, endstate = self.endstates[state] } ) + local returnevent = nil + local fromstate = fsmparent.current + self:E( fromstate ) + self:E( self.returnevents ) + for _, eventname in pairs( self.returnevents ) do + local event = fsmparent.events[eventname] + self:E( event ) + local to = event and event.map[fromstate] or event.map['*'] + if to and to == state then + return fsmparent, eventname + else + self:E( { "could not find parent event name for state", fromstate, to } ) + end + end + end + + return nil +end + +function STATEMACHINE:_add_to_map(map, event) + if type(event.from) == 'string' then + map[event.from] = event.to + else + for _, from in ipairs(event.from) do + map[from] = event.to + end + end +end + +function STATEMACHINE:is(state) + return self.current == state +end + +function STATEMACHINE:can(e) + local event = self.events[e] + local to = event and event.map[self.current] or event.map['*'] + return to ~= nil, to +end + +function STATEMACHINE:cannot(e) + return not self:can(e) +end + +function STATEMACHINE:todot(filename) + local dotfile = io.open(filename,'w') + dotfile:write('digraph {\n') + local transition = function(event,from,to) + dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) + end + for _, event in pairs(self.options.events) do + if type(event.from) == 'table' then + for _, from in ipairs(event.from) do + transition(event.name,from,event.to) + end + else + transition(event.name,event.from,event.to) + end + end + dotfile:write('}\n') + dotfile:close() +end + +--- STATEMACHINE_PROCESS class +-- @type STATEMACHINE_PROCESS +-- @field Process#PROCESS Process +-- @extends StateMachine#STATEMACHINE +STATEMACHINE_PROCESS = { + ClassName = "STATEMACHINE_PROCESS", +} + +--- Creates a new STATEMACHINE_PROCESS object. +-- @param #STATEMACHINE_PROCESS self +-- @return #STATEMACHINE_PROCESS +function STATEMACHINE_PROCESS:New( Process, options ) + + local FsmProcess = routines.utils.deepCopy( self ) -- Create a new self instance + local Parent = STATEMACHINE:New(options) + + setmetatable( FsmProcess, Parent ) + FsmProcess.__index = FsmProcess + + FsmProcess["onstatechange"] = Process.OnStateChange + FsmProcess.Process = Process + + return FsmProcess +end + +function STATEMACHINE_PROCESS:_call_handler( handler, params ) + if handler then + return handler( self.Process, unpack( params ) ) + end +end + +--- STATEMACHINE_TASK class +-- @type STATEMACHINE_TASK +-- @field Task#TASK_BASE Task +-- @extends StateMachine#STATEMACHINE +STATEMACHINE_TASK = { + ClassName = "STATEMACHINE_TASK", +} + +--- Creates a new STATEMACHINE_TASK object. +-- @param #STATEMACHINE_TASK self +-- @return #STATEMACHINE_TASK +function STATEMACHINE_TASK:New( Task, TaskUnit, options ) + + local FsmTask = routines.utils.deepCopy( self ) -- Create a new self instance + local Parent = STATEMACHINE:New(options) + + setmetatable( FsmTask, Parent ) + FsmTask.__index = FsmTask + + FsmTask["onstatechange"] = Task.OnStateChange + FsmTask["onAssigned"] = Task.OnAssigned + FsmTask["onSuccess"] = Task.OnSuccess + FsmTask["onFailed"] = Task.OnFailed + + FsmTask.Task = Task + FsmTask.TaskUnit = TaskUnit + + return FsmTask +end + +function STATEMACHINE_TASK:_call_handler( handler, params ) + if handler then + return handler( self.Task, self.TaskUnit, unpack( params ) ) + end +end +--- @module Process + +--- The PROCESS class +-- @type PROCESS +-- @field Scheduler#SCHEDULER ProcessScheduler +-- @field Unit#UNIT ProcessUnit +-- @field Task#TASK Task +-- @field StateMachine#STATEMACHINE_TASK Fsm +-- @field #string ProcessName +-- @extends Base#BASE +PROCESS = { + ClassName = "TASK", + ProcessScheduler = nil, + NextEvent = nil, + Scores = {}, +} + +--- Instantiates a new TASK Base. Should never be used. Interface Class. +-- @param #PROCESS self +-- @param #string ProcessName +-- @param Task#TASK_BASE Task +-- @param Unit#UNIT ProcessUnit +-- @return #PROCESS self +function PROCESS:New( ProcessName, Task, ProcessUnit ) + local self = BASE:Inherit( self, BASE:New() ) + self:F() + + self.ProcessUnit = ProcessUnit + self.Task = Task + self.ProcessName = ProcessName + + self.AllowEvents = true + + return self +end + +--- @param #PROCESS self +function PROCESS:NextEvent( NextEvent, ... ) + self:F2( arg ) + if self.AllowEvents == true then + self.ProcessScheduler = SCHEDULER:New( self.Fsm, NextEvent, { self, self.ProcessUnit, unpack( arg ) }, 1 ) + end +end + +--- @param #PROCESS self +function PROCESS:StopEvents( ) + self:F2() + if self.ProcessScheduler then + self:E( "Stop" ) + self.ProcessScheduler:Stop() + self.ProcessScheduler = nil + self.AllowEvents = false + end +end + +--- Adds a score for the PROCESS to be achieved. +-- @param #PROCESS self +-- @param #string ProcessStatus is the status of the PROCESS when the score needs to be given. +-- @param #string ScoreText is a text describing the score that is given according the status. +-- @param #number Score is a number providing the score of the status. +-- @return #PROCESS self +function PROCESS:AddScore( ProcessStatus, ScoreText, Score ) + self:F2( { ProcessStatus, ScoreText, Score } ) + + self.Scores[ProcessStatus] = self.Scores[ProcessStatus] or {} + self.Scores[ProcessStatus].ScoreText = ScoreText + self.Scores[ProcessStatus].Score = Score + return self +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS:OnStateChange( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName } ) + + if self:IsTrace() then + MESSAGE:New( "Process " .. self.ProcessName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() + end + + if self.Scores[To] then + + local Scoring = self.Task:GetScoring() + if Scoring then + Scoring:_AddMissionTaskScore( self.Task.Mission, self.ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end +end + + +--- This module contains the TASK_ASSIGN classes. +-- +-- === +-- +-- 1) @{Task_Assign#TASK_ASSIGN_ACCEPT} class, extends @{Task#TASK_BASE} +-- ===================================================================== +-- The @{Task_Assign#TASK_ASSIGN_ACCEPT} class accepts by default a task for a player. No player intervention is allowed to reject the task. +-- +-- 2) @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class, extends @{Task#TASK_BASE} +-- ========================================================================== +-- The @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class accepts a task when the player accepts the task through an added menu option. +-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. +-- The assignment type also allows to reject the task. +-- +-- +-- +-- +-- +-- +-- @module Task_Assign +-- + + +do -- PROCESS_ASSIGN_ACCEPT + + --- PROCESS_ASSIGN_ACCEPT class + -- @type PROCESS_ASSIGN_ACCEPT + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_ASSIGN_ACCEPT = { + ClassName = "PROCESS_ASSIGN_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #PROCESS_ASSIGN_ACCEPT self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_ASSIGN_ACCEPT self + function PROCESS_ASSIGN_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_ACCEPT + + self.TaskBriefing = TaskBriefing + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnAssigned', + events = { + { name = 'Start', from = 'UnAssigned', to = 'Assigned' }, + { name = 'Fail', from = 'UnAssigned', to = 'Failed' }, + }, + callbacks = { + onAssign = self.OnAssign, + }, + endstates = { + 'Assigned', 'Failed' + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_ACCEPT:OnAssigned( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + end + +end + + +do -- PROCESS_ASSIGN_MENU_ACCEPT + + --- PROCESS_ASSIGN_MENU_ACCEPT class + -- @type PROCESS_ASSIGN_MENU_ACCEPT + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_ASSIGN_MENU_ACCEPT = { + ClassName = "PROCESS_ASSIGN_MENU_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_MENU_ACCEPT + + self.TaskBriefing = TaskBriefing + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnAssigned', + events = { + { name = 'Start', from = 'UnAssigned', to = 'AwaitAccept' }, + { name = 'Assign', from = 'AwaitAccept', to = 'Assigned' }, + { name = 'Reject', from = 'AwaitAccept', to = 'Rejected' }, + { name = 'Fail', from = 'AwaitAccept', to = 'Rejected' }, + }, + callbacks = { + onStart = self.OnStart, + onAssign = self.OnAssign, + onReject = self.OnReject, + }, + endstates = { + 'Assigned', 'Rejected' + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnStart( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + MESSAGE:New( self.TaskBriefing .. "\nAccess the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", 30, "Assignment" ):ToGroup( self.ProcessUnit:GetGroup() ) + self.MenuText = self.Task.TaskName + + local ProcessGroup = self.ProcessUnit:GetGroup() + self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.MenuText .. " acceptance" ) + self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.MenuText, self.Menu, self.MenuAssign, self ) + self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.MenuText, self.Menu, self.MenuReject, self ) + end + + --- Menu function. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() + self:E( ) + + self:NextEvent( self.Fsm.Assign ) + end + + --- Menu function. + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + function PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() + self:E( ) + + self:NextEvent( self.Fsm.Reject ) + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnAssign( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.Menu:Remove() + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_ASSIGN_MENU_ACCEPT self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_ASSIGN_MENU_ACCEPT:OnReject( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.Menu:Remove() + self.Task:UnAssignFromUnit( self.ProcessUnit ) + self.ProcessUnit:Destroy() + end +end +--- @module Task_Route + +--- PROCESS_ROUTE class +-- @type PROCESS_ROUTE +-- @field Task#TASK TASK +-- @field Unit#UNIT ProcessUnit +-- @field Zone#ZONE_BASE TargetZone +-- @extends Task2#TASK2 +PROCESS_ROUTE = { + ClassName = "PROCESS_ROUTE", +} + + +--- Creates a new routing state machine. The task will route a CLIENT to a ZONE until the CLIENT is within that ZONE. +-- @param #PROCESS_ROUTE self +-- @param Task#TASK Task +-- @param Unit#UNIT Unit +-- @return #PROCESS_ROUTE self +function PROCESS_ROUTE:New( Task, ProcessUnit, TargetZone ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ROUTE", Task, ProcessUnit ) ) -- #PROCESS_ROUTE + + self.TargetZone = TargetZone + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Route is the default display category + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'UnArrived', + events = { + { name = 'Start', from = 'UnArrived', to = 'UnArrived' }, + { name = 'Fail', from = 'UnArrived', to = 'Failed' }, + }, + callbacks = { + onleaveUnArrived = self.OnLeaveUnArrived, + onFail = self.OnFail, + }, + endstates = { + 'Arrived', 'Failed' + }, + } ) + + return self +end + +--- Task Events + +--- StateMachine callback function for a TASK2 +-- @param #PROCESS_ROUTE self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_ROUTE:OnLeaveUnArrived( Fsm, Event, From, To ) + + if self.ProcessUnit:IsAlive() then + local IsInZone = self.ProcessUnit:IsInZone( self.TargetZone ) + + if self.DisplayCount >= self.DisplayInterval then + if not IsInZone then + local ZoneVec2 = self.TargetZone:GetVec2() + local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) + local TaskUnitVec2 = self.ProcessUnit:GetVec2() + local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) + local RouteText = self.ProcessUnit:GetCallSign() .. ": Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." + MESSAGE:New( RouteText, self.DisplayTime, self.DisplayCategory ):ToGroup( self.ProcessUnit:GetGroup() ) + end + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + --if not IsInZone then + self:NextEvent( Fsm.Start ) + --end + + return IsInZone -- if false, then the event will not be executed... + end + + return false + +end + +--- @module Process_Smoke + +do -- PROCESS_SMOKE_TARGETS + + --- PROCESS_SMOKE_TARGETS class + -- @type PROCESS_SMOKE_TARGETS + -- @field Task#TASK_BASE Task + -- @field Unit#UNIT ProcessUnit + -- @field Set#SET_UNIT TargetSetUnit + -- @field Zone#ZONE_BASE TargetZone + -- @extends Task2#TASK2 + PROCESS_SMOKE_TARGETS = { + ClassName = "PROCESS_SMOKE_TARGETS", + } + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #PROCESS_SMOKE_TARGETS self + -- @param Task#TASK Task + -- @param Unit#UNIT Unit + -- @return #PROCESS_SMOKE_TARGETS self + function PROCESS_SMOKE_TARGETS:New( Task, ProcessUnit, TargetSetUnit, TargetZone ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_SMOKE_TARGETS + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'None', + events = { + { name = 'Start', from = 'None', to = 'AwaitSmoke' }, + { name = 'Next', from = 'AwaitSmoke', to = 'Smoking' }, + { name = 'Next', from = 'Smoking', to = 'AwaitSmoke' }, + { name = 'Fail', from = 'Smoking', to = 'Failed' }, + { name = 'Fail', from = 'AwaitSmoke', to = 'Failed' }, + { name = 'Fail', from = 'None', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onNext = self.OnNext, + onSmoking = self.OnSmoking, + }, + endstates = { + }, + } ) + + return self + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_SMOKE_TARGETS self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_SMOKE_TARGETS:OnStart( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self:E("Set smoke menu") + + local ProcessGroup = self.ProcessUnit:GetGroup() + local MissionMenu = self.Task.Mission:GetMissionMenu( ProcessGroup ) + + local function MenuSmoke( MenuParam ) + self:E( MenuParam ) + local self = MenuParam.self + local SmokeColor = MenuParam.SmokeColor + self.SmokeColor = SmokeColor + self:NextEvent( self.Fsm.Next ) + end + + self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) + self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) + self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) + self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) + self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) + self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) + end + + --- StateMachine callback function for a TASK2 + -- @param #PROCESS_SMOKE_TARGETS self + -- @param StateMachine#STATEMACHINE_PROCESS Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + function PROCESS_SMOKE_TARGETS:OnSmoking( Fsm, Event, From, To ) + self:E( { Event, From, To, self.ProcessUnit.UnitName} ) + + self.TargetSetUnit:ForEachUnit( + --- @param Unit#UNIT SmokeUnit + function( SmokeUnit ) + if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then + SCHEDULER:New( self, + function() + SmokeUnit:Smoke( self.SmokeColor, 150 ) + end, {}, math.random( 10, 60 ) + ) + end + end + ) + + end + +end--- @module Process_Destroy + +--- PROCESS_DESTROY class +-- @type PROCESS_DESTROY +-- @field Unit#UNIT ProcessUnit +-- @field Set#SET_UNIT TargetSetUnit +-- @extends Process#PROCESS +PROCESS_DESTROY = { + ClassName = "PROCESS_DESTROY", + Fsm = {}, + TargetSetUnit = nil, +} + + +--- Creates a new DESTROY process. +-- @param #PROCESS_DESTROY self +-- @param Task#TASK Task +-- @param Unit#UNIT ProcessUnit +-- @param Set#SET_UNIT TargetSetUnit +-- @return #PROCESS_DESTROY self +function PROCESS_DESTROY:New( Task, ProcessName, ProcessUnit, TargetSetUnit ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_DESTROY + + self.TargetSetUnit = TargetSetUnit + + self.DisplayInterval = 60 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Targets is the default display category + + self.Fsm = STATEMACHINE_PROCESS:New( self, { + initial = 'Assigned', + events = { + { name = 'Start', from = 'Assigned', to = 'Waiting' }, + { name = 'Start', from = 'Waiting', to = 'Waiting' }, + { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, + { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, + { name = 'Destroyed', from = 'Destroy', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Waiting', to = 'Failed' }, + { name = 'Fail', from = 'Destroy', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onWaiting = self.OnWaiting, + onHitTarget = self.OnHitTarget, + onMoreTargets = self.OnMoreTargets, + onDestroyed = self.OnDestroyed, + onKilled = self.OnKilled, + }, + endstates = { 'Success', 'Failed' } + } ) + + + _EVENTDISPATCHER:OnDead( self.EventDead, self ) + + return self +end + +--- Process Events + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnStart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Start ) +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnWaiting( Fsm, Event, From, To ) + + local TaskGroup = self.ProcessUnit:GetGroup() + if self.DisplayCount >= self.DisplayInterval then + MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup ) + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + return true -- Process always the event. + +end + + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function PROCESS_DESTROY:OnHitTarget( Fsm, Event, From, To, Event ) + + self.TargetSetUnit:Flush() + + if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then + self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName ) + end + + local TaskGroup = self.ProcessUnit:GetGroup() + MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup ) + + if self.TargetSetUnit:Count() > 0 then + self:NextEvent( Fsm.MoreTargets ) + else + self:NextEvent( Fsm.Destroyed ) + end +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnMoreTargets( Fsm, Event, From, To ) + + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA DCSEvent +function PROCESS_DESTROY:OnKilled( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Restart ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnRestart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Menu ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_DESTROY self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_DESTROY:OnDestroyed( Fsm, Event, From, To ) + +end + +--- DCS Events + +--- @param #PROCESS_DESTROY self +-- @param Event#EVENTDATA Event +function PROCESS_DESTROY:EventDead( Event ) + + if Event.IniDCSUnit then + self.TargetSetUnit:Remove( Event.IniDCSUnitName ) + self:NextEvent( self.Fsm.HitTarget, Event ) + end +end + + +--- This module contains the TASK_BASE class. +-- +-- 1) @{#TASK_BASE} class, extends @{Base#BASE} +-- ============================================ +-- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. +-- ---------------------------------------------------------------------------------------- +-- The class provides a couple of methods to: +-- +-- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). +-- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. +-- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. +-- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. +-- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. +-- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} +-- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. +-- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. +-- +-- 1.2) Set and enquire task status (beyond the task state machine processing). +-- ---------------------------------------------------------------------------- +-- A task needs to implement as a minimum the following task states: +-- +-- * **Success**: Expresses the successful execution and finalization of the task. +-- * **Failed**: Expresses the failure of a task. +-- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. +-- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. +-- +-- A task may also implement the following task states: +-- +-- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. +-- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. +-- +-- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. +-- +-- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. +-- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. +-- +-- 1.3) Add scoring when reaching a certain task status: +-- ----------------------------------------------------- +-- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +-- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. +-- +-- 1.4) Task briefing: +-- ------------------- +-- A task briefing can be given that is shown to the player when he is assigned to the task. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task + +--- The TASK_BASE class +-- @type TASK_BASE +-- @field Scheduler#SCHEDULER TaskScheduler +-- @field Mission#MISSION Mission +-- @field StateMachine#STATEMACHINE Fsm +-- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task +-- @extends Base#BASE +TASK_BASE = { + ClassName = "TASK_BASE", + TaskScheduler = nil, + Processes = {}, + Players = nil, + Scores = {}, + Menu = {}, + SetGroup = nil, +} + + +--- Instantiates a new TASK_BASE. Should never be used. Interface Class. +-- @param #TASK_BASE self +-- @param Mission#MISSION The mission wherein the Task is registered. +-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. +-- @param #string TaskName The name of the Task +-- @param #string TaskType The type of the Task +-- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) +-- @return #TASK_BASE self +function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) + + local self = BASE:Inherit( self, BASE:New() ) + self:E( "New TASK " .. TaskName ) + + self.Processes = {} + self.Fsm = {} + + self.Mission = Mission + self.SetGroup = SetGroup + + self:SetCategory( TaskCategory ) + self:SetType( TaskType ) + self:SetName( TaskName ) + self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. + + self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." + + return self +end + +--- Cleans all references of a TASK_BASE. +-- @param #TASK_BASE self +-- @return #nil +function TASK_BASE:CleanUp() + + _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) + _EVENTDISPATCHER:OnDeadRemove( self ) + _EVENTDISPATCHER:OnCrashRemove( self ) + _EVENTDISPATCHER:OnPilotDeadRemove( self ) + + return nil +end + + +--- Assign the @{Task}to a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +function TASK_BASE:AssignToGroup( TaskGroup ) + self:F2( TaskGroup:GetName() ) + + local TaskGroupName = TaskGroup:GetName() + + TaskGroup:SetState( TaskGroup, "Assigned", self ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:AssignToUnit( TaskUnit ) + end + end +end + +--- Send the briefng message of the @{Task} to the assigned @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:SendBriefingToAssignedGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + if self:IsAssignedToGroup( TaskGroup ) then + TaskGroup:Message( self.TaskBriefing, 60 ) + end + end +end + + +--- Assign the @{Task} from the @{Group}s. +-- @param #TASK_BASE self +function TASK_BASE:UnAssignFromGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:UnAssignFromUnit( TaskUnit ) + end + end + end +end + +--- Returns if the @{Task} is assigned to the Group. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #boolean +function TASK_BASE:IsAssignedToGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + if self:IsStateAssigned() then + if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then + return true + end + end + + return false +end + +--- Assign the @{Task}to an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + return nil +end + +--- UnAssign the @{Task} from an alive @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:UnAssignFromUnit( TaskUnitName ) + self:F( TaskUnitName ) + + if self:HasStateMachine( TaskUnitName ) == true then + self:RemoveStateMachines( TaskUnitName ) + self:RemoveProcesses( TaskUnitName ) + end + + return self +end + +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenu() + + local MenuText = self:GetPlannedMenuText() + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not self:IsAssignedToGroup( TaskGroup ) then + self:SetPlannedMenuForGroup( TaskGroup, MenuText ) + end end - local FACGroup = self.Detection:GetFACGroup() - FACGroup:MessageToClient( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Client ) +end + +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if self:IsAssignedToGroup( TaskGroup ) then + self:SetAssignedMenuForGroup( TaskGroup ) + end + end +end + +--- Remove the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK_BASE self +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + end +end + +--- Set the planned menu option of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @param #string MenuText The menu text. +-- @return #TASK_BASE self +function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) + self:E( TaskGroup:GetName() ) + + local TaskMission = self.Mission:GetName() + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + Mission.MenuCategory = Mission.MenuCategory or {} + local MenuCategory = Mission.MenuCategory + + Mission.MenuType = Mission.MenuType or {} + local MenuType = Mission.MenuType + + self.Menu = self.Menu or {} + local Menu = self.Menu + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + + MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} + MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) + + MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} + MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) + + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + end + Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Set the assigned menu options of the @{Task}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) + self:E( TaskGroup:GetName() ) + + local TaskMission = self.Mission:GetName() + + local Mission = self.Mission + + Mission.MenuMission = Mission.MenuMission or {} + local MenuMission = Mission.MenuMission + + self.MenuStatus = self.MenuStatus or {} + local MenuStatus = self.MenuStatus + + + self.MenuAbort = self.MenuAbort or {} + local MenuAbort = self.MenuAbort + + local TaskGroupName = TaskGroup:GetName() + MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) + MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) + MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Remove the menu option of the @{Task} for a @{Group}. +-- @param #TASK_BASE self +-- @param Group#GROUP TaskGroup +-- @return #TASK_BASE self +function TASK_BASE:RemoveMenuForGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + local Mission = self.Mission + local MenuMission = Mission.MenuMission + local MenuCategory = Mission.MenuCategory + local MenuType = Mission.MenuType + local MenuStatus = self.MenuStatus + local MenuAbort = self.MenuAbort + local Menu = self.Menu + + Menu = Menu or {} + if Menu[TaskGroupName] then + Menu[TaskGroupName]:Remove() + Menu[TaskGroupName] = nil + end + + MenuType = MenuType or {} + if MenuType[TaskGroupName] then + for _, Menu in pairs( MenuType[TaskGroupName] ) do + Menu:Remove() + end + MenuType[TaskGroupName] = nil + end + + MenuCategory = MenuCategory or {} + if MenuCategory[TaskGroupName] then + for _, Menu in pairs( MenuCategory[TaskGroupName] ) do + Menu:Remove() + end + MenuCategory[TaskGroupName] = nil + end + + MenuStatus = MenuStatus or {} + if MenuStatus[TaskGroupName] then + MenuStatus[TaskGroupName]:Remove() + MenuStatus[TaskGroupName] = nil + end + + MenuAbort = MenuAbort or {} + if MenuAbort[TaskGroupName] then + MenuAbort[TaskGroupName]:Remove() + MenuAbort[TaskGroupName] = nil + end + +end + +function TASK_BASE.MenuAssignToGroup( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + self:AssignToGroup( TaskGroup ) +end + +function TASK_BASE.MenuTaskStatus( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + +function TASK_BASE.MenuTaskAbort( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + + + +--- Returns the @{Task} name. +-- @param #TASK_BASE self +-- @return #string TaskName +function TASK_BASE:GetTaskName() + return self.TaskName +end + + +--- Add Process to @{Task} with key @{Unit}. +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddProcess( TaskUnit, Process ) + local TaskUnitName = TaskUnit:GetName() + self.Processes = self.Processes or {} + self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} + self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process + return Process +end + + +--- Remove Processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + Process:StopEvents() + Process = nil + self.Processes[TaskUnitName][ProcessID] = nil + self:E( self.Processes[TaskUnitName][ProcessID] ) + end + self.Processes[TaskUnitName] = nil +end + +--- Fail processes from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:FailProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData -- Process#PROCESS + self:E( { "Failing process: ", Process } ) + Process.Fsm:Fail() + end +end + +--- Add a FiniteStateMachine to @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @return #TASK_BASE self +function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) + local TaskUnitName = TaskUnit:GetName() + self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} + self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm + return Fsm +end + +--- Remove FiniteStateMachines from @{Task} with key @{Unit} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:RemoveStateMachines( TaskUnitName ) + + for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do + Fsm = nil + self.Fsm[TaskUnitName][_] = nil + self:E( self.Fsm[TaskUnitName][_] ) + end + self.Fsm[TaskUnitName] = nil +end + +--- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} +-- @param #TASK_BASE self +-- @param #string TaskUnitName +-- @return #TASK_BASE self +function TASK_BASE:HasStateMachine( TaskUnitName ) + + self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) + return ( self.Fsm[TaskUnitName] ~= nil ) +end + + + + + +--- Register a potential new assignment for a new spawned @{Unit}. +-- Tasks only get assigned if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventAssignUnit( Event ) + if Event.IniUnit then + self:F( Event ) + local TaskUnit = Event.IniUnit + if TaskUnit:IsAlive() then + local TaskPlayerName = TaskUnit:GetPlayerName() + if TaskPlayerName ~= nil then + if not self:HasStateMachine( TaskUnit ) then + -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. + local TaskGroup = TaskUnit:GetGroup() + if self:IsAssignedToGroup( TaskGroup ) then + self:AssignToUnit( TaskUnit ) + end + end + end + end + end + return nil +end + +--- Catches the "player leave unit" event for a @{Unit} .... +-- When a player is an air unit, and leaves the unit: +-- +-- * and he is not at an airbase runway on the ground, he will fail its task. +-- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. +-- This is important to model the change from plane types for a player during mission assignment. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventPlayerLeaveUnit( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + if TaskUnit:IsAir() then + if TaskUnit:IsAboveRunway() then + -- do nothing + else + self:E( "IsNotAboveRunway" ) + -- Player left airplane during an assigned task and was not at an airbase. + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + end + end + + end + return nil +end + +--- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... +-- There are only assignments if there are players in it. +-- @param #TASK_BASE self +-- @param Event#EVENTDATA Event +-- @return #TASK_BASE self +function TASK_BASE:_EventDead( Event ) + self:F( Event ) + if Event.IniUnit then + local TaskUnit = Event.IniUnit + local TaskUnitName = Event.IniUnitName + + -- Check if for this unit in the task there is a process ongoing. + if self:HasStateMachine( TaskUnitName ) then + self:FailProcesses( TaskUnitName ) + self:UnAssignFromUnit( TaskUnitName ) + end + + local TaskGroup = Event.IniUnit:GetGroup() + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + end + return nil +end + +--- Gets the Scoring of the task +-- @param #TASK_BASE self +-- @return Scoring#SCORING Scoring +function TASK_BASE:GetScoring() + return self.Mission:GetScoring() +end + + +--- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. +-- @param #TASK_BASE self +-- @return #string The Task ID +function TASK_BASE:GetTaskIndex() + + local TaskCategory = self:GetCategory() + local TaskType = self:GetType() + local TaskName = self:GetName() + + return TaskCategory .. "." ..TaskType .. "." .. TaskName +end + +--- Sets the Name of the Task +-- @param #TASK_BASE self +-- @param #string TaskName +function TASK_BASE:SetName( TaskName ) + self.TaskName = TaskName +end + +--- Gets the Name of the Task +-- @param #TASK_BASE self +-- @return #string The Task Name +function TASK_BASE:GetName() + return self.TaskName +end + +--- Sets the Type of the Task +-- @param #TASK_BASE self +-- @param #string TaskType +function TASK_BASE:SetType( TaskType ) + self.TaskType = TaskType +end + +--- Gets the Type of the Task +-- @param #TASK_BASE self +-- @return #string TaskType +function TASK_BASE:GetType() + return self.TaskType +end + +--- Sets the Category of the Task +-- @param #TASK_BASE self +-- @param #string TaskCategory +function TASK_BASE:SetCategory( TaskCategory ) + self.TaskCategory = TaskCategory +end + +--- Gets the Category of the Task +-- @param #TASK_BASE self +-- @return #string TaskCategory +function TASK_BASE:GetCategory() + return self.TaskCategory +end + +--- Sets the ID of the Task +-- @param #TASK_BASE self +-- @param #string TaskID +function TASK_BASE:SetID( TaskID ) + self.TaskID = TaskID +end + +--- Gets the ID of the Task +-- @param #TASK_BASE self +-- @return #string TaskID +function TASK_BASE:GetID() + return self.TaskID +end + + +--- Sets a @{Task} to status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:StateSuccess() + self:SetState( self, "State", "Success" ) + return self +end + +--- Is the @{Task} status **Success**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateSuccess() + return self:GetStateString() == "Success" +end + +--- Sets a @{Task} to status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:StateFailed() + self:SetState( self, "State", "Failed" ) + return self +end + +--- Is the @{Task} status **Failed**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateFailed() + return self:GetStateString() == "Failed" +end + +--- Sets a @{Task} to status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:StatePlanned() + self:SetState( self, "State", "Planned" ) + return self +end + +--- Is the @{Task} status **Planned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStatePlanned() + return self:GetStateString() == "Planned" +end + +--- Sets a @{Task} to status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:StateAssigned() + self:SetState( self, "State", "Assigned" ) + return self +end + +--- Is the @{Task} status **Assigned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateAssigned() + return self:GetStateString() == "Assigned" +end + +--- Sets a @{Task} to status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:StateHold() + self:SetState( self, "State", "Hold" ) + return self +end + +--- Is the @{Task} status **Hold**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateHold() + return self:GetStateString() == "Hold" +end + +--- Sets a @{Task} to status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:StateReplanned() + self:SetState( self, "State", "Replanned" ) + return self +end + +--- Is the @{Task} status **Replanned**. +-- @param #TASK_BASE self +function TASK_BASE:IsStateReplanned() + return self:GetStateString() == "Replanned" +end + +--- Gets the @{Task} status. +-- @param #TASK_BASE self +function TASK_BASE:GetStateString() + return self:GetState( self, "State" ) +end + +--- Sets a @{Task} briefing. +-- @param #TASK_BASE self +-- @param #string TaskBriefing +-- @return #TASK_BASE self +function TASK_BASE:SetBriefing( TaskBriefing ) + self.TaskBriefing = TaskBriefing + return self +end + + + +--- Adds a score for the TASK to be achieved. +-- @param #TASK_BASE self +-- @param #string TaskStatus is the status of the TASK when the score needs to be given. +-- @param #string ScoreText is a text describing the score that is given according the status. +-- @param #number Score is a number providing the score of the status. +-- @return #TASK_BASE self +function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) + self:F2( { TaskStatus, ScoreText, Score } ) + + self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} + self.Scores[TaskStatus].ScoreText = ScoreText + self.Scores[TaskStatus].Score = Score + return self +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) + + self:E("Assigned") + + local TaskGroup = TaskUnit:GetGroup() + + TaskGroup:Message( self.TaskBriefing, 20 ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + +end + + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) + + self:E("Success") + + self:UnAssignFromGroups() + + local TaskGroup = TaskUnit:GetGroup() + self.Mission:SetPlannedMenu() + + self:StateSuccess() + + -- The task has become successful, the event catchers can be cleaned. + self:CleanUp() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) + + self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) + + -- A task cannot be "failed", so a task will always be there waiting for players to join. + -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. + -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. + + self:UnAssignFromGroups() + self:StatePlanned() + +end + +--- StateMachine callback function for a TASK +-- @param #TASK_BASE self +-- @param Unit#UNIT TaskUnit +-- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) + + if self:IsTrace() then + MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() + end + + self:E( { Event, From, To } ) + self:SetState( self, "State", To ) + + if self.Scores[To] then + local Scoring = self:GetScoring() + if Scoring then + Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + +end + + +--- @param #TASK_BASE self +function TASK_BASE:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self +end + + +--- @param #TASK_BASE self +function TASK_BASE._Scheduler() + self:F2() return true end + + +--- This module contains the TASK_SEAD classes. +-- +-- 1) @{#TASK_SEAD} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_SEAD} class defines a new SEAD task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_SEAD is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_SEAD + + +do -- TASK_SEAD + + --- The TASK_SEAD class + -- @type TASK_SEAD + -- @field Set#SET_UNIT TargetSetUnit + -- @extends Task#TASK_BASE + TASK_SEAD = { + ClassName = "TASK_SEAD", + } + + --- Instantiates a new TASK_SEAD. + -- @param #TASK_SEAD self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_SEAD self + function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "SEAD", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_SEAD. + -- @param #TASK_SEAD self + -- @return #nil + function TASK_SEAD:CleanUp() + + self:GetParent(self):CleanUp() + + return nil + end + + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_SEAD self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_SEAD self + function TASK_SEAD:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "SEAD", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a radar", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a radar", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a radar", -100 ) + self:AddScore( "Success", "Destroyed all target radars", 250 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_SEAD self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_SEAD:OnNext( Fsm, Event, From, To ) + + self:SetState( self, "State", To ) + + end + + + --- @param #TASK_SEAD self + function TASK_SEAD:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + --- @param #TASK_SEAD self + function TASK_SEAD:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_SEAD self + function TASK_SEAD._Scheduler() + self:F2() + + return true + end + +end +--- This module contains the TASK_CAS classes. +-- +-- 1) @{#TASK_CAS} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_CAS} class defines a new CAS task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_CAS is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_CAS + + +do -- TASK_CAS + + --- The TASK_CAS class + -- @type TASK_CAS + -- @extends Task#TASK_BASE + TASK_CAS = { + ClassName = "TASK_CAS", + } + + --- Instantiates a new TASK_CAS. + -- @param #TASK_CAS self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_CAS self + function TASK_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "CAS", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_CAS. + -- @param #TASK_CAS self + -- @return #nil + function TASK_CAS:CleanUp() + + self:GetParent( self ):CleanUp() + + return nil + end + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_CAS self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_CAS self + function TASK_CAS:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "CAS", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a ground unit", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_CAS self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_CAS:OnNext( Fsm, Event, From, To, Event ) + + self:SetState( self, "State", To ) + + end + + --- @param #TASK_CAS self + function TASK_CAS:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + + --- @param #TASK_CAS self + function TASK_CAS:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_CAS self + function TASK_CAS._Scheduler() + self:F2() + + return true + end + +end + + + +--- This module contains the TASK_BAI classes. +-- +-- 1) @{#TASK_BAI} class, extends @{Task#TASK_BASE} +-- ================================================= +-- The @{#TASK_BAI} class defines a new BAI task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. +-- The TASK_BAI is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_BAI + + +do -- TASK_BAI + + --- The TASK_BAI class + -- @type TASK_BAI + -- @extends Task#TASK_BASE + TASK_BAI = { + ClassName = "TASK_BAI", + } + + --- Instantiates a new TASK_BAI. + -- @param #TASK_BAI self + -- @param Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Zone#ZONE_BASE TargetZone + -- @return #TASK_BAI self + function TASK_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "BAI", "A2G" ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + _EVENTDISPATCHER:OnDead( self._EventDead, self ) + _EVENTDISPATCHER:OnCrash( self._EventDead, self ) + _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- Removes a TASK_BAI. + -- @param #TASK_BAI self + -- @return #nil + function TASK_BAI:CleanUp() + + self:GetParent( self ):CleanUp() + + return nil + end + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_BAI self + -- @param Unit#UNIT TaskUnit + -- @return #TASK_BAI self + function TASK_BAI:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) + local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "BAI", TaskUnit, self.TargetSetUnit ) ) + local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) + + local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + initial = 'None', + events = { + { name = 'Next', from = 'None', to = 'Planned' }, + { name = 'Next', from = 'Planned', to = 'Assigned' }, + { name = 'Reject', from = 'Planned', to = 'Rejected' }, + { name = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Arrived', to = 'Failed' } + }, + callbacks = { + onNext = self.OnNext, + onRemove = self.OnRemove, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, + Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, + Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + ProcessSEAD:AddScore( "Destroy", "destroyed a ground unit", 25 ) + ProcessSEAD:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_BAI self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_BAI:OnNext( Fsm, Event, From, To, Event ) + + self:SetState( self, "State", To ) + + end + + --- @param #TASK_BAI self + function TASK_BAI:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + + --- @param #TASK_BAI self + function TASK_BAI:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_BAI self + function TASK_BAI._Scheduler() + self:F2() + + return true + end + +end + + + + BASE:TraceOnOff( false ) env.info( '*** MOOSE INCLUDE END *** ' ) diff --git a/Moose Mission Setup/Moose_Create.bat b/Moose Mission Setup/Moose_Create.bat index a6ebea40e..486d22b90 100644 --- a/Moose Mission Setup/Moose_Create.bat +++ b/Moose Mission Setup/Moose_Create.bat @@ -39,6 +39,7 @@ COPY /b Moose.lua + "Moose Create Static\Moose_Static_Loader.lua" Moose. COPY /b Moose.lua + %1\Routines.lua Moose.lua +COPY /b Moose.lua + %1\Utils.lua Moose.lua COPY /b Moose.lua + %1\Base.lua Moose.lua COPY /b Moose.lua + %1\Object.lua Moose.lua COPY /b Moose.lua + %1\Identifiable.lua Moose.lua @@ -81,8 +82,22 @@ COPY /b Moose.lua + %1\MissileTrainer.lua Moose.lua COPY /b Moose.lua + %1\PatrolZone.lua Moose.lua COPY /b Moose.lua + %1\AIBalancer.lua Moose.lua COPY /b Moose.lua + %1\AirbasePolice.lua Moose.lua + COPY /b Moose.lua + %1\Detection.lua Moose.lua -COPY /b Moose.lua + %1\FAC.lua Moose.lua +COPY /b Moose.lua + %1\DetectionManager.lua Moose.lua + +COPY /b Moose.lua + %1\StateMachine.lua Moose.lua + +COPY /b Moose.lua + %1\Process.lua Moose.lua +COPY /b Moose.lua + %1\Process_Assign.lua Moose.lua +COPY /b Moose.lua + %1\Process_Route.lua Moose.lua +COPY /b Moose.lua + %1\Process_Smoke.lua Moose.lua +COPY /b Moose.lua + %1\Process_Destroy.lua Moose.lua + +COPY /b Moose.lua + %1\Task.lua Moose.lua +COPY /b Moose.lua + %1\Task_SEAD.lua Moose.lua +COPY /b Moose.lua + %1\Task_CAS.lua Moose.lua +COPY /b Moose.lua + %1\Task_BAI.lua Moose.lua COPY /b Moose.lua + "Moose Create Static\Moose_Trace_Off.lua" Moose.lua diff --git a/Moose Test Missions/MOOSE_Test_Template.miz b/Moose Test Missions/MOOSE_Test_Template.miz index 704fd8d76..944167682 100644 Binary files a/Moose Test Missions/MOOSE_Test_Template.miz and b/Moose Test Missions/MOOSE_Test_Template.miz differ diff --git a/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.miz b/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.miz index 3424a0811..363465a76 100644 Binary files a/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.miz and b/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.miz differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE-DB.miz b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE-DB.miz index 723b03e22..7f8085bbe 100644 Binary files a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE-DB.miz and b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE-DB.miz differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE.miz b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE.miz new file mode 100644 index 000000000..5bddc2b23 Binary files /dev/null and b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE.miz differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.miz b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.miz index 9c6b0f4ba..d7d8ea833 100644 Binary files a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.miz and b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.miz differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.miz b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.miz index bfbdd852c..02ac53eb9 100644 Binary files a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.miz and b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.miz differ diff --git a/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_with_Moose.miz b/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_with_Moose.miz index 84fdc5156..3a7aa114d 100644 Binary files a/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_with_Moose.miz and b/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_with_Moose.miz differ diff --git a/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_without_Moose.miz b/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_without_Moose.miz index 84f0095e3..11edf4a32 100644 Binary files a/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_without_Moose.miz and b/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_without_Moose.miz differ diff --git a/Moose Test Missions/Moose_Test_BASE/Moose_Test_BASE.miz b/Moose Test Missions/Moose_Test_BASE/Moose_Test_BASE.miz index a98c81cfd..4576e3460 100644 Binary files a/Moose Test Missions/Moose_Test_BASE/Moose_Test_BASE.miz and b/Moose Test Missions/Moose_Test_BASE/Moose_Test_BASE.miz differ diff --git a/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.miz b/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.miz index 8baef245a..8159ab8d9 100644 Binary files a/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.miz and b/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.miz differ diff --git a/Moose Test Missions/Moose_Test_DESTROY/MOOSE_Test_DESTROY.miz b/Moose Test Missions/Moose_Test_DESTROY/MOOSE_Test_DESTROY.miz index 7b6ce54e8..40d19fcb3 100644 Binary files a/Moose Test Missions/Moose_Test_DESTROY/MOOSE_Test_DESTROY.miz and b/Moose Test Missions/Moose_Test_DESTROY/MOOSE_Test_DESTROY.miz differ diff --git a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.lua b/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.lua index d98bc2215..f48027238 100644 --- a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.lua +++ b/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.lua @@ -1,5 +1,5 @@ local FACGroup = GROUP:FindByName( "FAC Group" ) -local FACDetection = DETECTION_UNITGROUPS:New( FACGroup, 1000, 250 ):SmokeDetectedZones():FlareDetectedUnits() +local FACDetection = DETECTION_AREAS:New( FACGroup, 1000, 250 ):FlareDetectedZones():FlareDetectedUnits() diff --git a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.miz b/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.miz index d87b87f86..ec8fb352e 100644 Binary files a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.miz and b/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.miz differ diff --git a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION_Laser.miz b/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION_Laser.miz new file mode 100644 index 000000000..f9c4a2735 Binary files /dev/null and b/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION_Laser.miz differ diff --git a/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.lua b/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.lua new file mode 100644 index 000000000..a023859c2 --- /dev/null +++ b/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.lua @@ -0,0 +1,17 @@ + +local Mission = MISSION:New( "Attack Detect Mission", "High", "Attack Detect Mission Briefing", coalition.side.RED ) +local Scoring = SCORING:New( "Detect Demo" ) +Mission:AddScoring( Scoring ) + + +local FACSet = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterCoalitions("red"):FilterStart() + +local FACDetection = DETECTION_AREAS:New( FACSet, 10000, 3000 ) + +local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Attack" ):FilterStart() + + +local CommandCenter = GROUP:FindByName( "HQ" ) + +local TaskAssign = DETECTION_DISPATCHER:New( Mission, CommandCenter, AttackGroups, FACDetection ) + diff --git a/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.miz b/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.miz new file mode 100644 index 000000000..c87a50c5b Binary files /dev/null and b/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.miz differ diff --git a/Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.miz b/Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.miz index 8e1581789..d26abd0ff 100644 Binary files a/Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.miz and b/Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.miz differ diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.lua index 8e51ca4db..1717feeb7 100644 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.lua +++ b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.lua @@ -2,7 +2,8 @@ local FACGroup = GROUP:FindByName( "FAC Group" ) -local FACDetection = DETECTION_UNITGROUPS:New( FACGroup, 1000, 250 ) -local FACClientSet = SET_CLIENT:New():FilterCoalitions( "blue" ):FilterStart() +local FACDetection = DETECTION_AREAS:New( FACGroup, 1000, 250 ) +local SeadClientSet = SET_CLIENT:New():FilterCoalitions( "blue" ):FilterStart() +local DestroyClientSet = SET_CLIENT:New():FilterCoalitions( "blue" ):FilterStart() -local FACReporting = FAC_REPORTING:New( FACClientSet, FACDetection ) \ No newline at end of file +local FACReporting = FAC_REPORTING:New( FACClientSet, FACDetection ) diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.miz b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.miz index 6e03d73c9..72ac4e5a8 100644 Binary files a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.miz and b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.miz differ diff --git a/Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/MOOSE_Test_GROUP_SwitchWayPoint.miz b/Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/MOOSE_Test_GROUP_SwitchWayPoint.miz index 53807ac46..ac909a27e 100644 Binary files a/Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/MOOSE_Test_GROUP_SwitchWayPoint.miz and b/Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/MOOSE_Test_GROUP_SwitchWayPoint.miz differ diff --git a/Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz b/Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz index d10f5204d..c1d2d8239 100644 Binary files a/Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz and b/Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz differ diff --git a/Moose Test Missions/Moose_Test_PATROLZONE/MOOSE_Test_PATROLZONE.miz b/Moose Test Missions/Moose_Test_PATROLZONE/MOOSE_Test_PATROLZONE.miz index 4febdf4bb..8c518c6f2 100644 Binary files a/Moose Test Missions/Moose_Test_PATROLZONE/MOOSE_Test_PATROLZONE.miz and b/Moose Test Missions/Moose_Test_PATROLZONE/MOOSE_Test_PATROLZONE.miz differ diff --git a/Moose Test Missions/Moose_Test_SEAD/MOOSE_Test_SEAD.miz b/Moose Test Missions/Moose_Test_SEAD/MOOSE_Test_SEAD.miz index 1261c928e..ba83cf5d9 100644 Binary files a/Moose Test Missions/Moose_Test_SEAD/MOOSE_Test_SEAD.miz and b/Moose Test Missions/Moose_Test_SEAD/MOOSE_Test_SEAD.miz differ diff --git a/Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.miz b/Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.miz index 14ccfcc13..05c7c4d76 100644 Binary files a/Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.miz and b/Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.miz differ diff --git a/Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.miz b/Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.miz index c9464d870..c8bdc3663 100644 Binary files a/Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.miz and b/Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.miz differ diff --git a/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.miz b/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.miz index da74d4f16..8694d5c0c 100644 Binary files a/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.miz and b/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz b/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz index a334ca87b..efa1e04de 100644 Binary files a/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz and b/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.miz b/Moose Test Missions/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.miz index c41aaf3da..3e09ead1f 100644 Binary files a/Moose Test Missions/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.miz and b/Moose Test Missions/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.miz b/Moose Test Missions/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.miz index 4c83699ef..9dc3b3075 100644 Binary files a/Moose Test Missions/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.miz and b/Moose Test Missions/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz b/Moose Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz index 908b4d5e2..1668439be 100644 Binary files a/Moose Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz and b/Moose Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz differ diff --git a/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.miz b/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.miz index 398f9ecab..932e07940 100644 Binary files a/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.miz and b/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.miz differ diff --git a/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.lua b/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.lua new file mode 100644 index 000000000..cb4f2a967 --- /dev/null +++ b/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.lua @@ -0,0 +1,16 @@ + +local Mission = MISSION:New( 'SEAD Targets', "Strategic", "SEAD the enemy", coalition.side.RED ) +local Scoring = SCORING:New( "SEAD" ) + +Mission:AddScoring( Scoring ) + +local SEADGroup = GROUP:FindByName( "Test SEAD" ) +local TargetSet = SET_UNIT:New():FilterPrefixes( "US Hawk SR" ):FilterStart() + +local TargetZone = ZONE:New( "Target Zone" ) + +local TaskSEAD = TASK_SEAD:New( Mission, TargetSet, TargetZone ):SetName( "SEAD Radars" ):AssignToGroup( SEADGroup ) + + + + diff --git a/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.miz b/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.miz new file mode 100644 index 000000000..1b36b65c7 Binary files /dev/null and b/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.miz differ diff --git a/Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.miz b/Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.miz index 6d375fbbf..c6422844e 100644 Binary files a/Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.miz and b/Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.miz b/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.miz index f61a470fb..f41e027f8 100644 Binary files a/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.miz and b/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.miz b/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.miz index 1fb62a487..fee2a66e8 100644 Binary files a/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.miz and b/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.miz b/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.miz index 53207baf2..dc7886640 100644 Binary files a/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.miz and b/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.miz b/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.miz index 54e2db9d5..991655cb5 100644 Binary files a/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.miz and b/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.miz differ diff --git a/Moose Training/Documentation/AIBalancer.html b/Moose Training/Documentation/AIBalancer.html index c113e2ea1..f66e41d9c 100644 --- a/Moose Training/Documentation/AIBalancer.html +++ b/Moose Training/Documentation/AIBalancer.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Airbase.html b/Moose Training/Documentation/Airbase.html index b3024951a..3b2cf5d9b 100644 --- a/Moose Training/Documentation/Airbase.html +++ b/Moose Training/Documentation/Airbase.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/AirbasePolice.html b/Moose Training/Documentation/AirbasePolice.html index 0e3559636..f7095171f 100644 --- a/Moose Training/Documentation/AirbasePolice.html +++ b/Moose Training/Documentation/AirbasePolice.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -102,15 +112,15 @@ CLIENTS should not be allowed to:

    All the airbases on the caucasus map can be monitored using this class. If you want to monitor specific airbases, you need to use the AIRBASEPOLICE_BASE.Monitor() method, which takes a table or airbase names. The following names can be given: - * AnapaVityazevo - * Batumi - * Beslan - * Gelendzhik - * Gudauta - * Kobuleti - * KrasnodarCenter - * KrasnodarPashkovsky - * Krymsk + * AnapaVityazevo + * Batumi + * Beslan + * Gelendzhik + * Gudauta + * Kobuleti + * KrasnodarCenter + * KrasnodarPashkovsky + * Krymsk * Kutaisi * MaykopKhanskaya * MineralnyeVody @@ -122,8 +132,17 @@ The following names can be given: * Soganlug * SukhumiBabushara * TbilisiLochini - * Vaziani -

    + * Vaziani

    + +

    3) AirbasePolice#AIRBASEPOLICE_NEVADA class, extends AirbasePolice#AIRBASEPOLICE_BASE

    +

    All the airbases on the NEVADA map can be monitored using this class. +If you want to monitor specific airbases, you need to use the AIRBASEPOLICE_BASE.Monitor() method, which takes a table or airbase names. +The following names can be given: + * Nellis + * McCarran + * Creech + * Groom Lake

    +

    Global(s)

    @@ -137,6 +156,12 @@ The following names can be given: + + + +
    AIRBASEPOLICE_CAUCASUS +
    AIRBASEPOLICE_NEVADA +
    @@ -162,6 +187,28 @@ The following names can be given: AIRBASEPOLICE_CAUCASUS.SetClient + + + + +

    Type AIRBASEPOLICE_NEVADA

    + + + + + + + + + + + +
    AIRBASEPOLICE_NEVADA.Airbases + +
    AIRBASEPOLICE_NEVADA.ClassName + +
    AIRBASEPOLICE_NEVADA:New(SetClient) +

    Creates a new AIRBASEPOLICE_NEVADA object.

    @@ -193,6 +240,20 @@ The following names can be given: + + +
    +
    + + #AIRBASEPOLICE_NEVADA + +AIRBASEPOLICE_NEVADA + +
    +
    + + +

    Type AirbasePolice

    @@ -244,6 +305,64 @@ The following names can be given: + + + +

    Type AIRBASEPOLICE_NEVADA

    +

    Field(s)

    +
    +
    + + + +AIRBASEPOLICE_NEVADA.Airbases + +
    +
    + + + +
    +
    +
    +
    + + #string + +AIRBASEPOLICE_NEVADA.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +AIRBASEPOLICE_NEVADA:New(SetClient) + +
    +
    + +

    Creates a new AIRBASEPOLICE_NEVADA object.

    + +

    Parameter

    +
      +
    • + +

      SetClient : +A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase.

      + +
    • +
    +

    Return value

    + +

    #AIRBASEPOLICE_NEVADA: +self

    +
    diff --git a/Moose Training/Documentation/Base.html b/Moose Training/Documentation/Base.html index dac7fe836..f948b46c0 100644 --- a/Moose Training/Documentation/Base.html +++ b/Moose Training/Documentation/Base.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -140,6 +150,8 @@ These tracing levels were defined to avoid bulks of tracing to be generated by l
    +

    Author: FlightControl

    +

    Global(s)

    diff --git a/Moose Training/Documentation/CARGO.html b/Moose Training/Documentation/CARGO.html index e6dfcbac7..ceada80ca 100644 --- a/Moose Training/Documentation/CARGO.html +++ b/Moose Training/Documentation/CARGO.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/CleanUp.html b/Moose Training/Documentation/CleanUp.html index f2a0ee524..ce8252cba 100644 --- a/Moose Training/Documentation/CleanUp.html +++ b/Moose Training/Documentation/CleanUp.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Client.html b/Moose Training/Documentation/Client.html index b0ae7e2f7..db145a6ef 100644 --- a/Moose Training/Documentation/Client.html +++ b/Moose Training/Documentation/Client.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -269,24 +279,6 @@ If the DCS Unit object does not exist or is nil, the CLIENT methods will return - - - - - - - - - - - - @@ -843,48 +835,6 @@ true if multi-seated.

    #boolean: true is a transport.

    - - -
    -
    - - - -CLIENT.MenuMessages - -
    -
    - - - -
    -
    -
    -
    - - - -CLIENT.MenuRouteMessageOff - -
    -
    - - - -
    -
    -
    -
    - - - -CLIENT.MenuRouteMessageOn - -
    -
    - - -
    diff --git a/Moose Training/Documentation/Controllable.html b/Moose Training/Documentation/Controllable.html index dd406caf8..e22281f82 100644 --- a/Moose Training/Documentation/Controllable.html +++ b/Moose Training/Documentation/Controllable.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSAirbase.html b/Moose Training/Documentation/DCSAirbase.html index 3ab6e9239..bc1c17d69 100644 --- a/Moose Training/Documentation/DCSAirbase.html +++ b/Moose Training/Documentation/DCSAirbase.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSCoalitionObject.html b/Moose Training/Documentation/DCSCoalitionObject.html index bbb8eeed4..ce7497516 100644 --- a/Moose Training/Documentation/DCSCoalitionObject.html +++ b/Moose Training/Documentation/DCSCoalitionObject.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSCommand.html b/Moose Training/Documentation/DCSCommand.html index 108b550fc..7c87e2d56 100644 --- a/Moose Training/Documentation/DCSCommand.html +++ b/Moose Training/Documentation/DCSCommand.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSController.html b/Moose Training/Documentation/DCSController.html index 9219c37a8..e13cdb876 100644 --- a/Moose Training/Documentation/DCSController.html +++ b/Moose Training/Documentation/DCSController.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSGroup.html b/Moose Training/Documentation/DCSGroup.html index cc29cda20..90809b92e 100644 --- a/Moose Training/Documentation/DCSGroup.html +++ b/Moose Training/Documentation/DCSGroup.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSObject.html b/Moose Training/Documentation/DCSObject.html index 59f399e2c..8a40cb461 100644 --- a/Moose Training/Documentation/DCSObject.html +++ b/Moose Training/Documentation/DCSObject.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSTask.html b/Moose Training/Documentation/DCSTask.html index f4086e3a2..e39911f15 100644 --- a/Moose Training/Documentation/DCSTask.html +++ b/Moose Training/Documentation/DCSTask.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSTypes.html b/Moose Training/Documentation/DCSTypes.html index 56df2800d..0783b9484 100644 --- a/Moose Training/Documentation/DCSTypes.html +++ b/Moose Training/Documentation/DCSTypes.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSUnit.html b/Moose Training/Documentation/DCSUnit.html index 534168c91..7456731b4 100644 --- a/Moose Training/Documentation/DCSUnit.html +++ b/Moose Training/Documentation/DCSUnit.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCSWorld.html b/Moose Training/Documentation/DCSWorld.html index d642f2cdd..46537ab4b 100644 --- a/Moose Training/Documentation/DCSWorld.html +++ b/Moose Training/Documentation/DCSWorld.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCScountry.html b/Moose Training/Documentation/DCScountry.html index d0158de9d..68056a71e 100644 --- a/Moose Training/Documentation/DCScountry.html +++ b/Moose Training/Documentation/DCScountry.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DCStimer.html b/Moose Training/Documentation/DCStimer.html index 5e4411da7..4f77d4031 100644 --- a/Moose Training/Documentation/DCStimer.html +++ b/Moose Training/Documentation/DCStimer.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DEPLOYTASK.html b/Moose Training/Documentation/DEPLOYTASK.html index 3e4c7063a..9fa6e114d 100644 --- a/Moose Training/Documentation/DEPLOYTASK.html +++ b/Moose Training/Documentation/DEPLOYTASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DESTROYBASETASK.html b/Moose Training/Documentation/DESTROYBASETASK.html index 338c74090..6147fbf47 100644 --- a/Moose Training/Documentation/DESTROYBASETASK.html +++ b/Moose Training/Documentation/DESTROYBASETASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DESTROYGROUPSTASK.html b/Moose Training/Documentation/DESTROYGROUPSTASK.html index 7ba5e251d..ce9fe0487 100644 --- a/Moose Training/Documentation/DESTROYGROUPSTASK.html +++ b/Moose Training/Documentation/DESTROYGROUPSTASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DESTROYRADARSTASK.html b/Moose Training/Documentation/DESTROYRADARSTASK.html index 2fe56f95a..7ea91d10e 100644 --- a/Moose Training/Documentation/DESTROYRADARSTASK.html +++ b/Moose Training/Documentation/DESTROYRADARSTASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/DESTROYUNITTYPESTASK.html b/Moose Training/Documentation/DESTROYUNITTYPESTASK.html index 38f2d846c..d1057dcb6 100644 --- a/Moose Training/Documentation/DESTROYUNITTYPESTASK.html +++ b/Moose Training/Documentation/DESTROYUNITTYPESTASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Database.html b/Moose Training/Documentation/Database.html index b6f53e90c..a649cd1c4 100644 --- a/Moose Training/Documentation/Database.html +++ b/Moose Training/Documentation/Database.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Detection.html b/Moose Training/Documentation/Detection.html index 88aef1870..8837fafa9 100644 --- a/Moose Training/Documentation/Detection.html +++ b/Moose Training/Documentation/Detection.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -87,7 +97,8 @@

    1) Detection#DETECTION_BASE class, extends Base#BASE

    -

    The Detection#DETECTION_BASE class defines the core functions to administer detected objects.

    +

    The Detection#DETECTION_BASE class defines the core functions to administer detected objects. + The Detection#DETECTION_BASE class will detect objects within the battle zone for a list of Groups detecting targets following (a) detection method(s).

    1.1) DETECTION_BASE constructor

    Construct a new DETECTION_BASE instance using the Detection#DETECTION_BASE.New() method.

    @@ -116,7 +127,7 @@

    2) Detection#DETECTION_UNITGROUPS class, extends Detection#DETECTION_BASE

    -

    The Detection#DETECTION_UNITGROUPS class will detect units within the battle zone for a FAC group, +

    The Detection#DETECTION_UNITGROUPS class will detect units within the battle zone for a list of Groups detecting targets following (a) detection method(s), and will build a list (table) of Set#SET_UNITs containing the Unit#UNITs detected. The class is group the detected units within zones given a DetectedZoneRange parameter. A set with multiple detected zones will be created as there are groups of units detected.

    @@ -139,7 +150,10 @@

    Use the methods Detection#DETECTION_UNITGROUPS.FlareDetectedZones() or Detection#DETECTION_UNITGROUPS.SmokeDetectedZones() to flare or smoke the detected zones when a new detection has taken place.


    -

    + +

    ### Contributions: Mechanic - Concept & Testing + ### Authors: FlightControl : Design & Programming +

    Global(s)

    CLIENT:IsTransport()

    Evaluates if the CLIENT is a transport.

    -
    CLIENT.MenuMessages - -
    CLIENT.MenuRouteMessageOff - -
    CLIENT.MenuRouteMessageOn -
    @@ -209,25 +223,25 @@ - + - - - - + + + + @@ -237,9 +251,9 @@ - + @@ -261,9 +275,9 @@ - + @@ -303,7 +317,7 @@ - + @@ -330,6 +344,40 @@ + +
    DETECTION_BASE.DetectedObjects - +

    The list of detected objects.

    DETECTION_BASE.DetectedSetsDETECTION_BASE.DetectionGroups -

    A list of Set#SET_BASEs containing the objects in each set that were detected. The base class will not build the detected sets, but will leave that to the derived classes.

    -
    DETECTION_BASE.DetectedZones - +

    The GROUP in the Forward Air Controller role.

    DETECTION_BASE.DetectionRange

    The range till which targets are accepted to be detected.

    +
    DETECTION_BASE.DetectionRun +
    DETECTION_BASE.FACGroupDETECTION_BASE:GetDetectedObject(ObjectName) -

    The GROUP in the Forward Air Controller role.

    +

    Gets a detected object with a given name.

    DETECTION_BASE:GetFACGroup()DETECTION_BASE:GetDetectionGroups() -

    Gets the FAC group.

    +

    Get the detection Groups.

    DETECTION_BASE:New(FACGroup, DetectionRange)DETECTION_BASE:New(DetectionGroups, DetectionRange)

    DETECTION constructor.

    DETECTION_BASE:_DetectionScheduler(SchedulerName)

    Form Sets of detected Unit#UNITs in an array of Set#SET_BASEs.

    +
    + +

    Type DETECTION_BASE.DetectedObject

    + + + + + + + + + + + + + + + + + + + +
    DETECTION_BASE.DetectedObject.Distance + +
    DETECTION_BASE.DetectedObject.Identified + +
    DETECTION_BASE.DetectedObject.Name + +
    DETECTION_BASE.DetectedObject.Type + +
    DETECTION_BASE.DetectedObject.Visible +
    @@ -337,6 +385,12 @@

    Type DETECTION_UNITGROUPS

    + + + + - + - + @@ -370,30 +424,42 @@ + + + + + + + + + + + + - - - - - - - - - + + + + + @@ -430,6 +496,28 @@ + +
    DETECTION_UNITGROUPS.AddDetectedArea(Set, Zone, self) +

    Add a detected DETECTION_UNITGROUPS.DetectedArea.

    +
    DETECTION_UNITGROUPS.ClassName @@ -349,15 +403,15 @@
    DETECTION_UNITGROUPS.DetectedSetsDETECTION_UNITGROUPS.DetectedAreas -

    A list of Set#SET_UNITs containing the units in each set that were detected within a DetectionZoneRange.

    +

    A list of areas containing the set of Units, Zones, the center Unit within the zone, and ID of each area that was detected within a DetectionZoneRange.

    DETECTION_UNITGROUPS.DetectedZonesDETECTION_UNITGROUPS.DetectionZoneRange -

    A list of Zone#ZONE_UNITs containing the zones of the reference detected units.

    +

    The range till which targets are grouped upon the first detected target.

    DETECTION_UNITGROUPS:FlareDetectedZones()

    Flare the detected zones

    +
    DETECTION_UNITGROUPS:GetDetectedAreaCount() +

    Get the amount of DETECTION_UNITGROUPS.DetectedAreas.

    +
    DETECTION_UNITGROUPS:GetDetectedAreas() +

    Get the detected DETECTION_UNITGROUPS.DetectedAreas.

    +
    DETECTION_UNITGROUPS:GetDetectedSet(Index) +

    Get the Set#SET_UNIT of a detecttion area using a given numeric index.

    DETECTION_UNITGROUPS:GetDetectedZone(Index) -

    Get a SET of detected objects using a given numeric index.

    +

    Get the Zone#ZONE_UNIT of a detection area using a given numeric index.

    DETECTION_UNITGROUPS:GetDetectedZoneCount() -

    Get the amount of Zone#ZONE_UNITs with detected units.

    -
    DETECTION_UNITGROUPS:GetDetectedZones() -

    Get the detected Zone#ZONE_UNITs.

    -
    DETECTION_UNITGROUPS:New(FACGroup, DetectionRange, DetectionZoneRange)DETECTION_UNITGROUPS:New(DetectionGroups, DetectionRange, DetectionZoneRange)

    DETECTION_UNITGROUPS constructor.

    +
    DETECTION_UNITGROUPS:RemoveDetectedArea(Index) +

    Remove a detected DETECTION_UNITGROUPS.DetectedArea with a given Index.

    DETECTION_UNITGROUPS._SmokeDetectedZones +
    + +

    Type DETECTION_UNITGROUPS.DetectedArea

    + + + + + + + + + + + +
    DETECTION_UNITGROUPS.DetectedArea.AreaID +

    -- The identifier of the detected area.

    +
    DETECTION_UNITGROUPS.DetectedArea.Set +

    -- The Set of Units in the detected area.

    +
    DETECTION_UNITGROUPS.DetectedArea.Zone +

    -- The Zone of the detected area.

    @@ -592,42 +680,28 @@ self

    - + #DETECTION_BASE.DetectedObjects DETECTION_BASE.DetectedObjects
    - +

    The list of detected objects.

    - #DETECTION_BASE.DetectedSets - -DETECTION_BASE.DetectedSets + Group#GROUP + +DETECTION_BASE.DetectionGroups
    -

    A list of Set#SET_BASEs containing the objects in each set that were detected. The base class will not build the detected sets, but will leave that to the derived classes.

    - -
    -
    -
    -
    - - - -DETECTION_BASE.DetectedZones - -
    -
    - - +

    The GROUP in the Forward Air Controller role.

    @@ -643,6 +717,20 @@ self

    The range till which targets are accepted to be detected.

    + + +
    +
    + + #number + +DETECTION_BASE.DetectionRun + +
    +
    + + +
    @@ -662,14 +750,26 @@ self

    - Group#GROUP - -DETECTION_BASE.FACGroup + +DETECTION_BASE:GetDetectedObject(ObjectName)
    -

    The GROUP in the Forward Air Controller role.

    +

    Gets a detected object with a given name.

    + +

    Parameter

    +
      +
    • + +

      #string ObjectName :

      + +
    • +
    +

    Return value

    + +

    #DETECTION_BASE.DetectedObject:

    +
    @@ -738,18 +838,18 @@ DetectedSets

    - -DETECTION_BASE:GetFACGroup() + +DETECTION_BASE:GetDetectionGroups()
    -

    Gets the FAC group.

    +

    Get the detection Groups.

    Return value

    -

    Group#GROUP: -self

    +

    Group#GROUP:

    +
    @@ -913,7 +1013,7 @@ self

    -DETECTION_BASE:New(FACGroup, DetectionRange) +DETECTION_BASE:New(DetectionGroups, DetectionRange)
    @@ -924,7 +1024,7 @@ self

    • -

      Group#GROUP FACGroup : +

      Group#GROUP DetectionGroups : The GROUP in the Forward Air Controller role.

    • @@ -1025,9 +1125,82 @@ self

    -

    Type DETECTION_BASE.DetectedSets

    +

    Type DETECTION_BASE.DetectedObject

    +

    Field(s)

    +
    +
    + + #number + +DETECTION_BASE.DetectedObject.Distance + +
    +
    + + + +
    +
    +
    +
    + + #boolean + +DETECTION_BASE.DetectedObject.Identified + +
    +
    + + + +
    +
    +
    +
    + + #string + +DETECTION_BASE.DetectedObject.Name + +
    +
    + + + +
    +
    +
    +
    + + #string + +DETECTION_BASE.DetectedObject.Type + +
    +
    + + + +
    +
    +
    +
    + + #boolean + +DETECTION_BASE.DetectedObject.Visible + +
    +
    + + + +
    +
    + +

    Type DETECTION_BASE.DetectedObjects

    -

    Type DETECTION_BASE.DetectedZones

    +

    Type DETECTION_BASE.DetectedSets

    Type DETECTION_UNITGROUPS

    @@ -1037,6 +1210,44 @@ self

    + +DETECTION_UNITGROUPS.AddDetectedArea(Set, Zone, self) + +
    +
    + +

    Add a detected DETECTION_UNITGROUPS.DetectedArea.

    + +

    Parameters

    +
      +
    • + +

      Set#SET_UNIT Set : +-- The Set of Units in the detected area.

      + +
    • +
    • + +

      Zone#ZONE_UNIT Zone : +-- The Zone of the detected area.

      + +
    • +
    • + +

      self :

      + +
    • +
    +

    Return value

    + +

    #DETECTION_UNITGROUPS.DetectedArea: +DetectedArea

    + +
    +
    +
    +
    + #string DETECTION_UNITGROUPS.ClassName @@ -1072,28 +1283,28 @@ self

    - #DETECTION_UNITGROUPS.DetectedSets - -DETECTION_UNITGROUPS.DetectedSets + #DETECTION_UNITGROUPS.DetectedAreas + +DETECTION_UNITGROUPS.DetectedAreas
    -

    A list of Set#SET_UNITs containing the units in each set that were detected within a DetectionZoneRange.

    +

    A list of areas containing the set of Units, Zones, the center Unit within the zone, and ID of each area that was detected within a DetectionZoneRange.

    - #DETECTION_UNITGROUPS.DetectedZones - -DETECTION_UNITGROUPS.DetectedZones + DCSTypes#Distance + +DETECTION_UNITGROUPS.DetectionZoneRange
    -

    A list of Zone#ZONE_UNITs containing the zones of the reference detected units.

    +

    The range till which targets are grouped upon the first detected target.

    @@ -1136,13 +1347,49 @@ self

    - -DETECTION_UNITGROUPS:GetDetectedZone(Index) + +DETECTION_UNITGROUPS:GetDetectedAreaCount()
    -

    Get a SET of detected objects using a given numeric index.

    +

    Get the amount of DETECTION_UNITGROUPS.DetectedAreas.

    + +

    Return value

    + +

    #number: +DetectedAreaCount

    + +
    +
    +
    +
    + + +DETECTION_UNITGROUPS:GetDetectedAreas() + +
    +
    + +

    Get the detected DETECTION_UNITGROUPS.DetectedAreas.

    + +

    Return value

    + +

    #DETECTION_UNITGROUPS.DetectedAreas: +DetectedAreas

    + +
    +
    +
    +
    + + +DETECTION_UNITGROUPS:GetDetectedSet(Index) + +
    +
    + +

    Get the Set#SET_UNIT of a detecttion area using a given numeric index.

    Parameter

      @@ -1154,44 +1401,34 @@ self

    Return value

    -

    Zone#ZONE_UNIT:

    - +

    Set#SET_UNIT: +DetectedSet

    - -DETECTION_UNITGROUPS:GetDetectedZoneCount() + +DETECTION_UNITGROUPS:GetDetectedZone(Index)
    -

    Get the amount of Zone#ZONE_UNITs with detected units.

    +

    Get the Zone#ZONE_UNIT of a detection area using a given numeric index.

    +

    Parameter

    +
      +
    • + +

      #number Index :

      + +
    • +

    Return value

    -

    #number: -Count

    - -
    -
    -
    -
    - - -DETECTION_UNITGROUPS:GetDetectedZones() - -
    -
    - -

    Get the detected Zone#ZONE_UNITs.

    - -

    Return value

    - -

    #DETECTION_UNITGROUPS.DetectedZones: -DetectedZones

    +

    Zone#ZONE_UNIT: +DetectedZone

    @@ -1199,7 +1436,7 @@ DetectedZones

    -DETECTION_UNITGROUPS:New(FACGroup, DetectionRange, DetectionZoneRange) +DETECTION_UNITGROUPS:New(DetectionGroups, DetectionRange, DetectionZoneRange)
    @@ -1210,7 +1447,7 @@ DetectedZones

    +
    +
    +
    + + +DETECTION_UNITGROUPS:RemoveDetectedArea(Index) + +
    +
    + +

    Remove a detected DETECTION_UNITGROUPS.DetectedArea with a given Index.

    + +

    Parameter

    +
      +
    • + +

      #number Index : +The Index of the detection are to be removed.

      + +
    • +
    +

    Return value

    + +

    #nil:

    + +
    @@ -1327,9 +1591,52 @@ self

    -

    Type DETECTION_UNITGROUPS.DetectedSets

    - -

    Type DETECTION_UNITGROUPS.DetectedZones

    +

    Type DETECTION_UNITGROUPS.DetectedArea

    +

    Field(s)

    +
    +
    + + #number + +DETECTION_UNITGROUPS.DetectedArea.AreaID + +
    +
    + +

    -- The identifier of the detected area.

    + +
    +
    +
    +
    + + Set#SET_UNIT + +DETECTION_UNITGROUPS.DetectedArea.Set + +
    +
    + +

    -- The Set of Units in the detected area.

    + +
    +
    +
    +
    + + Zone#ZONE_UNIT + +DETECTION_UNITGROUPS.DetectedArea.Zone + +
    +
    + +

    -- The Zone of the detected area.

    + +
    +
    + +

    Type DETECTION_UNITGROUPS.DetectedAreas

    diff --git a/Moose Training/Documentation/DetectionManager.html b/Moose Training/Documentation/DetectionManager.html new file mode 100644 index 000000000..524114c63 --- /dev/null +++ b/Moose Training/Documentation/DetectionManager.html @@ -0,0 +1,1065 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module DetectionManager

    + +

    This module contains the DETECTION_MANAGER class and derived classes.

    + + + +
    + +

    1) DetectionManager#DETECTION_MANAGER class, extends Base#BASE

    +

    The DetectionManager#DETECTION_MANAGER class defines the core functions to report detected objects to groups. +Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour.

    + +

    1.1) DETECTION_MANAGER constructor:

    +

    * DetectionManager#DETECTION_MANAGER.New(): Create a new DETECTION_MANAGER instance.

    + +

    1.2) DETECTION_MANAGER reporting:

    +

    Derived DETECTION_MANAGER classes will reports detected units using the method DetectionManager#DETECTION_MANAGER.ReportDetected(). This method implements polymorphic behaviour.

    + +

    The time interval in seconds of the reporting can be changed using the methods DetectionManager#DETECTION_MANAGER.SetReportInterval(). +To control how long a reporting message is displayed, use DetectionManager#DETECTION_MANAGER.SetReportDisplayTime(). +Derived classes need to implement the method DetectionManager#DETECTION_MANAGER.GetReportDisplayTime() to use the correct display time for displayed messages during a report.

    + +

    Reporting can be started and stopped using the methods DetectionManager#DETECTION_MANAGER.StartReporting() and DetectionManager#DETECTION_MANAGER.StopReporting() respectively. +If an ad-hoc report is requested, use the method DetectionManager#DETECTION_MANAGER().

    + +

    The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds.

    + +
    + +

    2) DetectionManager#FAC_REPORTING class, extends DetectionManager#DETECTION_MANAGER

    +

    The DetectionManager#FAC_REPORTING class implements detected units reporting. Reporting can be controlled using the reporting methods available in the DetectionManager#DETECTION_MANAGER class.

    + +

    2.1) FAC_REPORTING constructor:

    +

    The DetectionManager#FAC_REPORTING.New() method creates a new FAC_REPORTING instance.

    + +
    + +

    Contributions: Mechanic, Prof_Hilactic, FlightControl - Concept & Testing

    +

    Author: FlightControl - Framework Design & Programming

    + + +

    Global(s)

    + + + + + + + + + + + + + +
    DETECTION_MANAGER + +
    FAC_REPORTING + +
    TASK_DISPATCHER + +
    +

    Type DETECTION_MANAGER

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DETECTION_MANAGER.ClassName + +
    DETECTION_MANAGER.Detection +

    The DETECTION_BASE object that is used to report the detected objects.

    +
    DETECTION_MANAGER.FacScheduler + +
    DETECTION_MANAGER:GetReportDisplayTime() +

    Get the reporting message display time.

    +
    DETECTION_MANAGER:New(SetGroup, Detection) +

    FAC constructor.

    +
    DETECTION_MANAGER:ReportDetected(Detection) +

    Reports the detected items to the Set#SET_GROUP.

    +
    DETECTION_MANAGER:Schedule(DelayTime, ReportInterval) +

    Schedule the FAC reporting.

    +
    DETECTION_MANAGER.SetGroup +

    The groups to which the FAC will report to.

    +
    DETECTION_MANAGER:SetReportDisplayTime(ReportDisplayTime) +

    Set the reporting message display time.

    +
    DETECTION_MANAGER:SetReportInterval(ReportInterval) +

    Set the reporting time interval.

    +
    DETECTION_MANAGER:_FacScheduler(SchedulerName) +

    Report the detected Unit#UNITs detected within the Detection#DETECTION_BASE object to the Set#SET_GROUPs.

    +
    DETECTION_MANAGER._ReportDisplayTime + +
    DETECTION_MANAGER._ReportInterval + +
    DETECTION_MANAGER._ScheduleDelayTime + +
    + +

    Type FAC_REPORTING

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FAC_REPORTING.ClassName + +
    FAC_REPORTING.Detection +

    The DETECTION_BASE object that is used to report the detected objects.

    +
    FAC_REPORTING:GetDetectedItemsText(DetectedSet) +

    Creates a string of the detected items in a Detection.

    +
    FAC_REPORTING:New(SetGroup, Detection) +

    FAC_REPORTING constructor.

    +
    FAC_REPORTING:ProcessDetected(Group, Detection) +

    Reports the detected items to the Set#SET_GROUP.

    +
    FAC_REPORTING.SetGroup +

    The groups to which the FAC will report to.

    +
    + +

    Type TASK_DISPATCHER

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TASK_DISPATCHER.ClassName + +
    TASK_DISPATCHER.CommandCenter + +
    TASK_DISPATCHER.Detection +

    The DETECTION_BASE object that is used to report the detected objects.

    +
    TASK_DISPATCHER:EvaluateTaskCAS(Mission, DetectedArea) +

    Creates a CAS task when there are targets for it.

    +
    TASK_DISPATCHER:EvaluateTaskSEAD(Mission, DetectedArea) +

    Creates a SEAD task when there are targets for it.

    +
    TASK_DISPATCHER:GetDetectedItemsText(DetectedSet) +

    Creates a string of the detected items in a Detection.

    +
    TASK_DISPATCHER.Mission + +
    TASK_DISPATCHER:New(SetGroup, Detection, Mission, CommandCenter) +

    TASK_DISPATCHER constructor.

    +
    TASK_DISPATCHER:ProcessDetected(Group, Detection, TaskGroup) +

    Assigns tasks in relation to the detected items to the Set#SET_GROUP.

    +
    TASK_DISPATCHER.SetGroup +

    The groups to which the FAC will report to.

    +
    + +

    Global(s)

    +
    +
    + + #DETECTION_MANAGER + +DETECTION_MANAGER + +
    +
    + + + +
    +
    +
    +
    + + #FAC_REPORTING + +FAC_REPORTING + +
    +
    + + + +
    +
    +
    +
    + + #TASK_DISPATCHER + +TASK_DISPATCHER + +
    +
    + + + +
    +
    +

    Type DetectionManager

    + +

    Type DETECTION_MANAGER

    + +

    DETECTION_MANAGER class.

    + +

    Field(s)

    +
    +
    + + #string + +DETECTION_MANAGER.ClassName + +
    +
    + + + +
    +
    +
    +
    + + Detection#DETECTION_BASE + +DETECTION_MANAGER.Detection + +
    +
    + +

    The DETECTION_BASE object that is used to report the detected objects.

    + +
    +
    +
    +
    + + + +DETECTION_MANAGER.FacScheduler + +
    +
    + + + +
    +
    +
    +
    + + +DETECTION_MANAGER:GetReportDisplayTime() + +
    +
    + +

    Get the reporting message display time.

    + +

    Return value

    + +

    #number: +ReportDisplayTime The display time in seconds when a report needs to be done.

    + +
    +
    +
    +
    + + +DETECTION_MANAGER:New(SetGroup, Detection) + +
    +
    + +

    FAC constructor.

    + +

    Parameters

    + +

    Return value

    + +

    #DETECTION_MANAGER: +self

    + +
    +
    +
    +
    + + +DETECTION_MANAGER:ReportDetected(Detection) + +
    +
    + +

    Reports the detected items to the Set#SET_GROUP.

    + +

    Parameter

    + +

    Return value

    + +

    #DETECTION_MANAGER: +self

    + +
    +
    +
    +
    + + +DETECTION_MANAGER:Schedule(DelayTime, ReportInterval) + +
    +
    + +

    Schedule the FAC reporting.

    + +

    Parameters

    +
      +
    • + +

      #number DelayTime : +The delay in seconds to wait the reporting.

      + +
    • +
    • + +

      #number ReportInterval : +The repeat interval in seconds for the reporting to happen repeatedly.

      + +
    • +
    +

    Return value

    + +

    #DETECTION_MANAGER: +self

    + +
    +
    +
    +
    + + Set#SET_GROUP + +DETECTION_MANAGER.SetGroup + +
    +
    + +

    The groups to which the FAC will report to.

    + +
    +
    +
    +
    + + +DETECTION_MANAGER:SetReportDisplayTime(ReportDisplayTime) + +
    +
    + +

    Set the reporting message display time.

    + +

    Parameter

    +
      +
    • + +

      #number ReportDisplayTime : +The display time in seconds when a report needs to be done.

      + +
    • +
    +

    Return value

    + +

    #DETECTION_MANAGER: +self

    + +
    +
    +
    +
    + + +DETECTION_MANAGER:SetReportInterval(ReportInterval) + +
    +
    + +

    Set the reporting time interval.

    + +

    Parameter

    +
      +
    • + +

      #number ReportInterval : +The interval in seconds when a report needs to be done.

      + +
    • +
    +

    Return value

    + +

    #DETECTION_MANAGER: +self

    + +
    +
    +
    +
    + + +DETECTION_MANAGER:_FacScheduler(SchedulerName) + +
    +
    + +

    Report the detected Unit#UNITs detected within the Detection#DETECTION_BASE object to the Set#SET_GROUPs.

    + +

    Parameter

    +
      +
    • + +

      SchedulerName :

      + +
    • +
    +
    +
    +
    +
    + + + +DETECTION_MANAGER._ReportDisplayTime + +
    +
    + + + +
    +
    +
    +
    + + + +DETECTION_MANAGER._ReportInterval + +
    +
    + + + +
    +
    +
    +
    + + + +DETECTION_MANAGER._ScheduleDelayTime + +
    +
    + + + +
    +
    + +

    Type FAC_REPORTING

    + +

    FAC_REPORTING class.

    + +

    Field(s)

    +
    +
    + + #string + +FAC_REPORTING.ClassName + +
    +
    + + + +
    +
    +
    +
    + + Detection#DETECTION_BASE + +FAC_REPORTING.Detection + +
    +
    + +

    The DETECTION_BASE object that is used to report the detected objects.

    + +
    +
    +
    +
    + + +FAC_REPORTING:GetDetectedItemsText(DetectedSet) + +
    +
    + +

    Creates a string of the detected items in a Detection.

    + +

    Parameter

    + +

    Return value

    + +

    #DETECTION_MANAGER: +self

    + +
    +
    +
    +
    + + +FAC_REPORTING:New(SetGroup, Detection) + +
    +
    + +

    FAC_REPORTING constructor.

    + +

    Parameters

    + +

    Return value

    + +

    #FAC_REPORTING: +self

    + +
    +
    +
    +
    + + +FAC_REPORTING:ProcessDetected(Group, Detection) + +
    +
    + +

    Reports the detected items to the Set#SET_GROUP.

    + +

    Parameters

    + +

    Return value

    + +

    #boolean: +Return true if you want the reporting to continue... false will cancel the reporting loop.

    + +
    +
    +
    +
    + + Set#SET_GROUP + +FAC_REPORTING.SetGroup + +
    +
    + +

    The groups to which the FAC will report to.

    + +
    +
    + +

    Type TASK_DISPATCHER

    + +

    TASK_DISPATCHER class.

    + +

    Field(s)

    +
    +
    + + #string + +TASK_DISPATCHER.ClassName + +
    +
    + + + +
    +
    +
    +
    + + Group#GROUP + +TASK_DISPATCHER.CommandCenter + +
    +
    + + + +
    +
    +
    +
    + + Detection#DETECTION_BASE + +TASK_DISPATCHER.Detection + +
    +
    + +

    The DETECTION_BASE object that is used to report the detected objects.

    + +
    +
    +
    +
    + + +TASK_DISPATCHER:EvaluateTaskCAS(Mission, DetectedArea) + +
    +
    + +

    Creates a CAS task when there are targets for it.

    + +

    Parameters

    + +

    Return value

    + +

    #string: +Message explaining which task was added.

    + +
    +
    +
    +
    + + +TASK_DISPATCHER:EvaluateTaskSEAD(Mission, DetectedArea) + +
    +
    + +

    Creates a SEAD task when there are targets for it.

    + +

    Parameters

    + +

    Return value

    + +

    #string: +Message explaining which task was added.

    + +
    +
    +
    +
    + + +TASK_DISPATCHER:GetDetectedItemsText(DetectedSet) + +
    +
    + +

    Creates a string of the detected items in a Detection.

    + +

    Parameter

    + +

    Return value

    + +

    #string: +The text

    + +
    +
    +
    +
    + + Mission#MISSION + +TASK_DISPATCHER.Mission + +
    +
    + + + +
    +
    +
    +
    + + +TASK_DISPATCHER:New(SetGroup, Detection, Mission, CommandCenter) + +
    +
    + +

    TASK_DISPATCHER constructor.

    + +

    Parameters

    + +

    Return value

    + +

    #TASK_DISPATCHER: +self

    + +
    +
    +
    +
    + + +TASK_DISPATCHER:ProcessDetected(Group, Detection, TaskGroup) + +
    +
    + +

    Assigns tasks in relation to the detected items to the Set#SET_GROUP.

    + +

    Parameters

    + +

    Return value

    + +

    #boolean: +Return true if you want the task assigning to continue... false will cancel the loop.

    + +
    +
    +
    +
    + + Set#SET_GROUP + +TASK_DISPATCHER.SetGroup + +
    +
    + +

    The groups to which the FAC will report to.

    + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/Escort.html b/Moose Training/Documentation/Escort.html index 1c20a6ad6..a92adb6a0 100644 --- a/Moose Training/Documentation/Escort.html +++ b/Moose Training/Documentation/Escort.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Event.html b/Moose Training/Documentation/Event.html index 294e68dfd..ab526e990 100644 --- a/Moose Training/Documentation/Event.html +++ b/Moose Training/Documentation/Event.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -242,6 +252,12 @@ EVENT:OnLandForUnit(EventDCSUnitName, EventFunction, EventSelf)

    Set a new listener for an SEVENTLAND event.

    + + + + EVENT:OnPilotDead(EventFunction, EventSelf) + +

    Set a new listener for an SEVENTPILOT_DEAD event.

    @@ -1273,6 +1289,38 @@ The self instance of the class for which the event is.

    #EVENT:

    + + +
    +
    + + +EVENT:OnPilotDead(EventFunction, EventSelf) + +
    +
    + +

    Set a new listener for an SEVENTPILOT_DEAD event.

    + +

    Parameters

    +
      +
    • + +

      #function EventFunction : +The function to be called when the event occurs for the unit.

      + +
    • +
    • + +

      Base#BASE EventSelf :

      + +
    • +
    +

    Return value

    + +

    #EVENT:

    + +
    diff --git a/Moose Training/Documentation/GOHOMETASK.html b/Moose Training/Documentation/GOHOMETASK.html index ab5a3150a..c60a07581 100644 --- a/Moose Training/Documentation/GOHOMETASK.html +++ b/Moose Training/Documentation/GOHOMETASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Group.html b/Moose Training/Documentation/Group.html index 42621b18c..bef29759d 100644 --- a/Moose Training/Documentation/Group.html +++ b/Moose Training/Documentation/Group.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -370,15 +380,15 @@ Use the following Zone validation methods on the group:

    - GROUP:GetMinHeight() + GROUP:GetMessage(Message, Duration) -

    Returns the current minimum height of the group.

    +

    Returns a message for a coalition or a client.

    - GROUP:GetPointVec2() + GROUP:GetMinHeight() -

    Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group.

    +

    Returns the current minimum height of the group.

    @@ -427,6 +437,12 @@ Use the following Zone validation methods on the group:

    GROUP:GetUnits()

    Returns the UNITs wrappers of the DCS Units of the DCS Group.

    + + + + GROUP:GetVec2() + +

    Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group.

    @@ -492,7 +508,7 @@ Use the following Zone validation methods on the group:

    GROUP:Message(Message, Duration) -

    Returns a message for a coalition or a client.

    +

    Send a message to the players in the Group.

    @@ -511,6 +527,12 @@ Use the following Zone validation methods on the group:

    GROUP:MessageToClient(Message, Duration, Client)

    Send a message to a client.

    + + + + GROUP:MessageToGroup(Message, Duration, MessageGroup, MsgGroup) + +

    Send a message to a Group.

    @@ -961,6 +983,39 @@ Maximum height found.

    #number: Maximum velocity found.

    + +
    +
    +
    + + +GROUP:GetMessage(Message, Duration) + +
    +
    + +

    Returns a message for a coalition or a client.

    + +

    Parameters

    +
      +
    • + +

      #string Message : +The message text

      + +
    • +
    • + +

      DCSTypes#Duration Duration : +The duration of the message.

      + +
    • +
    +

    Return value

    + +

    Message#MESSAGE:

    + +
    @@ -987,24 +1042,6 @@ Minimum height found.

    - -GROUP:GetPointVec2() - -
    -
    - -

    Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group.

    - -

    Return value

    - -

    DCSTypes#Vec2: -Current Vec2 point of the first DCS Unit of the DCS Group.

    - -
    -
    -
    -
    - GROUP:GetPointVec3() @@ -1159,6 +1196,24 @@ The UNIT wrapper class.

    #table: The UNITs wrappers.

    + +
    +
    +
    + + +GROUP:GetVec2() + +
    +
    + +

    Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group.

    + +

    Return value

    + +

    DCSTypes#Vec2: +Current Vec2 point of the first DCS Unit of the DCS Group.

    +
    @@ -1379,7 +1434,10 @@ true if DCS Group contains Ships.

    -

    Returns a message for a coalition or a client.

    +

    Send a message to the players in the Group.

    + + +

    The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.

    Parameters

      @@ -1396,11 +1454,6 @@ The duration of the message.

    -

    Return value

    - -

    Message#MESSAGE:

    - -
    @@ -1505,6 +1558,48 @@ The client object receiving the message.

    + +GROUP:MessageToGroup(Message, Duration, MessageGroup, MsgGroup) + +
    +
    + +

    Send a message to a Group.

    + + +

    The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.

    + +

    Parameters

    +
      +
    • + +

      #string Message : +The message text

      + +
    • +
    • + +

      DCSTypes#Duration Duration : +The duration of the message.

      + +
    • +
    • + +

      Group#GROUP MessageGroup : +The GROUP object receiving the message.

      + +
    • +
    • + +

      MsgGroup :

      + +
    • +
    +
    +
    +
    +
    + GROUP:MessageToRed(Message, Duration) diff --git a/Moose Training/Documentation/Identifiable.html b/Moose Training/Documentation/Identifiable.html index 0f3cc9c40..4382e0d4f 100644 --- a/Moose Training/Documentation/Identifiable.html +++ b/Moose Training/Documentation/Identifiable.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/MISSION.html b/Moose Training/Documentation/MISSION.html index f349b86c9..9413ac912 100644 --- a/Moose Training/Documentation/MISSION.html +++ b/Moose Training/Documentation/MISSION.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -123,7 +133,19 @@ A CLIENT needs to be registered within the MISSION:AddTask(Task, TaskNumber) + MISSION:AddScoring(Scoring) + +

    Add a scoring to the mission.

    + + + + MISSION:AddTask(Task) + +

    Register a Task to be completed within the Mission.

    + + + + MISSION.AssignTaskToGroup(MenuParam) @@ -132,12 +154,24 @@ A CLIENT needs to be registered within the MISSION.ClassName + + + + MISSION:ClearMissionMenu() + +

    Clears the mission menu for the coalition.

    MISSION:Completed()

    Set a Mission to completed.

    + + + + MISSION:CreateTaskMenus(TaskGroup) + +

    Fill mission menu for the Group.

    @@ -156,6 +190,24 @@ A CLIENT needs to be registered within the MISSION.FindClient(CLIENT, self, ClientName)

    Find a CLIENT object within the MISSION by its ClientName.

    + + + + MISSION:GetMissionMenu() + +

    Gets the mission menu for the coalition.

    + + + + MISSION:GetName() + +

    Gets the mission name.

    + + + + MISSION:GetScoring() + +

    Get the scoring object of a mission.

    @@ -216,6 +268,18 @@ A CLIENT needs to be registered within the MISSION.MissionCoalition + + + + MISSION.MissionMenu + + + + + + MISSION.MissionPriority + + @@ -261,7 +325,7 @@ A CLIENT needs to be registered within the MISSION.New(string, string, string, string, self, MissionName, MissionPriority, MissionBriefing, MissionCoalition) + MISSION:New(MissionName, MissionPriority, MissionBriefing, MissionCoalition)

    This is the main MISSION declaration method.

    @@ -300,12 +364,48 @@ A CLIENT needs to be registered within the MISSION.SUCCESS + + + + MISSION.Scoring + + + + + + MISSION:SetMissionMenu() + +

    Sets the mission menu for the coalition.

    MISSION:StatusToClients()

    Send the status of the MISSION to all Clients.

    + + + + MISSION.TaskCategoryMenus + + + + + + MISSION.TaskMenus + + + + + + MISSION.TaskTypeMenus + + + + + + MISSION.Tasks + + @@ -324,12 +424,6 @@ A CLIENT needs to be registered within the MISSION._GoalTasks - - - - MISSION._Tasks - - @@ -630,24 +724,76 @@ GoalFunction is the function defined by the mission designer to evaluate whether
    + +MISSION:AddScoring(Scoring) + +
    +
    + +

    Add a scoring to the mission.

    + +

    Parameter

    +
      +
    • + +

      Scoring :

      + +
    • +
    +

    Return value

    + +

    #MISSION: +self

    + +
    +
    +
    +
    + -MISSION:AddTask(Task, TaskNumber) +MISSION:AddTask(Task) + +
    +
    + +

    Register a Task to be completed within the Mission.

    + + +

    Note that there can be multiple Tasks registered to be completed. +Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached.

    + +

    Parameter

    + +

    Return value

    + +

    Task#TASK_BASE: +The task added.

    + +
    +
    +
    +
    + + +MISSION.AssignTaskToGroup(MenuParam)
    -

    Parameters

    +

    Parameter

    • -

      Task :

      - -
    • -
    • - -

      TaskNumber :

      +

      MenuParam :

    @@ -665,6 +811,24 @@ GoalFunction is the function defined by the mission designer to evaluate whether +
    +
    +
    +
    + + +MISSION:ClearMissionMenu() + +
    +
    + +

    Clears the mission menu for the coalition.

    + +

    Return value

    + +

    #MISSION: +self

    +
    @@ -678,6 +842,32 @@ GoalFunction is the function defined by the mission designer to evaluate whether

    Set a Mission to completed.

    + +
    +
    +
    + + +MISSION:CreateTaskMenus(TaskGroup) + +
    +
    + +

    Fill mission menu for the Group.

    + +

    Parameter

    +
      +
    • + +

      TaskGroup :

      + +
    • +
    +

    Return value

    + +

    #MISSION: +self

    +
    @@ -751,6 +941,60 @@ local BomberClient = Mission:FindClient( "Bomber" )
    + +MISSION:GetMissionMenu() + +
    +
    + +

    Gets the mission menu for the coalition.

    + +

    Return value

    + +

    Menu#MENU_COALITION: +self

    + +
    +
    +
    +
    + + +MISSION:GetName() + +
    +
    + +

    Gets the mission name.

    + +

    Return value

    + +

    #MISSION: +self

    + +
    +
    +
    +
    + + +MISSION:GetScoring() + +
    +
    + +

    Get the scoring object of a mission.

    + +

    Return value

    + +

    #SCORING: +Scoring

    + +
    +
    +
    +
    + MISSION:GetTask(TaskNumber) @@ -904,7 +1148,7 @@ env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" )
    - #string + MISSION.MissionCoalition @@ -913,6 +1157,34 @@ env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) + +
    +
    +
    + + Menu#MENU_COALITION + +MISSION.MissionMenu + +
    +
    + + + +
    +
    +
    +
    + + + +MISSION.MissionPriority + +
    +
    + + +
    @@ -1002,7 +1274,7 @@ env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" )
    - #string + MISSION.Name @@ -1017,7 +1289,7 @@ env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" )
    -MISSION.New(string, string, string, string, self, MissionName, MissionPriority, MissionBriefing, MissionCoalition) +MISSION:New(MissionName, MissionPriority, MissionBriefing, MissionCoalition)
    @@ -1031,70 +1303,33 @@ env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" )
    • -

      string : -MissionName is the name of the mission. This name will be used to reference the status of each mission by the players.

      +

      #string MissionName : +is the name of the mission. This name will be used to reference the status of each mission by the players.

    • -

      string : -MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field.

      +

      #string MissionPriority : +is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field.

    • -

      string : -MissionBriefing is a string indicating the mission briefing to be shown when a player joins a CLIENT.

      +

      #string MissionBriefing : +is a string indicating the mission briefing to be shown when a player joins a CLIENT.

    • -

      string : -MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      MissionName :

      - -
    • -
    • - -

      MissionPriority :

      - -
    • -
    • - -

      MissionBriefing :

      - -
    • -
    • - -

      MissionCoalition :

      +

      DCSCoalitionObject#coalition MissionCoalition : +is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"...

    Return value

    - -

    MISSION

    - -

    Usage:

    -
    
    --- Declare a few missions.
    -local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' )
    -local Mission = MISSIONSCHEDULER.AddMission( 'Patriots', 'Primary', 'Our intelligence reports that 3 Patriot SAM defense batteries are located near Ruisi, Kvarhiti and Gori.', 'Russia'  )
    -local Mission = MISSIONSCHEDULER.AddMission( 'Package Delivery', 'Operational', 'In order to be in full control of the situation, we need you to deliver a very important package at a secret location. Fly undetected through the NATO defenses and deliver the secret package. The secret agent is located at waypoint 4.', 'Russia'  )
    -local Mission = MISSIONSCHEDULER.AddMission( 'Rescue General', 'Tactical', 'Our intelligence has received a remote signal behind Gori. We believe it is a very important Russian General that was captured by Georgia. Go out there and rescue him! Ensure you stay out of the battle zone, keep south. Waypoint 4 is the location of our Russian General.', 'Russia'  )
    -local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' )
    -local Mission = MISSIONSCHEDULER.AddMission( 'SA-6 SAMs', 'Primary', 'Our intelligence reports that 3 SA-6 SAM defense batteries are located near Didmukha, Khetagurov and Berula. Eliminate the Russian SAMs.', 'NATO'  )
    -local Mission = MISSIONSCHEDULER.AddMission( 'NATO Sling Load', 'Operational', 'Fly to the cargo pickup zone at Dzegvi or Kaspi, and sling the cargo to Soganlug airbase.', 'NATO' )
    -local Mission = MISSIONSCHEDULER.AddMission( 'Rescue secret agent', 'Tactical', 'In order to be in full control of the situation, we need you to rescue a secret agent from the woods behind enemy lines. Avoid the Russian defenses and rescue the agent. Keep south until Khasuri, and keep your eyes open for any SAM presence. The agent is located at waypoint 4 on your kneeboard.', 'NATO'  )
    +

    #MISSION: +self

    @@ -1179,6 +1414,38 @@ local Mission = MISSIONSCHEDULER.AddMission( 'Rescue secret agent', 'Tactical', + +
    +
    +
    + + + +MISSION.Scoring + +
    +
    + + + +
    +
    +
    +
    + + +MISSION:SetMissionMenu() + +
    +
    + +

    Sets the mission menu for the coalition.

    + +

    Return value

    + +

    #MISSION: +self

    +
    @@ -1192,6 +1459,62 @@ local Mission = MISSIONSCHEDULER.AddMission( 'Rescue secret agent', 'Tactical',

    Send the status of the MISSION to all Clients.

    + +
    +
    +
    + + + +MISSION.TaskCategoryMenus + +
    +
    + + + +
    +
    +
    +
    + + + +MISSION.TaskMenus + +
    +
    + + + +
    +
    +
    +
    + + + +MISSION.TaskTypeMenus + +
    +
    + + + +
    +
    +
    +
    + + + +MISSION.Tasks + +
    +
    + + +
    @@ -1234,20 +1557,6 @@ local Mission = MISSIONSCHEDULER.AddMission( 'Rescue secret agent', 'Tactical', - -
    -
    -
    - - - -MISSION._Tasks - -
    -
    - - -
    @@ -1649,6 +1958,8 @@ MISSIONSCHEDULER:RemoveMission( 'Russia Transport Troops SA-6' )

    Type MISSIONSCHEDULER.MISSIONS

    +

    Type SCORING

    +
    diff --git a/Moose Training/Documentation/MOVEMENT.html b/Moose Training/Documentation/MOVEMENT.html index 90ec5605b..4ac9e7079 100644 --- a/Moose Training/Documentation/MOVEMENT.html +++ b/Moose Training/Documentation/MOVEMENT.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Menu.html b/Moose Training/Documentation/Menu.html index cc6276f86..18b385e6e 100644 --- a/Moose Training/Documentation/Menu.html +++ b/Moose Training/Documentation/Menu.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -119,6 +129,18 @@ MENU_COALITION_COMMAND + + + + MENU_GROUP + + + + + + MENU_GROUP_COMMAND + + @@ -249,7 +271,7 @@ - MENU_COALITION:New(MenuCoalition, MenuText, ParentMenu) + MENU_COALITION:New(Coalition, MenuText, ParentMenu)

    Creates a new coalition menu item

    @@ -286,6 +308,56 @@ MENU_COALITION_COMMAND:Remove()

    Removes a radio command item for a coalition

    + + + + +

    Type MENU_GROUP

    + + + + + + + + + + + + + + + + + +
    MENU_GROUP.ClassName + +
    MENU_GROUP:New(MenuGroup, MenuText, ParentMenu) +

    Creates a new menu item for a group

    +
    MENU_GROUP:Remove() +

    Removes the sub menus recursively of this MENU_GROUP.

    +
    MENU_GROUP:RemoveSubMenus() +

    Removes the sub menus recursively of this MENU_GROUP.

    +
    + +

    Type MENU_GROUP_COMMAND

    + + + + + + + + + + + +
    MENU_GROUP_COMMAND.ClassName + +
    MENU_GROUP_COMMAND:New(MenuGroup, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument) +

    Creates a new radio command item for a group

    +
    MENU_GROUP_COMMAND:Remove() +
    @@ -389,6 +461,34 @@ + + +
    +
    + + #MENU_GROUP + +MENU_GROUP + +
    +
    + + + +
    +
    +
    +
    + + #MENU_GROUP_COMMAND + +MENU_GROUP_COMMAND + +
    +
    + + +
    @@ -777,7 +877,7 @@ self

    -MENU_COALITION:New(MenuCoalition, MenuText, ParentMenu) +MENU_COALITION:New(Coalition, MenuText, ParentMenu)
    @@ -788,7 +888,7 @@ self

    +
    + +

    Type MENU_GROUP

    + +

    The MENU_GROUP class

    + +

    Field(s)

    +
    +
    + + #string + +MENU_GROUP.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +MENU_GROUP:New(MenuGroup, MenuText, ParentMenu) + +
    +
    + +

    Creates a new menu item for a group

    + +

    Parameters

    +
      +
    • + +

      Group#GROUP MenuGroup : +The Group owning the menu.

      + +
    • +
    • + +

      #string MenuText : +The text for the menu.

      + +
    • +
    • + +

      #table ParentMenu : +The parent menu.

      + +
    • +
    +

    Return value

    + +

    #MENU_GROUP: +self

    + +
    +
    +
    +
    + + +MENU_GROUP:Remove() + +
    +
    + +

    Removes the sub menus recursively of this MENU_GROUP.

    + +

    Return value

    + +

    #MENU_GROUP: +self

    + +
    +
    +
    +
    + + +MENU_GROUP:RemoveSubMenus() + +
    +
    + +

    Removes the sub menus recursively of this MENU_GROUP.

    + +

    Return value

    + +

    #MENU_GROUP: +self

    + +
    +
    + +

    Type MENU_GROUP_COMMAND

    + +

    The MENUGROUPCOMMAND class

    + +

    Field(s)

    +
    +
    + + #string + +MENU_GROUP_COMMAND.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +MENU_GROUP_COMMAND:New(MenuGroup, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument) + +
    +
    + +

    Creates a new radio command item for a group

    + +

    Parameters

    +
      +
    • + +

      Group#GROUP MenuGroup : +The Group owning the menu.

      + +
    • +
    • + +

      MenuText : +The text for the menu.

      + +
    • +
    • + +

      ParentMenu : +The parent menu.

      + +
    • +
    • + +

      CommandMenuFunction : +A function that is called when the menu key is pressed.

      + +
    • +
    • + +

      CommandMenuArgument : +An argument for the function.

      + +
    • +
    +

    Return value

    + +

    Menu#MENUGROUPCOMMAND: +self

    + +
    +
    +
    +
    + + +MENU_GROUP_COMMAND:Remove() + +
    +
    + + +
    diff --git a/Moose Training/Documentation/Message.html b/Moose Training/Documentation/Message.html index 8b494fb03..419590a88 100644 --- a/Moose Training/Documentation/Message.html +++ b/Moose Training/Documentation/Message.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -161,6 +171,12 @@ To send messages, you need to use the To functions.

    MESSAGE:ToCoalition(CoalitionSide)

    Sends a MESSAGE to a Coalition.

    + + + + MESSAGE:ToGroup(Group) + +

    Sends a MESSAGE to a Group.

    @@ -422,6 +438,33 @@ or MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) MessageRED:ToCoalition( coalition.side.RED ) + + +
    +
    + + +MESSAGE:ToGroup(Group) + +
    +
    + +

    Sends a MESSAGE to a Group.

    + +

    Parameter

    + +

    Return value

    + +

    #MESSAGE:

    + +
    diff --git a/Moose Training/Documentation/MissileTrainer.html b/Moose Training/Documentation/MissileTrainer.html index 03a1fd552..010390d91 100644 --- a/Moose Training/Documentation/MissileTrainer.html +++ b/Moose Training/Documentation/MissileTrainer.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/NOTASK.html b/Moose Training/Documentation/NOTASK.html index f765ed02a..462126c29 100644 --- a/Moose Training/Documentation/NOTASK.html +++ b/Moose Training/Documentation/NOTASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Object.html b/Moose Training/Documentation/Object.html index 39b818c12..a8777c397 100644 --- a/Moose Training/Documentation/Object.html +++ b/Moose Training/Documentation/Object.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/PICKUPTASK.html b/Moose Training/Documentation/PICKUPTASK.html index 2c26a4668..cc43a0f2d 100644 --- a/Moose Training/Documentation/PICKUPTASK.html +++ b/Moose Training/Documentation/PICKUPTASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/PatrolZone.html b/Moose Training/Documentation/PatrolZone.html index 4b827c585..8758f52ff 100644 --- a/Moose Training/Documentation/PatrolZone.html +++ b/Moose Training/Documentation/PatrolZone.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Point.html b/Moose Training/Documentation/Point.html index 3df9956bc..247d5aa00 100644 --- a/Moose Training/Documentation/Point.html +++ b/Moose Training/Documentation/Point.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -88,6 +98,9 @@

    1) Point#POINT_VEC3 class, extends Base#BASE

    The Point#POINT_VEC3 class defines a 3D point in the simulator.

    +

    Important Note: Most of the functions in this section were taken from MIST, and reworked to OO concepts. +In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums.

    +

    1.1) POINT_VEC3 constructor

    A new POINT instance can be created with:

    @@ -141,6 +154,12 @@ POINT_VEC2:DistanceFromVec2(Vec2Reference)

    Calculate the distance from a reference DCSTypes#Vec2.

    + + + + POINT_VEC2:GetAltitudeText() + +

    Return no text for the altitude of the POINT_VEC2.

    @@ -199,12 +218,96 @@ POINT_VEC3:FlareYellow((, Azimuth)

    Flare the POINT_VEC3 Yellow.

    + + + + POINT_VEC3:Get2DDistance(TargetPointVec3) + +

    Return the 2D distance in meters between the target POINTVEC3 and the POINTVEC3.

    + + + + POINT_VEC3:Get3DDistance(TargetPointVec3) + +

    Return the 3D distance in meters between the target POINTVEC3 and the POINTVEC3.

    + + + + POINT_VEC3:GetAltitudeText() + +

    Return the altitude text of the POINT_VEC3.

    + + + + POINT_VEC3:GetBRText(TargetPointVec3) + +

    Return a BR string from a POINTVEC3 to the POINTVEC3.

    + + + + POINT_VEC3:GetDirectionRadians(DirectionVec3) + +

    Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format.

    + + + + POINT_VEC3:GetDirectionVec3(TargetPointVec3) + +

    Return a direction vector Vec3 from POINTVEC3 to the POINTVEC3.

    + + + + POINT_VEC3:GetNorthCorrectionRadians() + +

    Get a correction in radians of the real magnetic north of the POINT_VEC3.

    + + + + POINT_VEC3:GetVec3() + +

    Return the coordinates of the POINT_VEC3 in Vec3 format.

    + + + + POINT_VEC3:GetX() + +

    Return the x coordinate of the POINT_VEC3.

    + + + + POINT_VEC3:GetY() + +

    Return the y coordinate of the POINT_VEC3.

    + + + + POINT_VEC3:GetZ() + +

    Return the z coordinate of the POINT_VEC3.

    + + + + POINT_VEC3:IsMetric() + +

    Gets if the POINT_VEC3 is metric or NM.

    + + + + POINT_VEC3.Metric + + POINT_VEC3:New(x, y, z)

    Create a new POINT_VEC3 object.

    + + + + POINT_VEC3.PointVec3 + + @@ -229,6 +332,12 @@ POINT_VEC3.RoutePointType + + + + POINT_VEC3:SetMetric(Metric) + +

    Sets the POINT_VEC3 metric or NM.

    @@ -271,6 +380,12 @@ POINT_VEC3:SmokeWhite()

    Smoke the POINT_VEC3 White.

    + + + + POINT_VEC3:ToStringBR(AngleRadians, Distance) + +

    Provides a Bearing / Range string

    @@ -474,6 +589,24 @@ The distance from the reference DCSTypes#Vec2
    + +POINT_VEC2:GetAltitudeText() + +
    +
    + +

    Return no text for the altitude of the POINT_VEC2.

    + +

    Return value

    + +

    #string: +Empty string.

    + +
    +
    +
    +
    + POINT_VEC2:New(x, y, LandHeightAdd) @@ -696,6 +829,281 @@ ptional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + +
    +
    +
    + + +POINT_VEC3:Get2DDistance(TargetPointVec3) + +
    +
    + +

    Return the 2D distance in meters between the target POINTVEC3 and the POINTVEC3.

    + +

    Parameter

    +
      +
    • + +

      #POINT_VEC3 TargetPointVec3 : +The target PointVec3.

      + +
    • +
    +

    Return value

    + +

    DCSTypes#Distance: +Distance The distance in meters.

    + +
    +
    +
    +
    + + +POINT_VEC3:Get3DDistance(TargetPointVec3) + +
    +
    + +

    Return the 3D distance in meters between the target POINTVEC3 and the POINTVEC3.

    + +

    Parameter

    +
      +
    • + +

      #POINT_VEC3 TargetPointVec3 : +The target PointVec3.

      + +
    • +
    +

    Return value

    + +

    DCSTypes#Distance: +Distance The distance in meters.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetAltitudeText() + +
    +
    + +

    Return the altitude text of the POINT_VEC3.

    + +

    Return value

    + +

    #string: +Altitude text.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetBRText(TargetPointVec3) + +
    +
    + +

    Return a BR string from a POINTVEC3 to the POINTVEC3.

    + +

    Parameter

    +
      +
    • + +

      #POINT_VEC3 TargetPointVec3 : +The target PointVec3.

      + +
    • +
    +

    Return value

    + +

    #string: +The BR text.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetDirectionRadians(DirectionVec3) + +
    +
    + +

    Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format.

    + +

    Parameter

    +
      +
    • + +

      DCSTypes#Vec3 DirectionVec3 : +The direction vector in Vec3 format.

      + +
    • +
    +

    Return value

    + +

    #number: +DirectionRadians The direction in radians.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetDirectionVec3(TargetPointVec3) + +
    +
    + +

    Return a direction vector Vec3 from POINTVEC3 to the POINTVEC3.

    + +

    Parameter

    +
      +
    • + +

      #POINT_VEC3 TargetPointVec3 : +The target PointVec3.

      + +
    • +
    +

    Return value

    + +

    DCSTypes#Vec3: +DirectionVec3 The direction vector in Vec3 format.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetNorthCorrectionRadians() + +
    +
    + +

    Get a correction in radians of the real magnetic north of the POINT_VEC3.

    + +

    Return value

    + +

    #number: +CorrectionRadians The correction in radians.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetVec3() + +
    +
    + +

    Return the coordinates of the POINT_VEC3 in Vec3 format.

    + +

    Return value

    + +

    DCSTypes#Vec3: +The Vec3 coodinate.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetX() + +
    +
    + +

    Return the x coordinate of the POINT_VEC3.

    + +

    Return value

    + +

    #number: +The x coodinate.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetY() + +
    +
    + +

    Return the y coordinate of the POINT_VEC3.

    + +

    Return value

    + +

    #number: +The y coodinate.

    + +
    +
    +
    +
    + + +POINT_VEC3:GetZ() + +
    +
    + +

    Return the z coordinate of the POINT_VEC3.

    + +

    Return value

    + +

    #number: +The z coodinate.

    + +
    +
    +
    +
    + + +POINT_VEC3:IsMetric() + +
    +
    + +

    Gets if the POINT_VEC3 is metric or NM.

    + +

    Return value

    + +

    #boolean: +Metric true means metric, false means NM.

    + +
    +
    +
    +
    + + + +POINT_VEC3.Metric + +
    +
    + + +
    @@ -735,6 +1143,20 @@ The z coordinate of the Vec3 point, pointing to the Right.

    Point#POINT_VEC3: self

    + +
    +
    +
    + + DCSTypes#Vec3 + +POINT_VEC3.PointVec3 + +
    +
    + + +
    @@ -828,6 +1250,28 @@ The route point.

    + +
    +
    +
    + + +POINT_VEC3:SetMetric(Metric) + +
    +
    + +

    Sets the POINT_VEC3 metric or NM.

    + +

    Parameter

    +
      +
    • + +

      #boolean Metric : +true means metric, false means NM.

      + +
    • +
    @@ -928,6 +1372,39 @@ The route point.

    Smoke the POINT_VEC3 White.

    + +
    +
    +
    + + +POINT_VEC3:ToStringBR(AngleRadians, Distance) + +
    +
    + +

    Provides a Bearing / Range string

    + +

    Parameters

    +
      +
    • + +

      #number AngleRadians : +The angle in randians

      + +
    • +
    • + +

      #number Distance : +The distance

      + +
    • +
    +

    Return value

    + +

    #string: +The BR Text

    +
    diff --git a/Moose Training/Documentation/Positionable.html b/Moose Training/Documentation/Positionable.html index eef6b1fcc..6effc3f8f 100644 --- a/Moose Training/Documentation/Positionable.html +++ b/Moose Training/Documentation/Positionable.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -149,12 +159,6 @@ POSITIONABLE:GetHeading()

    Returns the DCS Positionable heading.

    - - - - POSITIONABLE:GetPointVec2() - -

    Returns the DCSTypes#Vec2 vector indicating the point in 2D of the DCS Positionable within the mission.

    @@ -167,6 +171,12 @@ POSITIONABLE:GetPositionVec3()

    Returns the DCSTypes#Position3 position vectors indicating the point and direction vectors in 3D of the DCS Positionable within the mission.

    + + + + POSITIONABLE:GetVec2() + +

    Returns the DCSTypes#Vec2 vector indicating the point in 2D of the DCS Positionable within the mission.

    @@ -305,34 +315,6 @@ The DCS Positionable heading

    - -POSITIONABLE:GetPointVec2() - -
    -
    - -

    Returns the DCSTypes#Vec2 vector indicating the point in 2D of the DCS Positionable within the mission.

    - -

    Return values

    -
      -
    1. - -

      DCSTypes#Vec2: -The 2D point vector of the DCS Positionable.

      - -
    2. -
    3. - -

      #nil: -The DCS Positionable is not existing or alive.

      - -
    4. -
    -
    -
    -
    -
    - POSITIONABLE:GetPointVec3() @@ -389,6 +371,34 @@ The DCS Positionable is not existing or alive.

    + +POSITIONABLE:GetVec2() + +
    +
    + +

    Returns the DCSTypes#Vec2 vector indicating the point in 2D of the DCS Positionable within the mission.

    + +

    Return values

    +
      +
    1. + +

      DCSTypes#Vec2: +The 2D point vector of the DCS Positionable.

      + +
    2. +
    3. + +

      #nil: +The DCS Positionable is not existing or alive.

      + +
    4. +
    +
    +
    +
    +
    + POSITIONABLE:GetVelocity() diff --git a/Moose Training/Documentation/Process.html b/Moose Training/Documentation/Process.html new file mode 100644 index 000000000..bae0c3b34 --- /dev/null +++ b/Moose Training/Documentation/Process.html @@ -0,0 +1,466 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Process

    + + + +

    Global(s)

    + + + + + +
    PROCESS + +
    +

    Type PROCESS

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PROCESS:AddScore(ProcessStatus, ScoreText, Score) +

    Adds a score for the PROCESS to be achieved.

    +
    PROCESS.AllowEvents + +
    PROCESS.ClassName + +
    PROCESS.Fsm + +
    PROCESS:New(ProcessName, Task, ProcessUnit) +

    Instantiates a new TASK Base.

    +
    PROCESS.NextEvent + +
    PROCESS:OnStateChange(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS.ProcessName + +
    PROCESS.ProcessScheduler + +
    PROCESS.ProcessUnit + +
    PROCESS.Scores + +
    PROCESS:StopEvents() + +
    PROCESS.Task + +
    + +

    Global(s)

    +
    +
    + + #PROCESS + +PROCESS + +
    +
    + + + +
    +
    +

    Type Process

    + +

    Type PROCESS

    + +

    The PROCESS class

    + +

    Field(s)

    +
    +
    + + +PROCESS:AddScore(ProcessStatus, ScoreText, Score) + +
    +
    + +

    Adds a score for the PROCESS to be achieved.

    + +

    Parameters

    +
      +
    • + +

      #string ProcessStatus : +is the status of the PROCESS when the score needs to be given.

      + +
    • +
    • + +

      #string ScoreText : +is a text describing the score that is given according the status.

      + +
    • +
    • + +

      #number Score : +is a number providing the score of the status.

      + +
    • +
    +

    Return value

    + +

    #PROCESS: +self

    + +
    +
    +
    +
    + + #boolean + +PROCESS.AllowEvents + +
    +
    + + + +
    +
    +
    +
    + + #string + +PROCESS.ClassName + +
    +
    + + + +
    +
    +
    +
    + + StateMachine#STATEMACHINE_TASK + +PROCESS.Fsm + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS:New(ProcessName, Task, ProcessUnit) + +
    +
    + +

    Instantiates a new TASK Base.

    + + +

    Should never be used. Interface Class.

    + +

    Parameters

    + +

    Return value

    + +

    #PROCESS: +self

    + +
    +
    +
    +
    + + +PROCESS.NextEvent + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS:OnStateChange(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + #string + +PROCESS.ProcessName + +
    +
    + + + +
    +
    +
    +
    + + Scheduler#SCHEDULER + +PROCESS.ProcessScheduler + +
    +
    + + + +
    +
    +
    +
    + + Unit#UNIT + +PROCESS.ProcessUnit + +
    +
    + + + +
    +
    +
    +
    + + + +PROCESS.Scores + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS:StopEvents() + +
    +
    + + + +
    +
    +
    +
    + + Task#TASK + +PROCESS.Task + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/Process_CAS.html b/Moose Training/Documentation/Process_CAS.html new file mode 100644 index 000000000..0cea09fae --- /dev/null +++ b/Moose Training/Documentation/Process_CAS.html @@ -0,0 +1,547 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Process_CAS

    + + + +

    Global(s)

    + + + + + +
    PROCESS_CAS + +
    +

    Type PROCESS_CAS

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PROCESS_CAS.ClassName + +
    PROCESS_CAS:EventDead(Event) + +
    PROCESS_CAS.Fsm + +
    PROCESS_CAS:New(Task, ProcessUnit, TargetSetUnit) +

    Creates a new CAS task.

    +
    PROCESS_CAS:OnDestroyed(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_CAS:OnHitTarget(Fsm, Event, From, To, Event) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_CAS:OnKilled(Fsm, Event, From, To, DCSEvent) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_CAS:OnMoreTargets(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_CAS:OnRestart(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_CAS:OnStart(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_CAS.ProcessUnit + +
    PROCESS_CAS.TargetSetUnit + +
    + +

    Global(s)

    +
    +
    + + #PROCESS_CAS + +PROCESS_CAS + +
    +
    + + + +
    +
    +

    Type Process_CAS

    + +

    Type PROCESS_CAS

    + +

    PROCESS_CAS class

    + +

    Field(s)

    +
    +
    + + #string + +PROCESS_CAS.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS_CAS:EventDead(Event) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + + + +PROCESS_CAS.Fsm + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS_CAS:New(Task, ProcessUnit, TargetSetUnit) + +
    +
    + +

    Creates a new CAS task.

    + +

    Parameters

    + +

    Return value

    + +

    #PROCESS_CAS: +self

    + +
    +
    +
    +
    + + +PROCESS_CAS:OnDestroyed(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_CAS:OnHitTarget(Fsm, Event, From, To, Event) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_CAS:OnKilled(Fsm, Event, From, To, DCSEvent) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_CAS:OnMoreTargets(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_CAS:OnRestart(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_CAS:OnStart(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + Unit#UNIT + +PROCESS_CAS.ProcessUnit + +
    +
    + + + +
    +
    +
    +
    + + Set#SET_UNIT + +PROCESS_CAS.TargetSetUnit + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/Process_SEAD.html b/Moose Training/Documentation/Process_SEAD.html new file mode 100644 index 000000000..7941d9006 --- /dev/null +++ b/Moose Training/Documentation/Process_SEAD.html @@ -0,0 +1,547 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Process_SEAD

    + + + +

    Global(s)

    + + + + + +
    PROCESS_SEAD + +
    +

    Type PROCESS_SEAD

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PROCESS_SEAD.ClassName + +
    PROCESS_SEAD:EventDead(Event) + +
    PROCESS_SEAD.Fsm + +
    PROCESS_SEAD:New(Task, ProcessUnit, TargetSetUnit) +

    Creates a new SEAD task.

    +
    PROCESS_SEAD:OnDestroyed(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_SEAD:OnHitTarget(Fsm, Event, From, To, Event) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_SEAD:OnKilled(Fsm, Event, From, To, DCSEvent) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_SEAD:OnMoreTargets(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_SEAD:OnRestart(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_SEAD:OnStart(Fsm, Event, From, To) +

    StateMachine callback function for a PROCESS

    +
    PROCESS_SEAD.ProcessUnit + +
    PROCESS_SEAD.TargetSetUnit + +
    + +

    Global(s)

    +
    +
    + + #PROCESS_SEAD + +PROCESS_SEAD + +
    +
    + + + +
    +
    +

    Type Process_SEAD

    + +

    Type PROCESS_SEAD

    + +

    PROCESS_SEAD class

    + +

    Field(s)

    +
    +
    + + #string + +PROCESS_SEAD.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS_SEAD:EventDead(Event) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + + + +PROCESS_SEAD.Fsm + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS_SEAD:New(Task, ProcessUnit, TargetSetUnit) + +
    +
    + +

    Creates a new SEAD task.

    + +

    Parameters

    + +

    Return value

    + +

    #PROCESS_SEAD: +self

    + +
    +
    +
    +
    + + +PROCESS_SEAD:OnDestroyed(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_SEAD:OnHitTarget(Fsm, Event, From, To, Event) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_SEAD:OnKilled(Fsm, Event, From, To, DCSEvent) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_SEAD:OnMoreTargets(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_SEAD:OnRestart(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_SEAD:OnStart(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a PROCESS

    + +

    Parameters

    + +
    +
    +
    +
    + + Unit#UNIT + +PROCESS_SEAD.ProcessUnit + +
    +
    + + + +
    +
    +
    +
    + + Set#SET_UNIT + +PROCESS_SEAD.TargetSetUnit + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/ROUTETASK.html b/Moose Training/Documentation/ROUTETASK.html index 540000e02..de6dbf7bd 100644 --- a/Moose Training/Documentation/ROUTETASK.html +++ b/Moose Training/Documentation/ROUTETASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/STAGE.html b/Moose Training/Documentation/STAGE.html index adacdaa27..1f8b7845c 100644 --- a/Moose Training/Documentation/STAGE.html +++ b/Moose Training/Documentation/STAGE.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Scheduler.html b/Moose Training/Documentation/Scheduler.html index 3cdcbdb91..542981294 100644 --- a/Moose Training/Documentation/Scheduler.html +++ b/Moose Training/Documentation/Scheduler.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Scoring.html b/Moose Training/Documentation/Scoring.html index 09f0747b0..cb33d39aa 100644 --- a/Moose Training/Documentation/Scoring.html +++ b/Moose Training/Documentation/Scoring.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -221,7 +231,7 @@ to a database or a BI tool to publish the scoring results to the player communit - SCORING:_AddMissionTaskScore(PlayerUnit, MissionName, Score) + SCORING:_AddMissionTaskScore(Mission, PlayerUnit, Text, Score)

    Registers Scores the players completing a Mission Task.

    @@ -693,7 +703,7 @@ self

    -SCORING:_AddMissionTaskScore(PlayerUnit, MissionName, Score) +SCORING:_AddMissionTaskScore(Mission, PlayerUnit, Text, Score)
    @@ -704,17 +714,22 @@ self

    • -

      PlayerUnit :

      +

      Mission#MISSION Mission :

    • -

      MissionName :

      +

      Unit#UNIT PlayerUnit :

    • -

      Score :

      +

      #string Text :

      + +
    • +
    • + +

      #number Score :

    diff --git a/Moose Training/Documentation/Sead.html b/Moose Training/Documentation/Sead.html index e2a5ae18c..0a142fedf 100644 --- a/Moose Training/Documentation/Sead.html +++ b/Moose Training/Documentation/Sead.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/Set.html b/Moose Training/Documentation/Set.html index 642df566e..f955c808e 100644 --- a/Moose Training/Documentation/Set.html +++ b/Moose Training/Documentation/Set.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -474,6 +484,24 @@ The following iterator methods are currently available within the SETAIRBAS SET_BASE.ClassName + + + + SET_BASE:Count() + +

    Retrieves the amount of objects in the Set#SET_BASE and derived classes.

    + + + + SET_BASE.Database + + + + + + SET_BASE.Filter + + @@ -504,6 +532,12 @@ The following iterator methods are currently available within the SETAIRBAS SET_BASE:IsIncludeObject(Object)

    Decides whether to include the Object

    + + + + SET_BASE.List + + @@ -522,6 +556,12 @@ The following iterator methods are currently available within the SETAIRBAS SET_BASE.Set + + + + SET_BASE:SetDatabase(BaseSet) + +

    Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set).

    @@ -858,6 +898,12 @@ The following iterator methods are currently available within the SETAIRBAS SET_UNIT:FilterCountries(Countries)

    Builds a set of units of defined countries.

    + + + + SET_UNIT:FilterHasRadar(RadarTypes) + +

    Builds a set of units having a radar of give types.

    @@ -912,6 +958,18 @@ The following iterator methods are currently available within the SETAIRBAS SET_UNIT:ForEachUnitNotInZone(ZoneObject, IteratorFunction, ...)

    Iterate the SET_UNIT and call an iterator function for each alive UNIT presence not in a Zone, providing the UNIT and optional parameters to the called function.

    + + + + SET_UNIT:HasGroundUnits() + +

    Returns if the Set has ground targets.

    + + + + SET_UNIT:HasRadar(RadarType) + +

    Returns if the Set has targets having a radar (of a given type).

    @@ -1466,6 +1524,55 @@ The added BASE Object.

    +
    +
    +
    +
    + + +SET_BASE:Count() + +
    +
    + +

    Retrieves the amount of objects in the Set#SET_BASE and derived classes.

    + +

    Return value

    + +

    #number: +Count

    + +
    +
    +
    +
    + + + +SET_BASE.Database + +
    +
    + + + + +

    Now base the new Set on the BaseSet

    + +
    +
    +
    +
    + + #table + +SET_BASE.Filter + +
    +
    + + +
    @@ -1605,6 +1712,20 @@ self

    #SET_BASE: self

    + +
    +
    +
    + + #table + +SET_BASE.List + +
    +
    + + +
    @@ -1661,7 +1782,7 @@ DBObject = SET_BASE:New()
    - + #table SET_BASE.Set @@ -1670,6 +1791,32 @@ DBObject = SET_BASE:New() + +
    +
    +
    + + +SET_BASE:SetDatabase(BaseSet) + +
    +
    + +

    Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set).

    + +

    Parameter

    + +

    Return value

    + +

    #SET_BASE:

    + +
    @@ -3116,6 +3263,36 @@ Can take those country strings known within DCS world.

    #SET_UNIT: self

    + +
    +
    +
    + + +SET_UNIT:FilterHasRadar(RadarTypes) + +
    +
    + +

    Builds a set of units having a radar of give types.

    + + +

    All the units having a radar of a given type will be included within the set.

    + +

    Parameter

    +
      +
    • + +

      #table RadarTypes : +The radar types.

      + +
    • +
    +

    Return value

    + +

    #SET_UNIT: +self

    +
    @@ -3386,6 +3563,50 @@ self

    + +SET_UNIT:HasGroundUnits() + +
    +
    + +

    Returns if the Set has ground targets.

    + +

    Return value

    + +

    #number: +The amount of ground targets in the Set.

    + +
    +
    +
    +
    + + +SET_UNIT:HasRadar(RadarType) + +
    +
    + +

    Returns if the Set has targets having a radar (of a given type).

    + +

    Parameter

    + +

    Return value

    + +

    #number: +The amount of radars in the Set with the given type

    + +
    +
    +
    +
    + SET_UNIT:IsIncludeObject(MUnit) diff --git a/Moose Training/Documentation/Spawn.html b/Moose Training/Documentation/Spawn.html index bbb0a1012..7455c39fe 100644 --- a/Moose Training/Documentation/Spawn.html +++ b/Moose Training/Documentation/Spawn.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -174,6 +184,12 @@ Check the SPAWN.CleanUp for further info.

    Type SPAWN

    + + + + + + + + + + + + @@ -357,6 +385,12 @@ Check the SPAWN.CleanUp for further info.

    + + + + @@ -494,7 +528,7 @@ Check the SPAWN.CleanUp for further info.

    @@ -516,15 +550,15 @@ Check the SPAWN.CleanUp for further info.

    - + - + @@ -608,6 +642,20 @@ Check the SPAWN.CleanUp for further info.

    + #number + +SPAWN.AliveUnits + +
    +
    + + + +
    +
    +
    +
    + SPAWN:Array(SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY) @@ -975,6 +1023,34 @@ self

    -- There will be maximum 24 groups spawned during the whole mission lifetime. Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Limit( 2, 24 ) + +
    +
    +
    + + #number + +SPAWN.MaxAliveGroups + +
    +
    + + + +
    +
    +
    +
    + + #number + +SPAWN.MaxAliveUnits + +
    +
    + + +
    @@ -1491,6 +1567,20 @@ when nothing was spawned.

    + +
    +
    +
    + + #number + +SPAWN.SpawnIndex + +
    +
    + + +
    @@ -1860,10 +1950,26 @@ self

    +

    Return values

    +
      +
    1. + +

      Group#GROUP: +The Group

      + +
    2. +
    3. + +

      #nil: +Nothing found

      + +
    4. +
    @@ -1885,8 +1991,8 @@ It will return nil of no prefix was found.

    • -

      DCSUnit : -The DCS unit to be searched.

      +

      DCSUnit#Unit DCSUnit : +The DCSUnit to be searched.

    @@ -1929,7 +2035,7 @@ Nothing found

    -

    Return the prefix of a DCSUnit.

    +

    Return the prefix of a SpawnUnit.

    The method will search for a #-mark, and will return the text before the #-mark. @@ -1939,8 +2045,8 @@ It will return nil of no prefix was found.

    • -

      DCSUnit : -The DCS unit to be searched.

      +

      DCSUnit#UNIT DCSUnit : +The DCSUnit to be searched.

    @@ -2039,21 +2145,18 @@ Nothing found

    -SPAWN:_OnBirth(event) +SPAWN:_OnBirth(Event)
    - -

    TODO Need to delete this... _DATABASE does this now ...

    -

    Parameter

    @@ -2063,18 +2166,18 @@ Nothing found

    -SPAWN:_OnDeadOrCrash(event) +SPAWN:_OnDeadOrCrash(Event)
    -

    Obscolete

    +

    Parameter

    diff --git a/Moose Training/Documentation/StateMachine.html b/Moose Training/Documentation/StateMachine.html new file mode 100644 index 000000000..5f59d56b0 --- /dev/null +++ b/Moose Training/Documentation/StateMachine.html @@ -0,0 +1,774 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module StateMachine

    + +

    This module contains the STATEMACHINE class.

    + + +

    This development is based on a state machine implementation made by Conroy Kyle.
    +The state machine can be found here: https://github.com/kyleconroy/lua-state-machine

    + +

    I've taken the development and enhanced it to make the state machine hierarchical... +It is a fantastic development, this module.

    + +
    + +

    1) Workflow#STATEMACHINE class, extends Base#BASE

    + +

    1.1) Add or remove objects from the STATEMACHINE

    + +

    Global(s)

    +
    SPAWN.AliveUnits + +
    SPAWN:Array(SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY)

    Makes the groups visible before start (like a batallion).

    @@ -249,6 +265,18 @@ Check the SPAWN.CleanUp for further info.

    SPAWN:Limit(SpawnMaxUnitsAlive, SpawnMaxGroups)

    Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned.

    +
    SPAWN.MaxAliveGroups + +
    SPAWN.MaxAliveUnits +
    SPAWN:SpawnInZone(Zone, ZoneRandomize, SpawnIndex)

    Will spawn a Group within a given Zone#ZONE.

    +
    SPAWN.SpawnIndex +
    SPAWN:_GetPrefixFromDCSUnit(DCSUnit) -

    Return the prefix of a DCSUnit.

    +

    Return the prefix of a SpawnUnit.

    SPAWN:_OnBirth(event)SPAWN:_OnBirth(Event)
    SPAWN:_OnDeadOrCrash(event)SPAWN:_OnDeadOrCrash(Event) -

    Obscolete

    +
    + + + + + + + + + + + + +
    STATEMACHINE + +
    STATEMACHINE_PROCESS + +
    STATEMACHINE_TASK + +
    +

    Type STATEMACHINE

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    STATEMACHINE.ClassName + +
    STATEMACHINE:New(options) +

    Creates a new STATEMACHINE object.

    +
    STATEMACHINE:_add_to_map(map, event) + +
    STATEMACHINE:_call_handler(handler, params) + +
    STATEMACHINE:_create_transition(name) + +
    STATEMACHINE:_gosub(parentstate, parentevent) + +
    STATEMACHINE:_isendstate(state) + +
    STATEMACHINE:_submap(subs, sub, name) + +
    STATEMACHINE:can(e) + +
    STATEMACHINE:cannot(e) + +
    STATEMACHINE:is(state) + +
    STATEMACHINE:todot(filename) + +
    + +

    Type STATEMACHINE_PROCESS

    + + + + + + + + + + + + + + + + + +
    STATEMACHINE_PROCESS.ClassName + +
    STATEMACHINE_PROCESS:New(Process, options) +

    Creates a new STATEMACHINE_PROCESS object.

    +
    STATEMACHINE_PROCESS.Process + +
    STATEMACHINE_PROCESS:_call_handler(handler, params) + +
    + +

    Type STATEMACHINE_TASK

    + + + + + + + + + + + + + + + + + +
    STATEMACHINE_TASK.ClassName + +
    STATEMACHINE_TASK:New(Task, options) +

    Creates a new STATEMACHINE_TASK object.

    +
    STATEMACHINE_TASK.Task + +
    STATEMACHINE_TASK:_call_handler(handler, params) + +
    + +

    Global(s)

    +
    +
    + + #STATEMACHINE + +STATEMACHINE + +
    +
    + + + +
    +
    +
    +
    + + #STATEMACHINE_PROCESS + +STATEMACHINE_PROCESS + +
    +
    + + + +
    +
    +
    +
    + + #STATEMACHINE_TASK + +STATEMACHINE_TASK + +
    +
    + + + +
    +
    +

    Type StateMachine

    + +

    Type STATEMACHINE

    + +

    STATEMACHINE class

    + +

    Field(s)

    +
    +
    + + #string + +STATEMACHINE.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +STATEMACHINE:New(options) + +
    +
    + +

    Creates a new STATEMACHINE object.

    + +

    Parameter

    +
      +
    • + +

      options :

      + +
    • +
    +

    Return value

    + +

    #STATEMACHINE:

    + + +
    +
    +
    +
    + + +STATEMACHINE:_add_to_map(map, event) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      map :

      + +
    • +
    • + +

      event :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:_call_handler(handler, params) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      handler :

      + +
    • +
    • + +

      params :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:_create_transition(name) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      name :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:_gosub(parentstate, parentevent) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      parentstate :

      + +
    • +
    • + +

      parentevent :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:_isendstate(state) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      state :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:_submap(subs, sub, name) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      subs :

      + +
    • +
    • + +

      sub :

      + +
    • +
    • + +

      name :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:can(e) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      e :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:cannot(e) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      e :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:is(state) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      state :

      + +
    • +
    +
    +
    +
    +
    + + +STATEMACHINE:todot(filename) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      filename :

      + +
    • +
    +
    +
    + +

    Type STATEMACHINE_PROCESS

    + +

    STATEMACHINE_PROCESS class

    + +

    Field(s)

    +
    +
    + + #string + +STATEMACHINE_PROCESS.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +STATEMACHINE_PROCESS:New(Process, options) + +
    +
    + +

    Creates a new STATEMACHINE_PROCESS object.

    + +

    Parameters

    +
      +
    • + +

      Process :

      + +
    • +
    • + +

      options :

      + +
    • +
    +

    Return value

    + +

    #STATEMACHINE_PROCESS:

    + + +
    +
    +
    +
    + + Process#PROCESS + +STATEMACHINE_PROCESS.Process + +
    +
    + + + +
    +
    +
    +
    + + +STATEMACHINE_PROCESS:_call_handler(handler, params) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      handler :

      + +
    • +
    • + +

      params :

      + +
    • +
    +
    +
    + +

    Type STATEMACHINE_TASK

    + +

    STATEMACHINE_TASK class

    + +

    Field(s)

    +
    +
    + + #string + +STATEMACHINE_TASK.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +STATEMACHINE_TASK:New(Task, options) + +
    +
    + +

    Creates a new STATEMACHINE_TASK object.

    + +

    Parameters

    +
      +
    • + +

      Task :

      + +
    • +
    • + +

      options :

      + +
    • +
    +

    Return value

    + +

    #STATEMACHINE_TASK:

    + + +
    +
    +
    +
    + + Task#TASK_BASE + +STATEMACHINE_TASK.Task + +
    +
    + + + +
    +
    +
    +
    + + +STATEMACHINE_TASK:_call_handler(handler, params) + +
    +
    + + + +

    Parameters

    +
      +
    • + +

      handler :

      + +
    • +
    • + +

      params :

      + +
    • +
    +
    +
    + + + + + + diff --git a/Moose Training/Documentation/Static.html b/Moose Training/Documentation/Static.html index 9623e149e..65737d556 100644 --- a/Moose Training/Documentation/Static.html +++ b/Moose Training/Documentation/Static.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/StaticObject.html b/Moose Training/Documentation/StaticObject.html index da3a69a1d..04be6eabe 100644 --- a/Moose Training/Documentation/StaticObject.html +++ b/Moose Training/Documentation/StaticObject.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/TASK.html b/Moose Training/Documentation/TASK.html index 3a47c534b..46e2e52e1 100644 --- a/Moose Training/Documentation/TASK.html +++ b/Moose Training/Documentation/TASK.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • -
  • TASK
  • +
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -79,296 +89,374 @@
    -

    Module TASK

    +

    Module Task

    -

    The TASK Classes define major end-to-end activities within a MISSION.

    +

    This module contains the TASK_BASE class.

    -

    The TASK Class is the Master Class to orchestrate these activities. From this class, many concrete TASK classes are inherited.

    + +

    1) #TASK_BASE class, extends Base#BASE

    +

    1.1) The #TASK_BASE class implements the methods for task orchestration within MOOSE.

    +

    The class provides a couple of methods to:

    + + + +

    1.2) Set and enquire task status (beyond the task state machine processing).

    +

    A task needs to implement as a minimum the following task states:

    + +
      +
    • Success: Expresses the successful execution and finalization of the task.
    • +
    • Failed: Expresses the failure of a task.
    • +
    • Planned: Expresses that the task is created, but not yet in execution and is not assigned yet.
    • +
    • Assigned: Expresses that the task is assigned to a Group of players, and that the task is in execution mode.
    • +
    + +

    A task may also implement the following task states:

    + +
      +
    • Rejected: Expresses that the task is rejected by a player, who was requested to accept the task.
    • +
    • Cancelled: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required.
    • +
    + +

    A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled.

    + +

    The status of tasks can be set by the methods State followed by the task status. An example is StateAssigned(). +The status of tasks can be enquired by the methods IsState followed by the task status name. An example is if IsStateAssigned() then.

    + +

    1.3) Add scoring when reaching a certain task status:

    +

    Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +Use the method TASK_BASE.AddScore() to add scores when a status is reached.

    + +

    1.4) Task briefing:

    +

    A task briefing can be given that is shown to the player when he is assigned to the task.

    + +
    + +

    Authors: FlightControl - Design and Programming

    +

    Global(s)

    - +
    TASKTASK_BASE
    -

    Type TASK

    - +

    Type TASK_BASE

    +
    - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + - + - + - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - + - + - - - - - - - - - + - - - - - + - + + + + + + + + + + + + + - - - - - - - - - + @@ -379,9 +467,9 @@
    - #TASK - -TASK + #TASK_BASE + +TASK_BASE
    @@ -390,556 +478,163 @@
    -

    Type TASK

    - -

    The TASK class

    +

    Type Task

    + +

    Type TASK_BASE

    + +

    The TASK_BASE class

    Field(s)

    - #number - -TASK.ActiveStage + +TASK_BASE:AddProcess(TaskUnit, Process)
    - - -
    -
    -
    -
    - - -TASK.AddFlareBlue(table, number, self, SignalUnitNames, SignalHeight) - -
    -
    - -

    When the CLIENT is approaching the landing zone, a BLUE FLARE will be fired by an optional SignalUnitNames.

    +

    Add Process to Task with key Unit

    Parameters

    • -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      +

      Unit#UNIT TaskUnit :

    • -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      +

      Process :

    +

    Return value

    + +

    #TASK_BASE: +self

    +
    - -TASK.AddFlareGreen(table, number, self, SignalUnitNames, SignalHeight) + +TASK_BASE:AddScore(TaskStatus, ScoreText, Score)
    -

    When the CLIENT is approaching the landing zone, a GREEN FLARE will be fired by an optional SignalUnitNames.

    +

    Adds a score for the TASK to be achieved.

    Parameters

    • -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      +

      #string TaskStatus : +is the status of the TASK when the score needs to be given.

    • -

      number : -SignalHeight Altitude that the Signal should be fired...

      +

      #string ScoreText : +is a text describing the score that is given according the status.

    • -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      +

      #number Score : +is a number providing the score of the status.

    +

    Return value

    + +

    #TASK_BASE: +self

    +
    - -TASK.AddFlareOrange(table, number, self, SignalUnitNames, SignalHeight) + +TASK_BASE:AddStateMachine(TaskUnit, Fsm)
    -

    When the CLIENT is approaching the landing zone, an ORANGE FLARE will be fired by an optional SignalUnitNames.

    +

    Add a FiniteStateMachine to Task with key Unit

    Parameters

    • -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      +

      Unit#UNIT TaskUnit :

    • -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      +

      Fsm :

    +

    Return value

    + +

    #TASK_BASE: +self

    +
    - -TASK.AddFlareRed(table, number, self, SignalUnitNames, SignalHeight) + +TASK_BASE:AssignToGroup(TaskGroup)
    -

    When the CLIENT is approaching the landing zone, a RED FLARE will be fired by an optional SignalUnitNames.

    +

    Assign the Taskto a Group.

    -

    Parameters

    +

    Parameter

    • -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      - -
    • -
    • - -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      +

      Group#GROUP TaskGroup :

    +

    Return value

    + +

    #TASK_BASE: +self

    +
    - -TASK.AddFlareWhite(table, number, self, SignalUnitNames, SignalHeight) + +TASK_BASE:AssignToUnit(TaskUnit)
    -

    When the CLIENT is approaching the landing zone, a WHITE FLARE will be fired by an optional SignalUnitNames.

    +

    Assign the Taskto an alive Unit.

    -

    Parameters

    +

    Parameter

    • -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      - -
    • -
    • - -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      +

      Unit#UNIT TaskUnit :

    -
    -
    -
    -
    - - -TASK.AddGoalCompletion(string, string, number, self, GoalVerb, GoalTask, GoalIncrease) - -
    -
    - -

    Adds an Additional Goal for the TASK to be achieved.

    - -

    Parameters

    -
      -
    • +

      Return value

      -

      string : -GoalVerb is the name of the Goal of the TASK.

      - -
    • -
    • - -

      string : -GoalTask is a text describing the Goal of the TASK to be achieved.

      - -
    • -
    • - -

      number : -GoalIncrease is a number by which the Goal achievement is increasing.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      GoalVerb :

      - -
    • -
    • - -

      GoalTask :

      - -
    • -
    • - -

      GoalIncrease :

      - -
    • -
    -
    -
    -
    -
    - - -TASK:AddSignal(SignalUnitNames, SignalType, SignalColor, SignalHeight) - -
    -
    - -

    Work function to set signal events within a TASK.

    - -

    Parameters

    -
      -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalType :

      - -
    • -
    • - -

      SignalColor :

      - -
    • -
    • - -

      SignalHeight :

      - -
    • -
    -
    -
    -
    -
    - - -TASK.AddSmokeBlue(table, number, self, SignalUnitNames, SignalHeight) - -
    -
    - -

    When the CLIENT is approaching the landing zone, a BLUE SMOKE will be fired by an optional SignalUnitNames.

    - -

    Parameters

    -
      -
    • - -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      - -
    • -
    • - -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      - -
    • -
    -
    -
    -
    -
    - - -TASK.AddSmokeGreen(table, number, self, SignalUnitNames, SignalHeight) - -
    -
    - -

    When the CLIENT is approaching the landing zone, a GREEN SMOKE will be fired by an optional SignalUnitNames.

    - -

    Parameters

    -
      -
    • - -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      - -
    • -
    • - -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      - -
    • -
    -
    -
    -
    -
    - - -TASK.AddSmokeOrange(table, number, self, SignalUnitNames, SignalHeight) - -
    -
    - -

    When the CLIENT is approaching the landing zone, an ORANGE SMOKE will be fired by an optional SignalUnitNames.

    - -

    Parameters

    -
      -
    • - -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      - -
    • -
    • - -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      - -
    • -
    -
    -
    -
    -
    - - -TASK.AddSmokeRed(table, number, self, SignalUnitNames, SignalHeight) - -
    -
    - -

    When the CLIENT is approaching the landing zone, a RED SMOKE will be fired by an optional SignalUnitNames.

    - -

    Parameters

    -
      -
    • - -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      - -
    • -
    • - -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      - -
    • -
    -
    -
    -
    -
    - - -TASK.AddSmokeWhite(table, number, self, SignalUnitNames, SignalHeight) - -
    -
    - -

    When the CLIENT is approaching the landing zone, a WHITE SMOKE will be fired by an optional SignalUnitNames.

    - -

    Parameters

    -
      -
    • - -

      table : -string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.

      - -
    • -
    • - -

      number : -SignalHeight Altitude that the Signal should be fired...

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      SignalUnitNames :

      - -
    • -
    • - -

      SignalHeight :

      - -
    • -
    -
    -
    -
    -
    - - - -TASK.Cargos - -
    -
    - - +

    #TASK_BASE: +self

    @@ -947,8 +642,8 @@ SignalHeight Altitude that the Signal should be fired...

    #string - -TASK.ClassName + +TASK_BASE.ClassName
    @@ -960,241 +655,230 @@ SignalHeight Altitude that the Signal should be fired...

    - -TASK:Done() + StateMachine#STATEMACHINE + +TASK_BASE.Fsm
    -

    Sets a TASK to status Done.

    +
    - -TASK:Failed() + +TASK_BASE:GetCategory()
    -

    Sets a TASK to status failed.

    - -
    -
    -
    -
    - - -TASK.GetGoalCompletion(?, self, GoalVerb) - -
    -
    - -

    Returns if the additional Goal for the TASK was completed.

    - -

    Parameters

    -
      -
    • - -

      ? : -tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      GoalVerb :

      - -
    • -
    -

    Return value

    - - -

    string Goals

    - -
    -
    -
    -
    - - -TASK.GetGoalCount(?, self, GoalVerb) - -
    -
    - -

    Gets the total of Goals currently achieved within the TASK of the GoalVerb.

    - -

    Parameters

    -
      -
    • - -

      ? : -tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      GoalVerb :

      - -
    • -
    -

    Return value

    - - -

    TASK

    - -
    -
    -
    -
    - - -TASK.GetGoalPercentage(?, self, GoalVerb) - -
    -
    - -

    Gets the percentage of Goals currently achieved within the TASK of the GoalVerb.

    - -

    Parameters

    -
      -
    • - -

      ? : -tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      GoalVerb :

      - -
    • -
    -

    Return value

    - - -

    TASK

    - -
    -
    -
    -
    - - -TASK:GetGoalProgress() - -
    -
    - -

    Get progress of a TASK.

    +

    Gets the Category of the Task

    Return value

    - -

    string GoalsText

    +

    #string: +TaskCategory

    - -TASK.GetGoalTotal(?, self, GoalVerb) + +TASK_BASE:GetID()
    -

    Gets the total of Goals to be achieved within the TASK of the GoalVerb.

    - -

    Parameters

    -
      -
    • - -

      ? : -tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      GoalVerb :

      - -
    • -
    -
    -
    -
    -
    - - -TASK:GetGoals() - -
    -
    - -

    Returns the Goals of a TASK

    +

    Gets the ID of the Task

    Return value

    - -

    @table Goals

    +

    #string: +TaskID

    - -TASK:Goal(GoalVerb) + +TASK_BASE:GetName()
    -

    Returns if a TASK has Goal(s).

    +

    Gets the Name of the Task

    + +

    Return value

    + +

    #string: +The Task Name

    + +
    +
    +
    +
    + + +TASK_BASE:GetScoring() + +
    +
    + +

    Gets the Scoring of the task

    + +

    Return value

    + +

    Scoring#SCORING: +Scoring

    + +
    +
    +
    +
    + + +TASK_BASE:GetStateString() + +
    +
    + +

    Gets the Task status.

    + +
    +
    +
    +
    + + +TASK_BASE:GetType() + +
    +
    + +

    Gets the Type of the Task

    + +

    Return value

    + +

    #string: +TaskType

    + +
    +
    +
    +
    + + +TASK_BASE:HasStateMachine(TaskUnit) + +
    +
    + +

    Checks if there is a FiniteStateMachine assigned to Unit for Task

    Parameter

    • -

      #string GoalVerb : -is the name of the Goal of the TASK.

      +

      Unit#UNIT TaskUnit :

    Return value

    - -

    bool

    +

    #TASK_BASE: +self

    - - -TASK.GoalTasks + +TASK_BASE:IsStateAssigned() + +
    +
    + +

    Is the Task status Assigned.

    + +
    +
    +
    +
    + + +TASK_BASE:IsStateFailed() + +
    +
    + +

    Is the Task status Failed.

    + +
    +
    +
    +
    + + +TASK_BASE:IsStateHold() + +
    +
    + +

    Is the Task status Hold.

    + +
    +
    +
    +
    + + +TASK_BASE:IsStatePlanned() + +
    +
    + +

    Is the Task status Planned.

    + +
    +
    +
    +
    + + +TASK_BASE:IsStateReplanned() + +
    +
    + +

    Is the Task status Replanned.

    + +
    +
    +
    +
    + + +TASK_BASE:IsStateSuccess() + +
    +
    + +

    Is the Task status Success.

    + +
    +
    +
    +
    + + Mission#MISSION + +TASK_BASE.Mission
    @@ -1206,213 +890,17 @@ is the name of the Goal of the TASK.

    - -TASK.IncreaseGoalCount(number, ?, self, GoalCountIncrease, GoalVerb) + +TASK_BASE:New(Mission, TaskName, TaskType, TaskCategory)
    -

    Increments the total of Goals currently achieved within the TASK of the GoalVerb, with the given GoalCountIncrease.

    - -

    Parameters

    -
      -
    • - -

      number : -GoalCountIncrease is the number of new Goals achieved within the TASK.

      - -
    • -
    • - -

      ? : -tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      GoalCountIncrease :

      - -
    • -
    • - -

      GoalVerb :

      - -
    • -
    -

    Return value

    - - -

    TASK

    - -
    -
    -
    -
    - - -TASK:Init() - -
    -
    - - - -
    -
    -
    -
    - - -TASK:IsDone() - -
    -
    - -

    Returns if a TASK is done.

    - -

    Return value

    - - -

    bool

    - -
    -
    -
    -
    - - -TASK:IsFailed() - -
    -
    - -

    Returns if a TASk has failed.

    - -

    Return value

    - - -

    bool

    - -
    -
    -
    -
    - - -TASK:IsGoalReached() - -
    -
    - -

    Returns if all the Goals of the TASK were achieved.

    - -

    Return value

    - - -

    bool

    - -
    -
    -
    -
    - - - -TASK.LandingZones - -
    -
    - - - -
    -
    -
    -
    - - -TASK.MenuAction(Parameter) - -
    -
    - - - -

    Parameter

    -
      -
    • - -

      Parameter :

      - -
    • -
    -
    -
    -
    -
    - - - -TASK.Mission - -
    -
    - - - -
    -
    -
    -
    - - #string - -TASK.Name - -
    -
    - - - -
    -
    -
    -
    - - -TASK:New() - -
    -
    - -

    Instantiates a new TASK Base.

    +

    Instantiates a new TASK_BASE.

    Should never be used. Interface Class.

    -

    Return value

    - - -

    TASK

    - -
    -
    -
    -
    - - -TASK:Reset(Mission, Client) - -
    -
    - - -

    Parameters

    • @@ -1422,136 +910,208 @@ tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given
    • -

      Client :

      +

      TaskName :

      + +
    • +
    • + +

      TaskType :

      + +
    • +
    • + +

      TaskCategory :

    +

    Return value

    + +

    #TASK_BASE: +self

    + +
    +
    +
    +
    + + +TASK_BASE:OnStateChange(Fsm, Event, From, To, Event) + +
    +
    + +

    StateMachine callback function for a TASK

    + +

    Parameters

    + +
    +
    +
    +
    + + +TASK_BASE.Players + +
    +
    + + +
    - -TASK.SIGNAL + +TASK_BASE.Processes
    - -

    Defines the different signal types with a Task.

    -
    - -TASK.SetGoalCount(number, ?, self, GoalCount, GoalVerb) + +TASK_BASE:RemoveProcesses(TaskUnit, FailProcesses)
    -

    Sets the total of Goals currently achieved within the TASK of the GoalVerb.

    +

    Remove Processes from Task with key Unit

    Parameters

    • -

      number : -GoalCount is the total number of Goals achieved within the TASK.

      +

      TaskUnit :

    • -

      ? : -tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      GoalCount :

      - -
    • -
    • - -

      GoalVerb :

      +

      FailProcesses :

    Return value

    - -

    TASK

    +

    #TASK_BASE: +self

    - -TASK.SetGoalTotal(number, ?, self, GoalTotal, GoalVerb) + +TASK_BASE:RemoveStateMachines(TaskUnit)
    -

    Sets the total Goals to be achieved of the Goal Name

    - -

    Parameters

    -
      -
    • - -

      number : -GoalTotal is the number of times the GoalVerb needs to be achieved.

      - -
    • -
    • - -

      ? : -tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      GoalTotal :

      - -
    • -
    • - -

      GoalVerb :

      - -
    • -
    -
    -
    -
    -
    - - -TASK:SetStage(StageSequenceIncrement) - -
    -
    - - +

    Remove FiniteStateMachines from Task with key Unit

    Parameter

    • -

      StageSequenceIncrement :

      +

      TaskUnit :

      + +
    • +
    +

    Return value

    + +

    #TASK_BASE: +self

    + +
    +
    +
    +
    + + + +TASK_BASE.Scores + +
    +
    + + + +
    +
    +
    +
    + + +TASK_BASE:SetBriefing(TaskBriefing) + +
    +
    + +

    Sets a Task briefing.

    + +

    Parameter

    +
      +
    • + +

      #string TaskBriefing :

      + +
    • +
    +

    Return value

    + + +

    self

    + +
    +
    +
    +
    + + +TASK_BASE:SetCategory(TaskCategory) + +
    +
    + +

    Sets the Category of the Task

    + +

    Parameter

    +
      +
    • + +

      #string TaskCategory :

    @@ -1560,53 +1120,336 @@ tring GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given
    - -TASK.ShowGoalProgress(MISSION, CLIENT, self, Mission, Client) + +TASK_BASE:SetID(TaskID)
    -

    Show progress of a TASK.

    +

    Sets the ID of the Task

    + +

    Parameter

    +
      +
    • + +

      #string TaskID :

      + +
    • +
    +
    +
    +
    +
    + + +TASK_BASE:SetName(TaskName) + +
    +
    + +

    Sets the Name of the Task

    + +

    Parameter

    +
      +
    • + +

      #string TaskName :

      + +
    • +
    +
    +
    +
    +
    + + +TASK_BASE:SetType(TaskType) + +
    +
    + +

    Sets the Type of the Task

    + +

    Parameter

    +
      +
    • + +

      #string TaskType :

      + +
    • +
    +
    +
    +
    +
    + + +TASK_BASE:StateAssigned() + +
    +
    + +

    Sets a Task to status Assigned.

    + +
    +
    +
    +
    + + +TASK_BASE:StateFailed() + +
    +
    + +

    Sets a Task to status Failed.

    + +
    +
    +
    +
    + + +TASK_BASE:StateHold() + +
    +
    + +

    Sets a Task to status Hold.

    + +
    +
    +
    +
    + + +TASK_BASE:StatePlanned() + +
    +
    + +

    Sets a Task to status Planned.

    + +
    +
    +
    +
    + + +TASK_BASE:StateReplanned() + +
    +
    + +

    Sets a Task to status Replanned.

    + +
    +
    +
    +
    + + +TASK_BASE:StateSuccess() + +
    +
    + +

    Sets a Task to status Success.

    + +
    +
    +
    +
    + + + +TASK_BASE.TaskBriefing + +
    +
    + + + +
    +
    +
    +
    + + + +TASK_BASE.TaskCategory + +
    +
    + + + +
    +
    +
    +
    + + + +TASK_BASE.TaskID + +
    +
    + + + +
    +
    +
    +
    + + + +TASK_BASE.TaskName + +
    +
    + + + +
    +
    +
    +
    + + Scheduler#SCHEDULER + +TASK_BASE.TaskScheduler + +
    +
    + + + +
    +
    +
    +
    + + + +TASK_BASE.TaskType + +
    +
    + + + +
    +
    +
    +
    + + + +TASK_BASE.TaskUnit + +
    +
    + + + +
    +
    +
    +
    + + +TASK_BASE:UnAssignFromUnit(TaskUnit, FailProcesses) + +
    +
    + +

    UnAssign the Task from an alive Unit.

    Parameters

    • -

      MISSION :

      -
      Mission         Group structure describing the Mission.
      -
      +

      Unit#UNIT TaskUnit :

    • -

      CLIENT : -Client Group structure describing the Client.

      - -
    • -
    • - -

      self :

      - -
    • -
    • - -

      Mission :

      - -
    • -
    • - -

      Client :

      +

      FailProcesses :

    +

    Return value

    + +

    #TASK_BASE: +self

    +
    - - -TASK.Stage + +TASK_BASE:_EventAssignUnit(Event) + +
    +
    + +

    Register a potential new assignment for a new spawned Unit.

    + + +

    Tasks only get assigned if there are players in it.

    + +

    Parameter

    + +

    Return value

    + +

    #TASK_BASE: +self

    + +
    +
    +
    +
    + + +TASK_BASE:_EventUnAssignUnit(Event) + +
    +
    + +

    UnAssigns a Unit that is left by a player, crashed, dead, ....

    + + +

    There are only assignments if there are players in it.

    + +

    Parameter

    + +

    Return value

    + +

    #TASK_BASE: +self

    + +
    +
    +
    +
    + + +TASK_BASE:_Schedule()
    @@ -1618,50 +1461,8 @@ Client Group structure describing the Client.

    - -TASK:StageExecute() - -
    -
    - - - -
    -
    -
    -
    - - - -TASK.Stages - -
    -
    - - - -
    -
    -
    -
    - - #boolean - -TASK.TaskDone - -
    -
    - - - -
    -
    -
    -
    - - #boolean - -TASK.TaskFailed + +TASK_BASE:_Scheduler()
    diff --git a/Moose Training/Documentation/Task_Assign.html b/Moose Training/Documentation/Task_Assign.html new file mode 100644 index 000000000..7622b1d01 --- /dev/null +++ b/Moose Training/Documentation/Task_Assign.html @@ -0,0 +1,810 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Task_Assign

    + +

    This module contains the TASK_ASSIGN classes.

    + + + +
    + +

    1) TaskAssign#TASKASSIGN_ACCEPT class, extends Task#TASK_BASE

    +

    The TaskAssign#TASKASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task.

    + +

    2) TaskAssign#TASKASSIGNMENUACCEPT class, extends Task#TASK_BASE

    +

    The TaskAssign#TASKASSIGNMENUACCEPT class accepts a task when the player accepts the task through an added menu option. +This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. +The assignment type also allows to reject the task.

    + + + + + + + +

    Global(s)

    +
    TASK.ActiveStageTASK_BASE:AddProcess(TaskUnit, Process) +

    Add Process to Task with key Unit

    +
    TASK_BASE:AddScore(TaskStatus, ScoreText, Score) +

    Adds a score for the TASK to be achieved.

    +
    TASK_BASE:AddStateMachine(TaskUnit, Fsm) +

    Add a FiniteStateMachine to Task with key Unit

    +
    TASK_BASE:AssignToGroup(TaskGroup) +

    Assign the Taskto a Group.

    +
    TASK_BASE:AssignToUnit(TaskUnit) +

    Assign the Taskto an alive Unit.

    +
    TASK_BASE.ClassName
    TASK.AddFlareBlue(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, a BLUE FLARE will be fired by an optional SignalUnitNames.

    -
    TASK.AddFlareGreen(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, a GREEN FLARE will be fired by an optional SignalUnitNames.

    -
    TASK.AddFlareOrange(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, an ORANGE FLARE will be fired by an optional SignalUnitNames.

    -
    TASK.AddFlareRed(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, a RED FLARE will be fired by an optional SignalUnitNames.

    -
    TASK.AddFlareWhite(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, a WHITE FLARE will be fired by an optional SignalUnitNames.

    -
    TASK.AddGoalCompletion(string, string, number, self, GoalVerb, GoalTask, GoalIncrease) -

    Adds an Additional Goal for the TASK to be achieved.

    -
    TASK:AddSignal(SignalUnitNames, SignalType, SignalColor, SignalHeight) -

    Work function to set signal events within a TASK.

    -
    TASK.AddSmokeBlue(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, a BLUE SMOKE will be fired by an optional SignalUnitNames.

    -
    TASK.AddSmokeGreen(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, a GREEN SMOKE will be fired by an optional SignalUnitNames.

    -
    TASK.AddSmokeOrange(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, an ORANGE SMOKE will be fired by an optional SignalUnitNames.

    -
    TASK.AddSmokeRed(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, a RED SMOKE will be fired by an optional SignalUnitNames.

    -
    TASK.AddSmokeWhite(table, number, self, SignalUnitNames, SignalHeight) -

    When the CLIENT is approaching the landing zone, a WHITE SMOKE will be fired by an optional SignalUnitNames.

    -
    TASK.CargosTASK_BASE.Fsm
    TASK.ClassNameTASK_BASE:GetCategory() +

    Gets the Category of the Task

    +
    TASK_BASE:GetID() +

    Gets the ID of the Task

    +
    TASK_BASE:GetName() +

    Gets the Name of the Task

    +
    TASK_BASE:GetScoring() +

    Gets the Scoring of the task

    +
    TASK_BASE:GetStateString() +

    Gets the Task status.

    +
    TASK_BASE:GetType() +

    Gets the Type of the Task

    +
    TASK_BASE:HasStateMachine(TaskUnit) +

    Checks if there is a FiniteStateMachine assigned to Unit for Task

    +
    TASK_BASE:IsStateAssigned() +

    Is the Task status Assigned.

    +
    TASK_BASE:IsStateFailed() +

    Is the Task status Failed.

    +
    TASK_BASE:IsStateHold() +

    Is the Task status Hold.

    +
    TASK_BASE:IsStatePlanned() +

    Is the Task status Planned.

    +
    TASK_BASE:IsStateReplanned() +

    Is the Task status Replanned.

    +
    TASK_BASE:IsStateSuccess() +

    Is the Task status Success.

    +
    TASK_BASE.Mission
    TASK:Done()TASK_BASE:New(Mission, TaskName, TaskType, TaskCategory) -

    Sets a TASK to status Done.

    +

    Instantiates a new TASK_BASE.

    TASK:Failed()TASK_BASE:OnStateChange(Fsm, Event, From, To, Event) -

    Sets a TASK to status failed.

    +

    StateMachine callback function for a TASK

    TASK.GetGoalCompletion(?, self, GoalVerb) -

    Returns if the additional Goal for the TASK was completed.

    -
    TASK.GetGoalCount(?, self, GoalVerb) -

    Gets the total of Goals currently achieved within the TASK of the GoalVerb.

    -
    TASK.GetGoalPercentage(?, self, GoalVerb) -

    Gets the percentage of Goals currently achieved within the TASK of the GoalVerb.

    -
    TASK:GetGoalProgress() -

    Get progress of a TASK.

    -
    TASK.GetGoalTotal(?, self, GoalVerb) -

    Gets the total of Goals to be achieved within the TASK of the GoalVerb.

    -
    TASK:GetGoals() -

    Returns the Goals of a TASK

    -
    TASK:Goal(GoalVerb) -

    Returns if a TASK has Goal(s).

    -
    TASK.GoalTasksTASK_BASE.Players
    TASK.IncreaseGoalCount(number, ?, self, GoalCountIncrease, GoalVerb) -

    Increments the total of Goals currently achieved within the TASK of the GoalVerb, with the given GoalCountIncrease.

    -
    TASK:Init()TASK_BASE.Processes
    TASK:IsDone()TASK_BASE:RemoveProcesses(TaskUnit, FailProcesses) -

    Returns if a TASK is done.

    +

    Remove Processes from Task with key Unit

    TASK:IsFailed()TASK_BASE:RemoveStateMachines(TaskUnit) -

    Returns if a TASk has failed.

    +

    Remove FiniteStateMachines from Task with key Unit

    TASK:IsGoalReached() -

    Returns if all the Goals of the TASK were achieved.

    -
    TASK.LandingZonesTASK_BASE.Scores
    TASK.MenuAction(Parameter)TASK_BASE:SetBriefing(TaskBriefing) +

    Sets a Task briefing.

    +
    TASK_BASE:SetCategory(TaskCategory) +

    Sets the Category of the Task

    +
    TASK_BASE:SetID(TaskID) +

    Sets the ID of the Task

    +
    TASK_BASE:SetName(TaskName) +

    Sets the Name of the Task

    +
    TASK_BASE:SetType(TaskType) +

    Sets the Type of the Task

    +
    TASK_BASE:StateAssigned() +

    Sets a Task to status Assigned.

    +
    TASK_BASE:StateFailed() +

    Sets a Task to status Failed.

    +
    TASK_BASE:StateHold() +

    Sets a Task to status Hold.

    +
    TASK_BASE:StatePlanned() +

    Sets a Task to status Planned.

    +
    TASK_BASE:StateReplanned() +

    Sets a Task to status Replanned.

    +
    TASK_BASE:StateSuccess() +

    Sets a Task to status Success.

    +
    TASK_BASE.TaskBriefing
    TASK.MissionTASK_BASE.TaskCategory
    TASK.NameTASK_BASE.TaskID
    TASK:New() -

    Instantiates a new TASK Base.

    -
    TASK:Reset(Mission, Client)TASK_BASE.TaskName
    TASK.SIGNALTASK_BASE.TaskScheduler
    TASK.SetGoalCount(number, ?, self, GoalCount, GoalVerb) -

    Sets the total of Goals currently achieved within the TASK of the GoalVerb.

    -
    TASK.SetGoalTotal(number, ?, self, GoalTotal, GoalVerb) -

    Sets the total Goals to be achieved of the Goal Name

    -
    TASK:SetStage(StageSequenceIncrement)TASK_BASE.TaskType
    TASK.ShowGoalProgress(MISSION, CLIENT, self, Mission, Client) -

    Show progress of a TASK.

    -
    TASK.StageTASK_BASE.TaskUnit
    TASK:StageExecute()TASK_BASE:UnAssignFromUnit(TaskUnit, FailProcesses) +

    UnAssign the Task from an alive Unit.

    +
    TASK_BASE:_EventAssignUnit(Event) +

    Register a potential new assignment for a new spawned Unit.

    +
    TASK_BASE:_EventUnAssignUnit(Event) +

    UnAssigns a Unit that is left by a player, crashed, dead, ....

    +
    TASK_BASE:_Schedule()
    TASK.Stages - -
    TASK.TaskDone - -
    TASK.TaskFailedTASK_BASE:_Scheduler()
    + + + + + + + + +
    PROCESS_ASSIGN_ACCEPT + +
    PROCESS_ASSIGN_MENU_ACCEPT + +
    +

    Type PROCESS_ASSIGN_ACCEPT

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PROCESS_ASSIGN_ACCEPT.ClassName + +
    PROCESS_ASSIGN_ACCEPT.Fsm + +
    PROCESS_ASSIGN_ACCEPT:New(Task, Unit, ProcessUnit, TaskBriefing) +

    Creates a new task assignment state machine.

    +
    PROCESS_ASSIGN_ACCEPT:OnAssign(Fsm, Event, From, To) +

    StateMachine callback function for a TASK2

    +
    PROCESS_ASSIGN_ACCEPT.ProcessUnit + +
    PROCESS_ASSIGN_ACCEPT.TargetZone + +
    PROCESS_ASSIGN_ACCEPT.Task + +
    PROCESS_ASSIGN_ACCEPT.TaskBriefing + +
    + +

    Type PROCESS_ASSIGN_MENU_ACCEPT

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PROCESS_ASSIGN_MENU_ACCEPT.ClassName + +
    PROCESS_ASSIGN_MENU_ACCEPT.Fsm + +
    PROCESS_ASSIGN_MENU_ACCEPT.Menu + +
    PROCESS_ASSIGN_MENU_ACCEPT.MenuAcceptTask + +
    PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() +

    Menu function.

    +
    PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() +

    Menu function.

    +
    PROCESS_ASSIGN_MENU_ACCEPT.MenuRejectTask + +
    PROCESS_ASSIGN_MENU_ACCEPT.MenuText + +
    PROCESS_ASSIGN_MENU_ACCEPT:New(Task, Unit, ProcessUnit, TaskBriefing) +

    Creates a new task assignment state machine.

    +
    PROCESS_ASSIGN_MENU_ACCEPT:OnAssign(Fsm, Event, From, To) +

    StateMachine callback function for a TASK2

    +
    PROCESS_ASSIGN_MENU_ACCEPT:OnReject(Fsm, Event, From, To) +

    StateMachine callback function for a TASK2

    +
    PROCESS_ASSIGN_MENU_ACCEPT:OnStart(Fsm, Event, From, To) +

    StateMachine callback function for a TASK2

    +
    PROCESS_ASSIGN_MENU_ACCEPT.ProcessUnit + +
    PROCESS_ASSIGN_MENU_ACCEPT.TargetZone + +
    PROCESS_ASSIGN_MENU_ACCEPT.Task + +
    PROCESS_ASSIGN_MENU_ACCEPT.TaskBriefing + +
    + +

    Global(s)

    +
    +
    + + #PROCESS_ASSIGN_ACCEPT + +PROCESS_ASSIGN_ACCEPT + +
    +
    + + + +
    +
    +
    +
    + + #PROCESS_ASSIGN_MENU_ACCEPT + +PROCESS_ASSIGN_MENU_ACCEPT + +
    +
    + + + +
    +
    +

    Type Task_Assign

    + +

    Type PROCESS_ASSIGN_ACCEPT

    + +

    PROCESSASSIGNACCEPT class

    + +

    Field(s)

    +
    +
    + + #string + +PROCESS_ASSIGN_ACCEPT.ClassName + +
    +
    + + + +
    +
    +
    +
    + + + +PROCESS_ASSIGN_ACCEPT.Fsm + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS_ASSIGN_ACCEPT:New(Task, Unit, ProcessUnit, TaskBriefing) + +
    +
    + +

    Creates a new task assignment state machine.

    + + +

    The process will accept the task by default, no player intervention accepted.

    + +

    Parameters

    +
      +
    • + +

      Task#TASK Task :

      + +
    • +
    • + +

      Unit#UNIT Unit :

      + +
    • +
    • + +

      ProcessUnit :

      + +
    • +
    • + +

      TaskBriefing :

      + +
    • +
    +

    Return value

    + +

    #PROCESSASSIGNACCEPT: +self

    + +
    +
    +
    +
    + + +PROCESS_ASSIGN_ACCEPT:OnAssign(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a TASK2

    + +

    Parameters

    + +
    +
    +
    +
    + + Unit#UNIT + +PROCESS_ASSIGN_ACCEPT.ProcessUnit + +
    +
    + + + +
    +
    +
    +
    + + Zone#ZONE_BASE + +PROCESS_ASSIGN_ACCEPT.TargetZone + +
    +
    + + + +
    +
    +
    +
    + + Task#TASK_BASE + +PROCESS_ASSIGN_ACCEPT.Task + +
    +
    + + + +
    +
    +
    +
    + + + +PROCESS_ASSIGN_ACCEPT.TaskBriefing + +
    +
    + + + +
    +
    + +

    Type PROCESS_ASSIGN_MENU_ACCEPT

    + +

    PROCESSASSIGNMENU_ACCEPT class

    + +

    Field(s)

    +
    +
    + + #string + +PROCESS_ASSIGN_MENU_ACCEPT.ClassName + +
    +
    + + + +
    +
    +
    +
    + + + +PROCESS_ASSIGN_MENU_ACCEPT.Fsm + +
    +
    + + + +
    +
    +
    +
    + + + +PROCESS_ASSIGN_MENU_ACCEPT.Menu + +
    +
    + + + +
    +
    +
    +
    + + + +PROCESS_ASSIGN_MENU_ACCEPT.MenuAcceptTask + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() + +
    +
    + +

    Menu function.

    + +
    +
    +
    +
    + + +PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() + +
    +
    + +

    Menu function.

    + +
    +
    +
    +
    + + + +PROCESS_ASSIGN_MENU_ACCEPT.MenuRejectTask + +
    +
    + + + +
    +
    +
    +
    + + + +PROCESS_ASSIGN_MENU_ACCEPT.MenuText + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS_ASSIGN_MENU_ACCEPT:New(Task, Unit, ProcessUnit, TaskBriefing) + +
    +
    + +

    Creates a new task assignment state machine.

    + + +

    The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.

    + +

    Parameters

    +
      +
    • + +

      Task#TASK Task :

      + +
    • +
    • + +

      Unit#UNIT Unit :

      + +
    • +
    • + +

      ProcessUnit :

      + +
    • +
    • + +

      TaskBriefing :

      + +
    • +
    +

    Return value

    + +

    #PROCESSASSIGNMENU_ACCEPT: +self

    + +
    +
    +
    +
    + + +PROCESS_ASSIGN_MENU_ACCEPT:OnAssign(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a TASK2

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_ASSIGN_MENU_ACCEPT:OnReject(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a TASK2

    + +

    Parameters

    + +
    +
    +
    +
    + + +PROCESS_ASSIGN_MENU_ACCEPT:OnStart(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a TASK2

    + +

    Parameters

    + +
    +
    +
    +
    + + Unit#UNIT + +PROCESS_ASSIGN_MENU_ACCEPT.ProcessUnit + +
    +
    + + + +
    +
    +
    +
    + + Zone#ZONE_BASE + +PROCESS_ASSIGN_MENU_ACCEPT.TargetZone + +
    +
    + + + +
    +
    +
    +
    + + Task#TASK_BASE + +PROCESS_ASSIGN_MENU_ACCEPT.Task + +
    +
    + + + +
    +
    +
    +
    + + + +PROCESS_ASSIGN_MENU_ACCEPT.TaskBriefing + +
    +
    + + + +
    +
    + +
    + + + + diff --git a/Moose Training/Documentation/Task_CAS.html b/Moose Training/Documentation/Task_CAS.html new file mode 100644 index 000000000..a1a1a46ba --- /dev/null +++ b/Moose Training/Documentation/Task_CAS.html @@ -0,0 +1,364 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Task_CAS

    + +

    This module contains the TASK_CAS classes.

    + + + +

    1) #TASK_CAS class, extends Task#TASK_BASE

    +

    The #TASK_CAS class defines a new CAS task of a Set of Target Units, located at a Target Zone, based on the tasking capabilities defined in Task#TASK_BASE. +The TASK_CAS is processed through a Statemachine#STATEMACHINE_TASK, and has the following statuses:

    + +
      +
    • None: Start of the process
    • +
    • Planned: The SEAD task is planned. Upon Planned, the sub-process ProcessAssign#PROCESSASSIGN_ACCEPT is started to accept the task.
    • +
    • Assigned: The SEAD task is assigned to a Group#GROUP. Upon Assigned, the sub-process ProcessRoute#PROCESSROUTE is started to route the active Units in the Group to the attack zone.
    • +
    • Success: The SEAD task is successfully completed. Upon Success, the sub-process ProcessSEAD#PROCESSSEAD is started to follow-up successful SEADing of the targets assigned in the task.
    • +
    • Failed: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
    • +
    + +
    + +

    Authors: FlightControl - Design and Programming

    + + +

    Global(s)

    + + + + + +
    TASK_CAS + +
    +

    Type TASK_CAS

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TASK_CAS:AssignToUnit(TaskUnit) +

    Assign the Task to a Unit.

    +
    TASK_CAS.ClassName + +
    TASK_CAS:New(Mission, UnitSetTargets, TargetZone, MenuText, TargetSetUnit) +

    Instantiates a new TASK_CAS.

    +
    TASK_CAS:OnNext(Fsm, Event, From, To, Event) +

    StateMachine callback function for a TASK

    +
    TASK_CAS.TaskScheduler + +
    TASK_CAS:_Schedule() + +
    TASK_CAS:_Scheduler() + +
    + +

    Global(s)

    +
    +
    + + #TASK_CAS + +TASK_CAS + +
    +
    + + + +
    +
    +

    Type Task_CAS

    + +

    Type TASK_CAS

    + +

    The TASK_CAS class

    + +

    Field(s)

    +
    +
    + + +TASK_CAS:AssignToUnit(TaskUnit) + +
    +
    + +

    Assign the Task to a Unit.

    + +

    Parameter

    + +

    Return value

    + +

    #TASK_CAS: +self

    + +
    +
    +
    +
    + + #string + +TASK_CAS.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +TASK_CAS:New(Mission, UnitSetTargets, TargetZone, MenuText, TargetSetUnit) + +
    +
    + +

    Instantiates a new TASK_CAS.

    + +

    Parameters

    + +

    Return value

    + +

    #TASK_CAS: +self

    + +
    +
    +
    +
    + + +TASK_CAS:OnNext(Fsm, Event, From, To, Event) + +
    +
    + +

    StateMachine callback function for a TASK

    + +

    Parameters

    + +
    +
    +
    +
    + + + +TASK_CAS.TaskScheduler + +
    +
    + + + +
    +
    +
    +
    + + +TASK_CAS:_Schedule() + +
    +
    + + + +
    +
    +
    +
    + + +TASK_CAS:_Scheduler() + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/Task_Client_Menu.html b/Moose Training/Documentation/Task_Client_Menu.html new file mode 100644 index 000000000..58cd3d482 --- /dev/null +++ b/Moose Training/Documentation/Task_Client_Menu.html @@ -0,0 +1,422 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Task_Client_Menu

    + + + +

    Global(s)

    + + + + + +
    TASK2_MENU_CLIENT + +
    +

    Type TASK2_MENU_CLIENT

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TASK2_MENU_CLIENT.ClassName + +
    TASK2_MENU_CLIENT.Fsm + +
    TASK2_MENU_CLIENT.Menu + +
    TASK2_MENU_CLIENT:MenuAssign() +

    Menu function.

    +
    TASK2_MENU_CLIENT.MenuTask + +
    TASK2_MENU_CLIENT.MenuText + +
    TASK2_MENU_CLIENT:New(Mission, TaskUnit, MenuText) +

    Creates a new MENU handling machine.

    +
    TASK2_MENU_CLIENT:OnAssign(Fsm, Event, From, To) +

    StateMachine callback function for a TASK2

    +
    TASK2_MENU_CLIENT:OnMenu(Fsm, Event, From, To) +

    StateMachine callback function for a TASK2

    +
    TASK2_MENU_CLIENT.TargetSet + +
    TASK2_MENU_CLIENT.TaskUnit + +
    + +

    Global(s)

    +
    +
    + + #TASK2_MENU_CLIENT + +TASK2_MENU_CLIENT + +
    +
    + + + +
    +
    +

    Type Task_Client_Menu

    + +

    Type TASK2_MENU_CLIENT

    + +

    TASK2MENUCLIENT class

    + +

    Field(s)

    +
    +
    + + #string + +TASK2_MENU_CLIENT.ClassName + +
    +
    + + + +
    +
    +
    +
    + + + +TASK2_MENU_CLIENT.Fsm + +
    +
    + + + +
    +
    +
    +
    + + + +TASK2_MENU_CLIENT.Menu + +
    +
    + + + +
    +
    +
    +
    + + +TASK2_MENU_CLIENT:MenuAssign() + +
    +
    + +

    Menu function.

    + +
    +
    +
    +
    + + Menu#MENU_CLIENT_COMMAND + +TASK2_MENU_CLIENT.MenuTask + +
    +
    + + + +
    +
    +
    +
    + + + +TASK2_MENU_CLIENT.MenuText + +
    +
    + + + +
    +
    +
    +
    + + +TASK2_MENU_CLIENT:New(Mission, TaskUnit, MenuText) + +
    +
    + +

    Creates a new MENU handling machine.

    + +

    Parameters

    +
      +
    • + +

      Mission#MISSION Mission :

      + +
    • +
    • + +

      Unit#UNIT TaskUnit :

      + +
    • +
    • + +

      #string MenuText : +The text of the menu item.

      + +
    • +
    +

    Return value

    + +

    #TASK2MENUCLIENT: +self

    + +
    +
    +
    +
    + + +TASK2_MENU_CLIENT:OnAssign(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a TASK2

    + +

    Parameters

    + +
    +
    +
    +
    + + +TASK2_MENU_CLIENT:OnMenu(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a TASK2

    + +

    Parameters

    + +
    +
    +
    +
    + + Set#SET_UNIT + +TASK2_MENU_CLIENT.TargetSet + +
    +
    + + + +
    +
    +
    +
    + + Unit#UNIT + +TASK2_MENU_CLIENT.TaskUnit + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/Task_Route.html b/Moose Training/Documentation/Task_Route.html new file mode 100644 index 000000000..16f3fa151 --- /dev/null +++ b/Moose Training/Documentation/Task_Route.html @@ -0,0 +1,434 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Task_Route

    + + + +

    Global(s)

    + + + + + +
    PROCESS_ROUTE + +
    +

    Type PROCESS_ROUTE

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PROCESS_ROUTE.ClassName + +
    PROCESS_ROUTE.DisplayCategory + +
    PROCESS_ROUTE.DisplayCount + +
    PROCESS_ROUTE.DisplayInterval + +
    PROCESS_ROUTE.DisplayMessage + +
    PROCESS_ROUTE.DisplayTime + +
    PROCESS_ROUTE.Fsm + +
    PROCESS_ROUTE:New(Task, Unit, ProcessUnit, TargetZone) +

    Creates a new routing state machine.

    +
    PROCESS_ROUTE:OnLeaveUnArrived(Fsm, Event, From, To) +

    StateMachine callback function for a TASK2

    +
    PROCESS_ROUTE.ProcessUnit + +
    PROCESS_ROUTE.TASK + +
    PROCESS_ROUTE.TargetZone + +
    + +

    Global(s)

    +
    +
    + + #PROCESS_ROUTE + +PROCESS_ROUTE + +
    +
    + + + +
    +
    +

    Type Task_Route

    + +

    Type PROCESS_ROUTE

    + +

    PROCESS_ROUTE class

    + +

    Field(s)

    +
    +
    + + #string + +PROCESS_ROUTE.ClassName + +
    +
    + + + +
    +
    +
    +
    + + #string + +PROCESS_ROUTE.DisplayCategory + +
    +
    + + + + +

    Route is the default display category

    + +
    +
    +
    +
    + + #number + +PROCESS_ROUTE.DisplayCount + +
    +
    + + + +
    +
    +
    +
    + + #number + +PROCESS_ROUTE.DisplayInterval + +
    +
    + + + +
    +
    +
    +
    + + #boolean + +PROCESS_ROUTE.DisplayMessage + +
    +
    + + + +
    +
    +
    +
    + + #number + +PROCESS_ROUTE.DisplayTime + +
    +
    + + + + +

    10 seconds is the default

    + +
    +
    +
    +
    + + + +PROCESS_ROUTE.Fsm + +
    +
    + + + +
    +
    +
    +
    + + +PROCESS_ROUTE:New(Task, Unit, ProcessUnit, TargetZone) + +
    +
    + +

    Creates a new routing state machine.

    + + +

    The task will route a CLIENT to a ZONE until the CLIENT is within that ZONE.

    + +

    Parameters

    +
      +
    • + +

      Task#TASK Task :

      + +
    • +
    • + +

      Unit#UNIT Unit :

      + +
    • +
    • + +

      ProcessUnit :

      + +
    • +
    • + +

      TargetZone :

      + +
    • +
    +

    Return value

    + +

    #PROCESS_ROUTE: +self

    + +
    +
    +
    +
    + + +PROCESS_ROUTE:OnLeaveUnArrived(Fsm, Event, From, To) + +
    +
    + +

    StateMachine callback function for a TASK2

    + +

    Parameters

    + +
    +
    +
    +
    + + Unit#UNIT + +PROCESS_ROUTE.ProcessUnit + +
    +
    + + + +
    +
    +
    +
    + + Task#TASK + +PROCESS_ROUTE.TASK + +
    +
    + + + +
    +
    +
    +
    + + Zone#ZONE_BASE + +PROCESS_ROUTE.TargetZone + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/Task_SEAD.html b/Moose Training/Documentation/Task_SEAD.html new file mode 100644 index 000000000..12a76c59d --- /dev/null +++ b/Moose Training/Documentation/Task_SEAD.html @@ -0,0 +1,364 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module Task_SEAD

    + +

    This module contains the TASK_SEAD classes.

    + + + +

    1) #TASK_SEAD class, extends Task#TASK_BASE

    +

    The #TASK_SEAD class defines a new SEAD task of a Set of Target Units, located at a Target Zone, based on the tasking capabilities defined in Task#TASK_BASE. +The TASK_SEAD is processed through a Statemachine#STATEMACHINE_TASK, and has the following statuses:

    + +
      +
    • None: Start of the process
    • +
    • Planned: The SEAD task is planned. Upon Planned, the sub-process ProcessAssign#PROCESSASSIGN_ACCEPT is started to accept the task.
    • +
    • Assigned: The SEAD task is assigned to a Group#GROUP. Upon Assigned, the sub-process ProcessRoute#PROCESSROUTE is started to route the active Units in the Group to the attack zone.
    • +
    • Success: The SEAD task is successfully completed. Upon Success, the sub-process ProcessSEAD#PROCESSSEAD is started to follow-up successful SEADing of the targets assigned in the task.
    • +
    • Failed: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
    • +
    + +
    + +

    Authors: FlightControl - Design and Programming

    + + +

    Global(s)

    + + + + + +
    TASK_SEAD + +
    +

    Type TASK_SEAD

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TASK_SEAD:AssignToUnit(TaskUnit) +

    Assign the Task to a Unit.

    +
    TASK_SEAD.ClassName + +
    TASK_SEAD:New(Mission, UnitSetTargets, TargetZone, MenuText, TargetSetUnit) +

    Instantiates a new TASK_SEAD.

    +
    TASK_SEAD:OnNext(Fsm, Event, From, To, Event) +

    StateMachine callback function for a TASK

    +
    TASK_SEAD.TaskScheduler + +
    TASK_SEAD:_Schedule() + +
    TASK_SEAD:_Scheduler() + +
    + +

    Global(s)

    +
    +
    + + #TASK_SEAD + +TASK_SEAD + +
    +
    + + + +
    +
    +

    Type Task_SEAD

    + +

    Type TASK_SEAD

    + +

    The TASK_SEAD class

    + +

    Field(s)

    +
    +
    + + +TASK_SEAD:AssignToUnit(TaskUnit) + +
    +
    + +

    Assign the Task to a Unit.

    + +

    Parameter

    + +

    Return value

    + +

    #TASK_SEAD: +self

    + +
    +
    +
    +
    + + #string + +TASK_SEAD.ClassName + +
    +
    + + + +
    +
    +
    +
    + + +TASK_SEAD:New(Mission, UnitSetTargets, TargetZone, MenuText, TargetSetUnit) + +
    +
    + +

    Instantiates a new TASK_SEAD.

    + +

    Parameters

    + +

    Return value

    + +

    #TASK_SEAD: +self

    + +
    +
    +
    +
    + + +TASK_SEAD:OnNext(Fsm, Event, From, To, Event) + +
    +
    + +

    StateMachine callback function for a TASK

    + +

    Parameters

    + +
    +
    +
    +
    + + + +TASK_SEAD.TaskScheduler + +
    +
    + + + +
    +
    +
    +
    + + +TASK_SEAD:_Schedule() + +
    +
    + + + +
    +
    +
    +
    + + +TASK_SEAD:_Scheduler() + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/Unit.html b/Moose Training/Documentation/Unit.html index 1a3baca8f..9642a536d 100644 --- a/Moose Training/Documentation/Unit.html +++ b/Moose Training/Documentation/Unit.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -163,6 +173,12 @@ If you want to obtain the complete 3D position including ori UNIT.ClassName + + + + UNIT:Destroy() + +

    Destroys the Unit.

    @@ -287,6 +303,12 @@ If you want to obtain the complete 3D position including ori UNIT:GetSensors()

    Returns the unit sensors.

    + + + + UNIT:HasSensors(...) + +

    Returns if the unit has sensors of a certain type.

    @@ -299,6 +321,12 @@ If you want to obtain the complete 3D position including ori UNIT:IsAir()

    Returns if the unit is of an air category.

    + + + + UNIT:IsGround() + +

    Returns if the unit is of an ground category.

    @@ -311,6 +339,12 @@ If you want to obtain the complete 3D position including ori UNIT:IsNotInZone(Zone)

    Returns true if the unit is not within a Zone.

    + + + + UNIT:IsShip() + +

    Returns if the unit is of a ship category.

    @@ -427,6 +461,34 @@ If you want to obtain the complete 3D position including ori UNIT.SmokeColor.White + + + + +

    Type Unit.SensorType

    + + + + + + + + + + + + + + + +
    Unit.SensorType.IRST + +
    Unit.SensorType.OPTIC + +
    Unit.SensorType.RADAR + +
    Unit.SensorType.RWR +
    @@ -465,6 +527,24 @@ If you want to obtain the complete 3D position including ori + +
    +
    +
    + + +UNIT:Destroy() + +
    +
    + +

    Destroys the Unit.

    + +

    Return value

    + +

    #nil: +The DCS Unit is not existing or alive.

    +
    @@ -968,6 +1048,42 @@ The DCS Unit is not existing or alive.

    + +UNIT:HasSensors(...) + +
    +
    + +

    Returns if the unit has sensors of a certain type.

    + +

    Parameter

    +
      +
    • + +

      ... :

      + +
    • +
    +

    Return values

    +
      +
    1. + +

      #boolean: +returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors.

      + +
    2. +
    3. + +

      #nil: +The DCS Unit is not existing or alive.

      + +
    4. +
    +
    +
    +
    +
    + UNIT:IsActive() @@ -1017,6 +1133,27 @@ Air category evaluation result.

    + +UNIT:IsGround() + +
    +
    + +

    Returns if the unit is of an ground category.

    + + +

    If the unit is a ground vehicle or infantry, this method will return true, otherwise false.

    + +

    Return value

    + +

    #boolean: +Ground category evaluation result.

    + +
    +
    +
    +
    + UNIT:IsInZone(Zone) @@ -1071,6 +1208,27 @@ Returns true if the unit is not within the Zone
    + +UNIT:IsShip() + +
    +
    + +

    Returns if the unit is of a ship category.

    + + +

    If the unit is a ship, this method will return true, otherwise false.

    + +

    Return value

    + +

    #boolean: +Ship category evaluation result.

    + +
    +
    +
    +
    + UNIT:OtherUnitInRadius(AwaitUnit, Radius) @@ -1365,6 +1523,64 @@ The name of the DCS unit.

    + +
    + +

    Type Unit.SensorType

    + +

    Unit.SensorType

    + +

    Field(s)

    +
    +
    + + +Unit.SensorType.IRST + +
    +
    + + + +
    +
    +
    +
    + + +Unit.SensorType.OPTIC + +
    +
    + + + +
    +
    +
    +
    + + +Unit.SensorType.RADAR + +
    +
    + + + +
    +
    +
    +
    + + +Unit.SensorType.RWR + +
    +
    + + +
    diff --git a/Moose Training/Documentation/Zone.html b/Moose Training/Documentation/Zone.html index 9fbef93d4..41b642783 100644 --- a/Moose Training/Documentation/Zone.html +++ b/Moose Training/Documentation/Zone.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -235,7 +245,13 @@ - ZONE_BASE:IsPointVec2InZone(PointVec2) + ZONE_BASE:GetVec2() + +

    Returns the Vec2 coordinate of the zone.

    + + + + ZONE_BASE:IsPointVec2InZone(Vec2)

    Returns if a location is within the zone.

    @@ -371,7 +387,7 @@ - ZONE_POLYGON_BASE:IsPointVec2InZone(PointVec2) + ZONE_POLYGON_BASE:IsPointVec2InZone(Vec2)

    Returns if a location is within the zone.

    @@ -408,12 +424,6 @@ ZONE_RADIUS:FlareZone(FlareColor, Points, Azimuth)

    Flares the zone boundaries in a color.

    - - - - ZONE_RADIUS:GetPointVec2() - -

    Returns the location of the zone.

    @@ -435,27 +445,27 @@ - ZONE_RADIUS:IsPointVec2InZone(PointVec2) + ZONE_RADIUS:GetVec2() + +

    Returns the location of the zone.

    + + + + ZONE_RADIUS:IsPointVec2InZone(Vec2)

    Returns if a location is within the zone.

    - ZONE_RADIUS:IsPointVec3InZone(PointVec3) + ZONE_RADIUS:IsPointVec3InZone(PointVec3, Vec3)

    Returns if a point is within the zone.

    - ZONE_RADIUS:New(ZoneName, PointVec2, Radius) + ZONE_RADIUS:New(ZoneName, Vec2, Radius)

    Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius.

    - - - - ZONE_RADIUS.PointVec2 - -

    The current location of the zone.

    @@ -465,7 +475,7 @@ - ZONE_RADIUS:SetPointVec2(PointVec2) + ZONE_RADIUS:SetPointVec2(Vec2)

    Sets the location of the zone.

    @@ -480,6 +490,12 @@ ZONE_RADIUS:SmokeZone(SmokeColor, Points)

    Smokes the zone boundaries in a color.

    + + + + ZONE_RADIUS.Vec2 + +

    The current location of the zone.

    @@ -490,18 +506,18 @@ ZONE_UNIT.ClassName - - - - ZONE_UNIT:GetPointVec2() - -

    Returns the current location of the Unit#UNIT.

    ZONE_UNIT:GetRandomVec2()

    Returns a random location within the zone.

    + + + + ZONE_UNIT:GetVec2() + +

    Returns the current location of the Unit#UNIT.

    @@ -705,7 +721,7 @@ The name of the zone as defined within the mission editor.

    Return value

    -

    #ZONE_BASE.BoundingSquare: +

    #nil: The bounding square.

    @@ -723,16 +739,34 @@ The bounding square.

    Return value

    -

    DCSTypes#Vec2: +

    #nil: The Vec2 coordinates.

    + +
    +
    +
    + + +ZONE_BASE:GetVec2() + +
    +
    + +

    Returns the Vec2 coordinate of the zone.

    + +

    Return value

    + +

    #nil:

    + +
    -ZONE_BASE:IsPointVec2InZone(PointVec2) +ZONE_BASE:IsPointVec2InZone(Vec2)
    @@ -743,7 +777,7 @@ The Vec2 coordinates.

    -
    -
    - - -ZONE_UNIT:GetPointVec2() - -
    -
    - -

    Returns the current location of the Unit#UNIT.

    - -

    Return value

    - -

    DCSTypes#Vec2: -The location of the zone based on the Unit#UNITlocation.

    -
    @@ -1667,6 +1688,24 @@ The random location within the zone.

    + +ZONE_UNIT:GetVec2() + +
    +
    + +

    Returns the current location of the Unit#UNIT.

    + +

    Return value

    + +

    DCSTypes#Vec2: +The location of the zone based on the Unit#UNITlocation.

    + +
    +
    +
    +
    + ZONE_UNIT:New(ZoneName, ZoneUNIT, Radius) diff --git a/Moose Training/Documentation/env.html b/Moose Training/Documentation/env.html index f4f96a2cc..457a49cb7 100644 --- a/Moose Training/Documentation/env.html +++ b/Moose Training/Documentation/env.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/index.html b/Moose Training/Documentation/index.html index b883d3908..e48b51011 100644 --- a/Moose Training/Documentation/index.html +++ b/Moose Training/Documentation/index.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • @@ -241,6 +251,12 @@ Detection

    This module contains the DETECTION classes.

    + + + + DetectionManager + +

    This module contains the DETECTION_MANAGER class and derived classes.

    @@ -253,12 +269,6 @@ Event

    The EVENT class models an efficient event handling process between other classes and its units, weapons.

    - - - - Fac - -

    This module contains the FAC classes.

    @@ -343,6 +353,24 @@ Positionable

    This module contains the POSITIONABLE class.

    + + + + Process + + + + + + Process_CAS + + + + + + Process_SEAD + + @@ -385,6 +413,12 @@ Spawn

    This module contains the SPAWN class.

    + + + + StateMachine + +

    This module contains the STATEMACHINE class.

    @@ -403,6 +437,42 @@ TASK

    The TASK Classes define major end-to-end activities within a MISSION.

    + + + + Task + +

    This module contains the TASK_BASE class.

    + + + + Task_Assign + +

    This module contains the TASK_ASSIGN classes.

    + + + + Task_CAS + +

    This module contains the TASK_CAS classes.

    + + + + Task_Client_Menu + + + + + + Task_Route + + + + + + Task_SEAD + +

    This module contains the TASK_SEAD classes.

    diff --git a/Moose Training/Documentation/land.html b/Moose Training/Documentation/land.html index d8d6da2c2..3e13a6a45 100644 --- a/Moose Training/Documentation/land.html +++ b/Moose Training/Documentation/land.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Documentation/routines.html b/Moose Training/Documentation/routines.html index 0c86aabda..92c6233d8 100644 --- a/Moose Training/Documentation/routines.html +++ b/Moose Training/Documentation/routines.html @@ -44,9 +44,9 @@
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • +
  • DetectionManager
  • Escort
  • Event
  • -
  • Fac
  • GOHOMETASK
  • Group
  • Identifiable
  • @@ -61,6 +61,9 @@
  • PatrolZone
  • Point
  • Positionable
  • +
  • Process
  • +
  • Process_CAS
  • +
  • Process_SEAD
  • ROUTETASK
  • STAGE
  • Scheduler
  • @@ -68,9 +71,16 @@
  • Sead
  • Set
  • Spawn
  • +
  • StateMachine
  • Static
  • StaticObject
  • TASK
  • +
  • Task
  • +
  • Task_Assign
  • +
  • Task_CAS
  • +
  • Task_Client_Menu
  • +
  • Task_Route
  • +
  • Task_SEAD
  • Unit
  • Zone
  • env
  • diff --git a/Moose Training/Presentations/DCS World - MOOSE - Detection - Part 1 - Explanation - Old.pptx b/Moose Training/Presentations/DCS World - MOOSE - Detection - Part 1 - Explanation - Old.pptx new file mode 100644 index 000000000..522240385 Binary files /dev/null and b/Moose Training/Presentations/DCS World - MOOSE - Detection - Part 1 - Explanation - Old.pptx differ diff --git a/Moose Training/Presentations/DCS World - MOOSE - Detection - Part 1 - Explanation.pptx b/Moose Training/Presentations/DCS World - MOOSE - Detection - Part 1 - Explanation.pptx new file mode 100644 index 000000000..c3ac9b7f0 Binary files /dev/null and b/Moose Training/Presentations/DCS World - MOOSE - Detection - Part 1 - Explanation.pptx differ diff --git a/Moose Training/Presentations/DCS World - MOOSE - Detection and Fac.pptx b/Moose Training/Presentations/DCS World - MOOSE - Detection and Fac.pptx new file mode 100644 index 000000000..4c578e417 Binary files /dev/null and b/Moose Training/Presentations/DCS World - MOOSE - Detection and Fac.pptx differ diff --git a/Moose Training/Presentations/DCS World - MOOSE - Tasking - SEAD.pptx b/Moose Training/Presentations/DCS World - MOOSE - Tasking - SEAD.pptx index 5509407bb..329c0710a 100644 Binary files a/Moose Training/Presentations/DCS World - MOOSE - Tasking - SEAD.pptx and b/Moose Training/Presentations/DCS World - MOOSE - Tasking - SEAD.pptx differ diff --git a/Moose Training/Presentations/NATO SYMBOLS/basic-unit-and-operationa.ppt b/Moose Training/Presentations/NATO SYMBOLS/basic-unit-and-operationa.ppt new file mode 100644 index 000000000..6cdfa0360 Binary files /dev/null and b/Moose Training/Presentations/NATO SYMBOLS/basic-unit-and-operationa.ppt differ