diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 6d6ac18df..a0f4fc877 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2970,8 +2970,8 @@ do -- COORDINATE -- * Uses default settings in COORDINATE. -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self - -- @param #COORDINATE ReferenceCoord The refrence coordinate. - -- @param #string ReferenceName The refrence name. + -- @param #COORDINATE ReferenceCoord The reference coordinate. + -- @param #string ReferenceName The reference name. -- @param Wrapper.Controllable#CONTROLLABLE Controllable -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. @@ -2998,7 +2998,40 @@ do -- COORDINATE return nil end + + --- Provides a coordinate string of the point, based on a coordinate format system: + -- * Uses default settings in COORDINATE. + -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. + -- @param #COORDINATE self + -- @param #COORDINATE ReferenceCoord The reference coordinate. + -- @param #string ReferenceName The reference name. + -- @param Wrapper.Controllable#CONTROLLABLE Controllable + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @return #string The coordinate Text in the configured coordinate system. + function COORDINATE:ToStringFromRPShort( ReferenceCoord, ReferenceName, Controllable, Settings ) + self:F2( { ReferenceCoord = ReferenceCoord, ReferenceName = ReferenceName } ) + + local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS + + local IsAir = Controllable and Controllable:IsAirPlane() or false + + if IsAir then + local DirectionVec3 = ReferenceCoord:GetDirectionVec3( self ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + local Distance = self:Get2DDistance( ReferenceCoord ) + return self:GetBRText( AngleRadians, Distance, Settings ) .. " from " .. ReferenceName + else + local DirectionVec3 = ReferenceCoord:GetDirectionVec3( self ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + local Distance = self:Get2DDistance( ReferenceCoord ) + return self:GetBRText( AngleRadians, Distance, Settings ) .. " from " .. ReferenceName + end + + return nil + + end + --- Provides a coordinate string of the point, based on the A2G coordinate format system. -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 1f43c2659..4e279d56f 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -813,14 +813,31 @@ do -- SET_BASE return true end - --- Decides whether to include the Object. + --- Decides whether an object is in the SET -- @param #SET_BASE self -- @param #table Object -- @return #SET_BASE self - function SET_BASE:IsInSet( ObjectName ) + function SET_BASE:IsInSet( Object ) self:F3( Object ) - - return true + local outcome = false + local name = Object:GetName() + self:ForEach( + function(object) + if object:GetName() == name then + outcome = true + end + end + ) + return outcome + end + + --- Decides whether an object is **not** in the SET + -- @param #SET_BASE self + -- @param #table Object + -- @return #SET_BASE self + function SET_BASE:IsNotInSet( Object ) + self:F3( Object ) + return not self:IsInSet(Object) end --- Gets a string with all the object names. diff --git a/Moose Development/Moose/Core/Spot.lua b/Moose Development/Moose/Core/Spot.lua index b8dfa0e75..f4dc1f967 100644 --- a/Moose Development/Moose/Core/Spot.lua +++ b/Moose Development/Moose/Core/Spot.lua @@ -249,8 +249,10 @@ do local RecceDcsUnit = self.Recce:GetDCSObject() - self.SpotIR = Spot.createInfraRed( RecceDcsUnit, { x = 0, y = 2, z = 0 }, Target:GetPointVec3():AddY(1):GetVec3() ) - self.SpotLaser = Spot.createLaser( RecceDcsUnit, { x = 0, y = 2, z = 0 }, Target:GetPointVec3():AddY(1):GetVec3(), LaserCode ) + local relativespot = self.relstartpos or { x = 0, y = 2, z = 0 } + + self.SpotIR = Spot.createInfraRed( RecceDcsUnit, relativespot, Target:GetPointVec3():AddY(1):GetVec3() ) + self.SpotLaser = Spot.createLaser( RecceDcsUnit, relativespot, Target:GetPointVec3():AddY(1):GetVec3(), LaserCode ) if Duration then self.ScheduleID = self.LaseScheduler:Schedule( self, StopLase, {self}, Duration ) @@ -368,4 +370,16 @@ do return self.Lasing end -end \ No newline at end of file + --- Set laser start position relative to the lasing unit. + -- @param #SPOT self + -- @param #table position Start position of the laser relative to the lasing unit. Default is { x = 0, y = 2, z = 0 } + -- @return #SPOT self + -- @usage + -- -- Set lasing position to be the position of the optics of the Gazelle M: + -- myspot:SetRelativeStartPosition({ x = 1.7, y = 1.2, z = 0 }) + function SPOT:SetRelativeStartPosition(position) + self.relstartpos = position or { x = 0, y = 2, z = 0 } + return self + end + +end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 1ec83be66..296dbb3ba 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -46,7 +46,7 @@ -- === -- -- ### Author: **FlightControl** --- ### Contributions: +-- ### Contributions: **Applevangelist**, **FunkyFranky** -- -- === -- @@ -910,9 +910,6 @@ function ZONE_RADIUS:GetVec3( Height ) end - - - --- Scan the zone for the presence of units of the given ObjectCategories. -- Note that **only after** a zone has been scanned, the zone can be evaluated by: -- @@ -921,7 +918,6 @@ end -- * @{ZONE_RADIUS.IsSomeInZoneOfCoalition}(): Scan if there is some presence of units in the zone of the given coalition. -- * @{ZONE_RADIUS.IsNoneInZoneOfCoalition}(): Scan if there isn't any presence of units in the zone of an other coalition than the given one. -- * @{ZONE_RADIUS.IsNoneInZone}(): Scan if the zone is empty. --- @{#ZONE_RADIUS. -- @param #ZONE_RADIUS self -- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}` -- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}` @@ -953,7 +949,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) if ZoneObject then local ObjectCategory = ZoneObject:getCategory() - + --local name=ZoneObject:getName() --env.info(string.format("Zone object %s", tostring(name))) --self:E(ZoneObject) @@ -1294,13 +1290,13 @@ end -- @return DCS#Vec2 The random location within the zone. function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) - local Vec2 = self:GetVec2() - local _inner = inner or 0 - local _outer = outer or self:GetRadius() + local Vec2 = self:GetVec2() + local _inner = inner or 0 + local _outer = outer or self:GetRadius() - if surfacetypes and type(surfacetypes)~="table" then + if surfacetypes and type(surfacetypes)~="table" then surfacetypes={surfacetypes} - end + end local function _getpoint() local point = {} @@ -1320,10 +1316,10 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) return false end - local point=_getpoint() + local point=_getpoint() - if surfacetypes then - local N=1 ; local Nmax=100 ; local gotit=false + if surfacetypes then + local N=1 ; local Nmax=100 ; local gotit=false while gotit==false and N<=Nmax do gotit=_checkSurface(point) if gotit then @@ -1335,7 +1331,7 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) end end - return point + return point end --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. @@ -1458,7 +1454,7 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma for _,_coord in pairs (buildings) do local coord = _coord -- Core.Point#COORDINATE -- keep >50m dist from buildings - if coord:Get2DDistance(rcoord) > dist then + if coord:Get3DDistance(rcoord) > dist then found = true else found = false @@ -2385,6 +2381,21 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup ) return self end +--- Constructor to create a ZONE_POLYGON instance, taking the zone name and an array of DCS#Vec2, forming a polygon. +-- @param #ZONE_POLYGON self +-- @param #string ZoneName Name of the zone. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon. +-- @return #ZONE_POLYGON self +function ZONE_POLYGON:NewFromPointsArray( ZoneName, PointsArray ) + + local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) ) + self:F( { ZoneName, self._.Polygon } ) + + -- Zone objects are added to the _DATABASE and SET_ZONE objects. + _EVENTDISPATCHER:CreateEventNewZone( self ) + + return self +end --- Constructor to create a ZONE_POLYGON instance, taking the zone name and the **name** of the @{Wrapper.Group#GROUP} defined within the Mission Editor. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. @@ -2417,6 +2428,296 @@ function ZONE_POLYGON:FindByName( ZoneName ) return ZoneFound end +--- Scan the zone for the presence of units of the given ObjectCategories. Does **not** scan for scenery at the moment. +-- Note that **only after** a zone has been scanned, the zone can be evaluated by: +-- +-- * @{ZONE_POLYGON.IsAllInZoneOfCoalition}(): Scan the presence of units in the zone of a coalition. +-- * @{ZONE_POLYGON.IsAllInZoneOfOtherCoalition}(): Scan the presence of units in the zone of an other coalition. +-- * @{ZONE_POLYGON.IsSomeInZoneOfCoalition}(): Scan if there is some presence of units in the zone of the given coalition. +-- * @{ZONE_POLYGON.IsNoneInZoneOfCoalition}(): Scan if there isn't any presence of units in the zone of an other coalition than the given one. +-- * @{ZONE_POLYGON.IsNoneInZone}(): Scan if the zone is empty. +-- @param #ZONE_POLYGON self +-- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}` +-- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}` +-- @usage +-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition ) +function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) + + self.ScanData = {} + self.ScanData.Coalitions = {} + self.ScanData.Scenery = {} + self.ScanData.Units = {} + + local function EvaluateZone( ZoneObject ) + + if ZoneObject then + + local ObjectCategory = ZoneObject:getCategory() + + if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then + + local CoalitionDCSUnit = ZoneObject:getCoalition() + + local Include = false + if not UnitCategories then + -- Anything found is included. + Include = true + else + -- Check if found object is in specified categories. + local CategoryDCSUnit = ZoneObject:getDesc().category + + for UnitCategoryID, UnitCategory in pairs( UnitCategories ) do + if UnitCategory == CategoryDCSUnit then + Include = true + break + end + end + + end + + if Include then + + local CoalitionDCSUnit = ZoneObject:getCoalition() + + -- This coalition is inside the zone. + self.ScanData.Coalitions[CoalitionDCSUnit] = true + + self.ScanData.Units[ZoneObject] = ZoneObject + + self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) + end + end + + --[[ + -- no scenery possible at the moment + if ObjectCategory == Object.Category.SCENERY then + local SceneryType = ZoneObject:getTypeName() + local SceneryName = ZoneObject:getName() + self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} + self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) + self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) + end + --]] + end + + return true + end + + -- Search objects. + local inzoneunits = SET_UNIT:New():FilterZones({self}):FilterOnce() + local inzonestatics = SET_STATIC:New():FilterZones({self}):FilterOnce() + + inzoneunits:ForEach( + function(unit) + local Unit = unit --Wrapper.Unit#UNIT + local DCS = Unit:GetDCSObject() + EvaluateZone(DCS) + end + ) + + inzonestatics:ForEach( + function(static) + local Static = static --Wrapper.Static#STATIC + local DCS = Static:GetDCSObject() + EvaluateZone(DCS) + end + ) + +end + +--- Count the number of different coalitions inside the zone. +-- @param #ZONE_POLYGON self +-- @return #table Table of DCS units and DCS statics inside the zone. +function ZONE_POLYGON:GetScannedUnits() + return self.ScanData.Units +end + +--- Get a set of scanned units. +-- @param #ZONE_POLYGON self +-- @return Core.Set#SET_UNIT Set of units and statics inside the zone. +function ZONE_POLYGON:GetScannedSetUnit() + + local SetUnit = SET_UNIT:New() + + if self.ScanData then + for ObjectID, UnitObject in pairs( self.ScanData.Units ) do + local UnitObject = UnitObject -- DCS#Unit + if UnitObject:isExist() then + local FoundUnit = UNIT:FindByName( UnitObject:getName() ) + if FoundUnit then + SetUnit:AddUnit( FoundUnit ) + else + local FoundStatic = STATIC:FindByName( UnitObject:getName() ) + if FoundStatic then + SetUnit:AddUnit( FoundStatic ) + end + end + end + end + end + + return SetUnit +end + +--- Get a set of scanned units. +-- @param #ZONE_POLYGON self +-- @return Core.Set#SET_GROUP Set of groups. +function ZONE_POLYGON:GetScannedSetGroup() + + self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP + + self.ScanSetGroup.Set={} + + if self.ScanData then + for ObjectID, UnitObject in pairs( self.ScanData.Units ) do + local UnitObject = UnitObject -- DCS#Unit + if UnitObject:isExist() then + + local FoundUnit=UNIT:FindByName(UnitObject:getName()) + if FoundUnit then + local group=FoundUnit:GetGroup() + self.ScanSetGroup:AddGroup(group) + end + end + end + end + + return self.ScanSetGroup +end + +--- Count the number of different coalitions inside the zone. +-- @param #ZONE_POLYGON self +-- @return #number Counted coalitions. +function ZONE_POLYGON:CountScannedCoalitions() + + local Count = 0 + + for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do + Count = Count + 1 + end + + return Count +end + +--- Check if a certain coalition is inside a scanned zone. +-- @param #ZONE_POLYGON self +-- @param #number Coalition The coalition id, e.g. coalition.side.BLUE. +-- @return #boolean If true, the coalition is inside the zone. +function ZONE_POLYGON:CheckScannedCoalition( Coalition ) + if Coalition then + return self.ScanData.Coalitions[Coalition] + end + return nil +end + +--- Get Coalitions of the units in the Zone, or Check if there are units of the given Coalition in the Zone. +-- Returns nil if there are none to two Coalitions in the zone! +-- Returns one Coalition if there are only Units of one Coalition in the Zone. +-- Returns the Coalition for the given Coalition if there are units of the Coalition in the Zone. +-- @param #ZONE_POLYGON self +-- @return #table +function ZONE_POLYGON:GetScannedCoalition( Coalition ) + + if Coalition then + return self.ScanData.Coalitions[Coalition] + else + local Count = 0 + local ReturnCoalition = nil + + for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do + Count = Count + 1 + ReturnCoalition = CoalitionID + end + + if Count ~= 1 then + ReturnCoalition = nil + end + + return ReturnCoalition + end +end + +--- Get scanned scenery type (currently not implemented in ZONE_POLYGON) +-- @param #ZONE_POLYGON self +-- @return #table Table of DCS scenery type objects. +function ZONE_POLYGON:GetScannedSceneryType( SceneryType ) + return self.ScanData.Scenery[SceneryType] +end + +--- Get scanned scenery table (currently not implemented in ZONE_POLYGON) +-- @param #ZONE_POLYGON self +-- @return #table Table of DCS scenery objects. +function ZONE_POLYGON:GetScannedScenery() + return self.ScanData.Scenery +end + +--- Is All in Zone of Coalition? +-- Check if only the specifed coalition is inside the zone and noone else. +-- @param #ZONE_POLYGON self +-- @param #number Coalition Coalition ID of the coalition which is checked to be the only one in the zone. +-- @return #boolean True, if **only** that coalition is inside the zone and no one else. +-- @usage +-- self.Zone:Scan() +-- local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition ) +function ZONE_POLYGON:IsAllInZoneOfCoalition( Coalition ) + return self:CountScannedCoalitions() == 1 and self:GetScannedCoalition( Coalition ) == true +end + +--- Is All in Zone of Other Coalition? +-- Check if only one coalition is inside the zone and the specified coalition is not the one. +-- You first need to use the @{#ZONE_POLYGON.Scan} method to scan the zone before it can be evaluated! +-- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. +-- @param #ZONE_POLYGON self +-- @param #number Coalition Coalition ID of the coalition which is not supposed to be in the zone. +-- @return #boolean True, if and only if only one coalition is inside the zone and the specified coalition is not it. +-- @usage +-- self.Zone:Scan() +-- local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition ) +function ZONE_POLYGON:IsAllInZoneOfOtherCoalition( Coalition ) + return self:CountScannedCoalitions() == 1 and self:GetScannedCoalition( Coalition ) == nil +end + +--- Is Some in Zone of Coalition? +-- Check if more than one coaltion is inside the zone and the specifed coalition is one of them. +-- You first need to use the @{#ZONE_POLYGON.Scan} method to scan the zone before it can be evaluated! +-- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. +-- @param #ZONE_POLYGON self +-- @param #number Coalition ID of the coaliton which is checked to be inside the zone. +-- @return #boolean True if more than one coalition is inside the zone and the specified coalition is one of them. +-- @usage +-- self.Zone:Scan() +-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) +function ZONE_POLYGON:IsSomeInZoneOfCoalition( Coalition ) + return self:CountScannedCoalitions() > 1 and self:GetScannedCoalition( Coalition ) == true +end + +--- Is None in Zone of Coalition? +-- You first need to use the @{#ZONE_POLYGON.Scan} method to scan the zone before it can be evaluated! +-- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. +-- @param #ZONE_POLYGON self +-- @param Coalition +-- @return #boolean +-- @usage +-- self.Zone:Scan() +-- local IsOccupied = self.Zone:IsNoneInZoneOfCoalition( self.Coalition ) +function ZONE_POLYGON:IsNoneInZoneOfCoalition( Coalition ) + return self:GetScannedCoalition( Coalition ) == nil +end + +--- Is None in Zone? +-- You first need to use the @{#ZONE_POLYGON.Scan} method to scan the zone before it can be evaluated! +-- Note that once a zone has been scanned, multiple evaluations can be done on the scan result set. +-- @param #ZONE_POLYGON self +-- @return #boolean +-- @usage +-- self.Zone:Scan() +-- local IsEmpty = self.Zone:IsNoneInZone() +function ZONE_POLYGON:IsNoneInZone() + return self:CountScannedCoalitions() == 0 +end + + do -- ZONE_ELASTIC --- @type ZONE_ELASTIC diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 7894f25f6..bd6c24146 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -890,7 +890,7 @@ function AUTOLASE:onafterMonitor(From, Event, To) if unit and unit:IsAlive() then local threat = unit:GetThreatLevel() local coord = unit:GetCoordinate() - if threat > 0 then + if threat >= self.minthreatlevel then local unitname = unit:GetName() -- prefer radar units if unit:HasAttribute("RADAR_BAND1_FOR_ARM") or unit:HasAttribute("RADAR_BAND2_FOR_ARM") or unit:HasAttribute("Optical Tracker") then diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index 9be2c35bf..e8abc02dd 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -363,7 +363,7 @@ do -- ZONE_CAPTURE_COALITION --- ZONE_CAPTURE_COALITION Constructor. -- @param #ZONE_CAPTURE_COALITION self - -- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved. + -- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved. Alternatively, can be handed as the name of late activated group describing a @{ZONE_POLYGON} with its waypoints. -- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone. -- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}. -- @param #table ObjectCategories Table of unit categories. See [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object). Default {Object.Category.UNIT, Object.Category.STATIC}, i.e. all UNITS and STATICS. diff --git a/Moose Development/Moose/Functional/ZoneGoal.lua b/Moose Development/Moose/Functional/ZoneGoal.lua index cd55b2603..3ed9554f9 100644 --- a/Moose Development/Moose/Functional/ZoneGoal.lua +++ b/Moose Development/Moose/Functional/ZoneGoal.lua @@ -8,7 +8,7 @@ -- === -- -- ### Author: **FlightControl** --- ### Contributions: **funkyfranky** +-- ### Contributions: **funkyfranky**, **Applevangelist** -- -- === -- @@ -56,13 +56,18 @@ do -- Zone --- ZONE_GOAL Constructor. -- @param #ZONE_GOAL self - -- @param Core.Zone#ZONE_RADIUS Zone A @{Zone} object with the goal to be achieved. + -- @param Core.Zone#ZONE_RADIUS Zone A @{Zone} object with the goal to be achieved. Alternatively, can be handed as the name of late activated group describing a @{ZONE_POLYGON} with its waypoints. -- @return #ZONE_GOAL function ZONE_GOAL:New( Zone ) - - local self = BASE:Inherit( self, ZONE_RADIUS:New( Zone:GetName(), Zone:GetVec2(), Zone:GetRadius() ) ) -- #ZONE_GOAL - self:F( { Zone = Zone } ) - + + BASE:I({Zone=Zone}) + local self = BASE:Inherit( self, BASE:New()) + if type(Zone) == "string" then + self = BASE:Inherit( self, ZONE_POLYGON:NewFromGroupName(Zone) ) + else + self = BASE:Inherit( self, ZONE_RADIUS:New( Zone:GetName(), Zone:GetVec2(), Zone:GetRadius() ) ) -- #ZONE_GOAL + self:F( { Zone = Zone } ) + end -- Goal object. self.Goal = GOAL:New() diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index f63f57e84..d1040822f 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -44,3 +44,15 @@ end if __na then BASE:I("Check /Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") end +BASE.ServerName="Unknown" +if lfs and loadfile then + local serverfile=lfs.writedir() .. 'Config/serverSettings.lua' + if UTILS.FileExists(serverfile) then + loadfile(serverfile)() + if cfg and cfg.name then + BASE.ServerName=cfg.name + end + end + BASE.ServerName=BASE.ServerName or "Unknown" + BASE:I("Server Name: "..tostring(BASE.ServerName)) +end diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index a7588315b..d9decff69 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -104,6 +104,7 @@ __Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsZone.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' ) __Moose.Include( 'Scripts/Moose/Ops/PlayerTask.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/PlayerRecce.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 96c67805c..ab66d8b5a 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -92,6 +92,7 @@ -- @field Sound.SRS#MSRS msrs Moose SRS object. -- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used. -- @field #boolean ReportmBar Report mBar/hpa even if not metric, i.e. for Mirage flights +-- @field #boolean TransmitOnlyWithPlayers For SRS - If true, only transmit if there are alive Players. -- @extends Core.Fsm#FSM --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -346,6 +347,7 @@ ATIS = { markerid = nil, relHumidity = nil, ReportmBar = false, + TransmitOnlyWithPlayers = false, } --- NATO alphabet. @@ -588,7 +590,7 @@ _ATIS = {} --- ATIS class version. -- @field #string version -ATIS.version = "0.9.9" +ATIS.version = "0.9.10" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -780,6 +782,18 @@ function ATIS:SetTowerFrequencies( freqs ) return self end +--- For SRS - Switch to only transmit if there are players on the server. +-- @param #ATIS self +-- @param #boolean Switch If true, only send SRS if there are alive Players. +-- @return #ATIS self +function ATIS:SetTransmitOnlyWithPlayers(Switch) + self.TransmitOnlyWithPlayers = Switch + if self.msrsQ then + self.msrsQ:SetTransmitOnlyWithPlayers(Switch) + end + return self +end + --- Set active runway for **landing** operations. This can be used if the automatic runway determination via the wind direction gives incorrect results. -- For example, use this if there are two runways with the same directions. -- @param #ATIS self @@ -1195,6 +1209,7 @@ function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port, GoogleKey) self.msrs:SetLabel("ATIS") self.msrs:SetGoogle(GoogleKey) self.msrsQ = MSRSQUEUE:New("ATIS") + self.msrsQ:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) if self.dTQueueCheck<=10 then self:SetQueueUpdateTime(90) end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index bfa135ab5..118b9e642 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -33,6 +33,7 @@ -- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module] -- * [USS Forrestal](https://en.wikipedia.org/wiki/USS_Forrestal_(CV-59)) (CV-59) [Heatblur Carrier Module] -- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12)) (R12) [**WIP**] +-- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_(R05) (R05) [**WIP**] -- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**] -- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6)) (LHA-6) [**WIP**] -- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**] @@ -115,6 +116,7 @@ -- * [Harrier Ship Landing Mission with Auto LSO!](https://www.youtube.com/watch?v=lqmVvpunk2c) -- * [Updated Airboss V/STOL Features USS Tarawa](https://youtu.be/K7I4pU6j718) -- * [Harrier Practice pattern USS America](https://youtu.be/99NigITYmcI) +-- * [Harrier CASE III TACAN Approach USS Tarawa](https://www.youtube.com/watch?v=bTgJXZ9Mhdc&t=1s) -- * [Harrier CASE III TACAN Approach USS Tarawa](https://www.youtube.com/watch?v=wWHag5WpNZ0) -- -- === @@ -1265,7 +1267,7 @@ AIRBOSS = { --- Aircraft types capable of landing on carrier (human+AI). -- @type AIRBOSS.AircraftCarrier --- @field #string AV8B AV-8B Night Harrier. Works only with the HMS Hermes, USS Tarawa, USS America, and Juan Carlos I. +-- @field #string AV8B AV-8B Night Harrier. Works only with the HMS Hermes, HMS Invincible, USS Tarawa, USS America, and Juan Carlos I. -- @field #string A4EC A-4E Community mod. -- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics. -- @field #string F14A F-14A by Heatblur. @@ -1302,6 +1304,7 @@ AIRBOSS.AircraftCarrier={ -- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module] -- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete] -- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier] +-- @field #string INVINCIBLE HMS Invincible (R05) [V/STOL Carrier] -- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier] -- @field #string AMERICA USS America (LHA-6) [V/STOL Carrier] -- @field #string JCARLOS Juan Carlos I (L61) [V/STOL Carrier] @@ -1316,6 +1319,7 @@ AIRBOSS.CarrierType = { FORRESTAL = "Forrestal", VINSON = "VINSON", HERMES = "HERMES81", + INVINCIBLE = "hms_invincible", TARAWA = "LHA_Tarawa", AMERICA = "USS America LHA-6", JCARLOS = "L61", @@ -1332,6 +1336,7 @@ AIRBOSS.CarrierType = { -- @field #number wire2 Distance in meters from carrier position to second wire. -- @field #number wire3 Distance in meters from carrier position to third wire. -- @field #number wire4 Distance in meters from carrier position to fourth wire. +-- @field #number landingdist Distance in meeters to the landing position. -- @field #number rwylength Length of the landing runway in meters. -- @field #number rwywidth Width of the landing runway in meters. -- @field #number totlength Total length of carrier. @@ -1731,7 +1736,7 @@ AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version = "1.2.1" +AIRBOSS.version = "1.3.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1978,7 +1983,7 @@ function AIRBOSS:New( carriername, alias ) -- Init carrier parameters. if self.carriertype == AIRBOSS.CarrierType.STENNIS then - --self:_InitStennis() + -- Stennis parameters were updated to match the other Super Carriers. self:_InitNimitz() elseif self.carriertype == AIRBOSS.CarrierType.ROOSEVELT then self:_InitNimitz() @@ -1991,11 +1996,14 @@ function AIRBOSS:New( carriername, alias ) elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then self:_InitForrestal() elseif self.carriertype == AIRBOSS.CarrierType.VINSON then - -- TODO: Carl Vinson parameters. + -- Carl Vinson is legacy now. self:_InitStennis() elseif self.carriertype == AIRBOSS.CarrierType.HERMES then -- Hermes parameters. self:_InitHermes() + elseif self.carriertype == AIRBOSS.CarrierType.INVINCIBLE then + -- Invincible parameters. + self:_InitInvincible() elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then -- Tarawa parameters. self:_InitTarawa() @@ -2006,8 +2014,8 @@ function AIRBOSS:New( carriername, alias ) -- Use Juan Carlos parameters. self:_InitJcarlos() elseif self.carriertype == AIRBOSS.CarrierType.CANBERRA then - -- Use Juan Carlos parameters at this stage --TODO Check primary Landing spot. - self:_InitJcarlos() + -- Use Juan Carlos parameters at this stage. + self:_InitCanberra() elseif self.carriertype == AIRBOSS.CarrierType.KUZNETSOV then -- Kusnetsov parameters - maybe... self:_InitStennis() @@ -2098,7 +2106,7 @@ function AIRBOSS:New( carriername, alias ) -- cL:FlareYellow() -- Carrier specific. - if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then + if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.INVINCIBLE or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then -- Flare wires. local w1 = stern:Translate( self.carrierparam.wire1, FB, true ) @@ -2831,7 +2839,7 @@ end function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW) --Check if V/STOL Carrier - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- allow a larger GSE for V/STOL operations --Pene Testing self.gle._max=_max or 0.7 @@ -2868,7 +2876,7 @@ end function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) --Check if V/STOL Carrier -- Pene testing - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- V/STOL Values -- allow a larger LUE for V/STOL operations self.lue._max=_max or 1.8 @@ -3023,7 +3031,6 @@ end -- @param #AIRBOSS self -- @param #number TimeInterval (Optional) Time interval in seconds. Default 1200 sec = 20 min. -- @return #AIRBOSS self - function AIRBOSS:SetBeaconRefresh( TimeInterval ) self.dTbeacon = TimeInterval or (20 * 60) return self @@ -4286,6 +4293,9 @@ function AIRBOSS:_InitStennis() self.carrierparam.wire3 = 46 + 24 self.carrierparam.wire4 = 46 + 35 -- Last wire is strangely one meter closer. + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.wire3 + -- Platform at 5k. Reduce descent rate to 2000 ft/min to 1200 dirty up level flight. self.Platform.name = "Platform 5k" self.Platform.Xmin = -UTILS.NMToMeters( 22 ) -- Not more than 22 NM behind the boat. Last check was at 21 NM. @@ -4436,6 +4446,9 @@ function AIRBOSS:_InitNimitz() self.carrierparam.wire3 = 79 self.carrierparam.wire4 = 92 + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.wire3 + end --- Init parameters for Forrestal class super carriers. @@ -4465,6 +4478,9 @@ function AIRBOSS:_InitForrestal() self.carrierparam.wire3 = 64 -- 62 self.carrierparam.wire4 = 74 -- 72.5 + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.wire3 + end --- Init parameters for R12 HMS Hermes carrier. @@ -4494,12 +4510,64 @@ function AIRBOSS:_InitHermes() self.carrierparam.wire3 = nil self.carrierparam.wire4 = nil + -- Distance to landing spot. + self.carrierparam.landingspot=69 + + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.landingspot + -- Late break. self.BreakLate.name = "Late Break" self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? - self.BreakLate.Zmin = -UTILS.NMToMeters( 0.25 ) -- Not more than 0.25 NM port. - self.BreakLate.Zmax = UTILS.NMToMeters( 0.5 ) -- Not more than 0.5 NM starboard. + self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax = nil + self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax = nil + +end + +--- Init parameters for R05 HMS Invincible carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitInvincible() + + -- Init Stennis as default. + self:_InitStennis() + + -- Carrier Parameters. + self.carrierparam.sterndist = -105 + self.carrierparam.deckheight = 12 -- From model viewer WL0. + + -- Total size of the carrier (approx as rectangle). + self.carrierparam.totlength = 228.19 + self.carrierparam.totwidthport = 20.5 + self.carrierparam.totwidthstarboard = 24.5 + + -- Landing runway. + self.carrierparam.rwyangle = 0 + self.carrierparam.rwylength = 215 + self.carrierparam.rwywidth = 13 + + -- Wires. + self.carrierparam.wire1 = nil + self.carrierparam.wire2 = nil + self.carrierparam.wire3 = nil + self.carrierparam.wire4 = nil + + -- Distance to landing spot. + self.carrierparam.landingspot=69 + + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.landingspot + + -- Late break. + self.BreakLate.name = "Late Break" + self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port. + self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. self.BreakLate.LimitXmax = nil self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 @@ -4534,6 +4602,12 @@ function AIRBOSS:_InitTarawa() self.carrierparam.wire3 = nil self.carrierparam.wire4 = nil + -- Distance to landing spot. + self.carrierparam.landingspot=57 + + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.landingspot + -- Late break. self.BreakLate.name = "Late Break" self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. @@ -4574,6 +4648,12 @@ function AIRBOSS:_InitAmerica() self.carrierparam.wire3 = nil self.carrierparam.wire4 = nil + -- Distance to landing spot. + self.carrierparam.landingspot=59 + + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.landingspot + -- Late break. self.BreakLate.name = "Late Break" self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. @@ -4614,6 +4694,12 @@ function AIRBOSS:_InitJcarlos() self.carrierparam.wire3 = nil self.carrierparam.wire4 = nil + -- Distance to landing spot. + self.carrierparam.landingspot=89 + + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.landingspot + -- Late break. self.BreakLate.name = "Late Break" self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. @@ -4626,6 +4712,16 @@ function AIRBOSS:_InitJcarlos() self.BreakLate.LimitZmax = nil end + +--- Init parameters for L02 Canberra carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitCanberra() + + -- Init Juan Carlos as default. + self:_InitJcarlos() + +end + --- Init parameters for Marshal Voice overs *Gabriella* by HighwaymanEd. -- @param #AIRBOSS self -- @param #string mizfolder (Optional) Folder within miz file where the sound files are located. @@ -5302,7 +5398,6 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 800 ) speed = UTILS.KnotsToMps( 350 ) elseif skyhawk then - alt = UTILS.FeetToMeters( 600 ) speed = UTILS.KnotsToMps( 250 ) elseif goshawk then @@ -5396,7 +5491,6 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 300 ) -- ? elseif harrier then alt=UTILS.FeetToMeters(312)-- 300-325 ft - end aoa = aoaac.OnSpeed @@ -6341,7 +6435,7 @@ function AIRBOSS:_GetMarshalAltitude( stack, case ) p2 = Carrier:Translate( UTILS.NMToMeters( 1.5 ), hdg ) -- Tarawa,LHA,LHD Delta patterns. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Pattern is directly overhead the carrier. p1 = Carrier:Translate( UTILS.NMToMeters( 1.0 ), hdg + 90 ) @@ -8180,7 +8274,7 @@ function AIRBOSS:OnEventLand( EventData ) self:T( self.lid .. text ) -- Check carrier type. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Power "Idle". self:RadioTransmission( self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true ) @@ -8215,8 +8309,7 @@ function AIRBOSS:OnEventLand( EventData ) -- AI unit landed -- -------------------- - if self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then - + if self.carriertype ~= AIRBOSS.CarrierType.INVINCIBLE or self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then -- Coordinate at landing event local coord = EventData.IniUnit:GetCoordinate() @@ -9254,7 +9347,7 @@ function AIRBOSS:_CheckForLongDownwind( playerData ) local limit = UTILS.NMToMeters( -1.6 ) -- For the tarawa, other LHA and LHD we give a bit more space. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then limit = UTILS.NMToMeters( -2.0 ) end @@ -9341,8 +9434,7 @@ function AIRBOSS:_Ninety( playerData ) self:_PlayerHint( playerData ) -- Next step: wake. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then - + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then -- Harrier has no wake stop. It stays port of the boat. self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.FINAL ) else @@ -10037,7 +10129,7 @@ function AIRBOSS:_GetSternCoord() -- local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). --Pene testing Case III - if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then if case==3 then -- CASE III V/STOL translation Due over deck approach if needed. self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) @@ -10087,7 +10179,6 @@ function AIRBOSS:_GetWire( Lcoord, dc ) -- Multiplayer wire correction. if self.mpWireCorrection then d = d - self.mpWireCorrection - end -- Shift wires from stern to their correct position. @@ -10679,7 +10770,7 @@ function AIRBOSS:_GetZoneRunwayBox() return self.zoneRunwaybox end ---- Get zone of primary abeam landing position of HMS Hermes, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters. +--- Get zone of primary abeam landing position of HMS Hermes, HMS Invincible, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters. --- Allow for Clear to land call from LSO approaching abeam the landing spot if stable as per NATOPS 00-80T -- @param #AIRBOSS self @@ -10784,7 +10875,7 @@ function AIRBOSS:_GetZoneHolding( case, stack ) self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", Post:GetVec2(), self.marshalradius ) -- Delta pattern. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters( 5 ) ) end @@ -10836,7 +10927,7 @@ function AIRBOSS:_GetZoneCommence( case, stack ) -- Three position local Three = self:GetCoordinate():Translate( D, hdg + 275 ) - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then local Dx = UTILS.NMToMeters( 2.25 ) local Dz = UTILS.NMToMeters( 2.25 ) @@ -11126,7 +11217,7 @@ function AIRBOSS:_GetAltCarrier( unit ) return h end ---- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I and HMS Hermes it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot. +--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I, HMS Invincible, and HMS Hermes and Invincible it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot. -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() @@ -11134,28 +11225,31 @@ function AIRBOSS:_GetOptLandingCoordinate() -- Start with stern coordiante. self.landingcoord:UpdateFromCoordinate( self:_GetSternCoord() ) - -- Stern coordinate. - -- local stern=self:_GetSternCoord() -- Final bearing. - local FB=self:GetFinalBearing(false) + + -- Cse local case=self.case + -- set Case III V/STOL abeam landing spot over deck -- Pene Testing - if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then + if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then if case==3 then - self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()) - -- Altitude 120ft -- is this corect for Case III? - self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + + -- Landing coordinate. + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()) + + -- Altitude 120ft -- is this corect for Case III? + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) elseif case==2 or case==1 then - -- Landing 100 ft abeam, 120 ft alt. - self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) - --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + -- Landing 100 ft abeam, 120 ft alt. + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) + + -- Alitude 120 ft. + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) - -- Alitude 120 ft. - self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) end else @@ -11163,8 +11257,7 @@ function AIRBOSS:_GetOptLandingCoordinate() -- Ideally we want to land between 2nd and 3rd wire. if self.carrierparam.wire3 then -- We take the position of the 3rd wire to approximately account for the length of the aircraft. - local w3 = self.carrierparam.wire3 - self.landingcoord:Translate( w3, FB, true, true ) + self.landingcoord:Translate( self.carrierparam.wire3, FB, true, true ) end -- Add 2 meters to account for aircraft height. @@ -11175,54 +11268,19 @@ function AIRBOSS:_GetOptLandingCoordinate() return self.landingcoord end ---- Get landing spot on Tarawa. +--- Get landing spot on Tarawa and others. -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Primary landing spot coordinate. function AIRBOSS:_GetLandingSpotCoordinate() + -- Start at stern coordinate. self.landingspotcoord:UpdateFromCoordinate( self:_GetSternCoord() ) - -- Stern coordinate. - -- local stern=self:_GetSternCoord() + -- Landing 100 ft abeam, 100 alt. + local hdg = self:GetHeading() - if self.carriertype==AIRBOSS.CarrierType.HERMES then - - -- Landing 100 ft abeam, 100 alt. - local hdg = self:GetHeading() - - -- Primary landing spot 5 - self.landingspotcoord:Translate( 69, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) - elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then - - -- Landing 100 ft abeam, 120 alt. - local hdg = self:GetHeading() - - -- Primary landing spot 7.5 - self.landingspotcoord:Translate( 57, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) - elseif self.carriertype == AIRBOSS.CarrierType.AMERICA then - - -- Landing 100 ft abeam, 120 alt. - local hdg = self:GetHeading() - - -- Primary landing spot 7.5 a little further forwad on the America - self.landingspotcoord:Translate( 59, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) - - elseif self.carriertype == AIRBOSS.CarrierType.JCARLOS then - - -- Landing 100 ft abeam, 120 alt. - local hdg = self:GetHeading() - - -- Primary landing spot 5.0 -- Done voice for different landing Spots. - self.landingspotcoord:Translate( 89, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) - - elseif self.carriertype == AIRBOSS.CarrierType.CANBERRA then - - -- Landing 100 ft abeam, 120 alt. - local hdg = self:GetHeading() - - -- Primary landing spot 5.0 -- Done voice for different landing Spots. - self.landingspotcoord:Translate( 89, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) - end + -- Primary landing spot. Different carriers handled via carrier parameter landingspot now. + self.landingspotcoord:Translate( self.carrierparam.landingspot, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) return self.landingspotcoord end @@ -11270,8 +11328,8 @@ function AIRBOSS:GetWind( alt, magnetic, coord ) -- Current position of the carrier or input. local cv = coord or self:GetCoordinate() - -- Wind direction and speed. By default at 50 meters ASL. - local Wdir, Wspeed = cv:GetWind( alt or 15 ) + -- Wind direction and speed. By default at 18 meters ASL. + local Wdir, Wspeed = cv:GetWind( alt or 18 ) -- Include magnetic declination. if magnetic then @@ -11796,7 +11854,7 @@ function AIRBOSS:_LSOgrade( playerData ) local grade local points - if N == 0 and (TgrooveUnicorn or TgrooveVstolUnicorn) then + if N == 0 and (TgrooveUnicorn or TgrooveVstolUnicorn or playerData.case==3) then -- No deviations, should be REALLY RARE! grade = "_OK_" points = 5.0 @@ -12133,7 +12191,7 @@ function AIRBOSS:_GS( step, n ) if n == -1 then gp = AIRBOSS.GroovePos.IC elseif n == 1 then - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then gp = AIRBOSS.GroovePos.AL else gp = AIRBOSS.GroovePos.IW @@ -14016,7 +14074,7 @@ function AIRBOSS:_IsCarrierAircraft( unit ) -- Special case for Harrier which can only land on Tarawa, LHA and LHD. if aircrafttype == AIRBOSS.AircraftCarrier.AV8B then - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then return true else return false @@ -14024,7 +14082,7 @@ function AIRBOSS:_IsCarrierAircraft( unit ) end -- Also only Harriers can land on the Tarawa, LHA and LHD. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then if aircrafttype ~= AIRBOSS.AircraftCarrier.AV8B then return false end @@ -17472,7 +17530,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare ) end -- Tarawa, LHA and LHD landing spots. - if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then text = text .. "\n* abeam landing stop with RED flares" -- Abeam landing spot zone. local ALSPT = self:_GetZoneAbeamLandingSpot() @@ -17961,6 +18019,7 @@ function AIRBOSS:onafterLSOGrade(From, Event, To, playerData, grade) result.carriertype=grade.carriertype result.carriername=grade.carriername result.carrierrwy=grade.carrierrwy + result.landingdist=self.carrierparam.landingdist result.theatre=grade.theatre result.case=playerData.case result.Tgroove=grade.Tgroove diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index e1e9bea0b..30a84aa53 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -27,7 +27,7 @@ do -- @field #string ClassName Name of this class. -- @field #string version Versioning. -- @field #string lid LID for log entries. --- @field #number coalition Colition side. +-- @field #number coalition Coalition side. -- @field #string coalitiontxt e.g."blue" -- @field Core.Zone#ZONE OpsZone, -- @field Core.Zone#ZONE StationZone, @@ -497,7 +497,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.43", -- #string + version = "0.2.44", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -1788,10 +1788,10 @@ function AWACS:SetAdditionalZone(Zone, Draw) self:T(self.lid.."SetAdditionalZone") self.BorderZone = Zone if self.debug then - Zone:DrawZone(-1,{1,0.64,0},1,{1,0.64,0},0.2,1,true) - MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToAll() + Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) + MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) elseif Draw then - Zone:DrawZone(-1,{1,0.64,0},1,{1,0.64,0},0.2,1,true) + Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end return self end @@ -1805,11 +1805,11 @@ function AWACS:SetRejectionZone(Zone,Draw) self:T(self.lid.."SetRejectionZone") self.RejectZone = Zone if Draw then - Zone:DrawZone(-1,{1,0.64,0},1,{1,0.64,0},0.2,1,true) + Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) --MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToAll() elseif self.debug then - Zone:DrawZone(-1,{1,0.64,0},1,{1,0.64,0},0.2,1,true) - MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToAll() + Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) + MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end return self end @@ -1818,7 +1818,7 @@ end -- @param #AWACS self -- @return #AWACS self function AWACS:DrawFEZ() - self.OpsZone:DrawZone(-1,{1,0,0},1,{1,0,0},0.2,5,true) + self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true) return self end @@ -3780,7 +3780,7 @@ function AWACS:_MoveAnchorStackFromMarker(Name,Coord) marker:UpdateText(stationtag) station.AnchorMarker = marker if self.debug then - station.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + station.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) end self.AnchorStacks:Push(station,Name) end @@ -3811,12 +3811,12 @@ function AWACS:_CreateAnchorStackFromMarker(Name,Coord) --push to AnchorStacks if self.debug then - AnchorStackOne.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) else local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end self.AnchorStacks:Push(AnchorStackOne,newname) @@ -3857,12 +3857,12 @@ function AWACS:_CreateAnchorStack() --push to AnchorStacks if self.debug then --self.AnchorStacks:Flush() - AnchorStackOne.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) else local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end self.AnchorStacks:Push(AnchorStackOne,newname) else @@ -3885,12 +3885,12 @@ function AWACS:_CreateAnchorStack() --push to AnchorStacks if self.debug then --self.AnchorStacks:Flush() - AnchorStackOne.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) else local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end self.AnchorStacks:Push(AnchorStackOne,newname) end @@ -4819,12 +4819,12 @@ function AWACS:AddCAPAirWing(AirWing,Zone) --push to AnchorStacks if self.debug then --self.AnchorStacks:Flush() - AnchorStackOne.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) else local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation = true @@ -5601,28 +5601,28 @@ function AWACS:onafterStart(From, Event, To) local controlzonename = "FEZ-"..self.AOName self.ControlZone = ZONE_RADIUS:New(controlzonename,self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius)) if self.debug then - self.ControlZone:DrawZone(-1,{0,1,0},1,{1,0,0},0.05,3,true) + self.ControlZone:DrawZone(self.coalition,{0,1,0},1,{1,0,0},0.05,3,true) --MARKER:New(self.ControlZone:GetCoordinate(),"Radar Zone"):ToAll() - self.OpsZone:DrawZone(-1,{1,0,0},1,{1,0,0},0.2,5,true) + self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true) local AOCoordString = self.AOCoordinate:ToStringLLDDM() local Rocktag = string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) - MARKER:New(self.AOCoordinate,Rocktag):ToAll() - self.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) + self.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) if not self.GCI then - MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToAll() - self.OrbitZone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,5,true) - MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToAll() + MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) + MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end else local AOCoordString = self.AOCoordinate:ToStringLLDDM() local Rocktag = string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) - MARKER:New(self.AOCoordinate,Rocktag):ToAll() + MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) if not self.GCI then - MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToAll() + MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end local stationtag = string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) - MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToAll() + MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end if not self.GCI then diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua new file mode 100644 index 000000000..fcef0bcb9 --- /dev/null +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -0,0 +1,1576 @@ +--- **Ops** - Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others. +-- +-- ## Features: +-- +-- * Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others. +-- * Implements visual detection from the helo +-- * Implements optical detection via the Vivianne system and lasing +-- * Upload target info to a PLAYERTASKCONTROLLER Instance +-- +-- === +-- +-- # Demo Missions +-- +-- ### Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/). +-- +-- === +-- +-- +-- ### Authors: +-- +-- * Applevengelist (Design & Programming) +-- +-- === +-- +-- @module Ops.PlayerRecce +-- @image @image Detection.JPG + +------------------------------------------------------------------------------------------------------------------- +-- PLAYERRECCE +-- TODO: PLAYERRECCE +-- DONE: No messages when no targets to flare or smoke +-- TODO: Flare smoke group, not all targets +-- DONE: Messages to Attack Group, use client settings +-- DONE: Lasing dist 8km +-- DONE: Reference Point RP +-- DONE: Sort for multiple targets in one direction +-- DONE: Targets with forget timeout, also report +------------------------------------------------------------------------------------------------------------------- + +--- PLAYERRECCE class. +-- @type PLAYERRECCE +-- @field #string ClassName Name of the class. +-- @field #boolean verbose Switch verbosity. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string version +-- @field #table ViewZone +-- @field #table ViewZoneVisual +-- @field Core.Set#SET_CLIENT PlayerSet +-- @field #string Name +-- @field #number Coalition +-- @field #string CoalitionName +-- @field #boolean debug +-- @field #table LaserSpots +-- @field #table UnitLaserCodes +-- @field #table LaserCodes +-- @field #table ClientMenus +-- @field #table OnStation +-- @field #number minthreatlevel +-- @field #number lasingtime +-- @field #table AutoLase +-- @field Core.Set#SET_CLIENT AttackSet +-- @field #boolean TransmitOnlyWithPlayers +-- @field Sound.SRS#MSRS SRS +-- @field Sound.SRS#MSRSQUEUE SRSQueue +-- @field #boolean UseController +-- @field Ops.PlayerTask#PLAYERTASKCONTROLLER Controller +-- @field #boolean ShortCallsign +-- @field #boolean Keepnumber +-- @field #table CallsignTranslations +-- @field Core.Point#COORDINATE ReferencePoint +-- @field #string RPName +-- @field Wrapper.Marker#MARKER RPMarker +-- @field #number TForget +-- @field Utilities.FiFo#FIFO TargetCache +-- @extends Core.Fsm#FSM + +--- +-- +-- *It is our attitude at the beginning of a difficult task which, more than anything else, which will affect its successful outcome.* (William James) +-- +-- === +-- +-- # PLAYERRECCE +-- +-- * Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others. +-- * Implements visual detection from the helo +-- * Implements optical detection via the Vivianne system and lasing +-- +-- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) channel. +-- +-- +-- @field #PLAYERRECCE +PLAYERRECCE = { + ClassName = "PLAYERRECCE", + verbose = true, + lid = nil, + version = "0.0.8", + ViewZone = {}, + ViewZoneVisual = {}, + PlayerSet = nil, + debug = true, + LaserSpots = {}, + UnitLaserCodes = {}, + LaserCodes = {}, + ClientMenus = {}, + OnStation = {}, + minthreatlevel = 0, + lasingtime = 60, + AutoLase = {}, + AttackSet = nil, + TransmitOnlyWithPlayers = true, + UseController = false, + Controller = nil, + ShortCallsign = true, + Keepnumber = true, + CallsignTranslations = nil, + ReferencePoint = nil, + TForget = 600, + TargetCache = nil, +} + +--- +-- @type PlayerRecceDetected +-- @field #boolean detected +-- @field Wrapper.Client#CLIENT recce +-- @field #string playername +-- @field #number timestamp + +--- +-- @type LaserRelativePos +-- @field #string typename Unit type name +PLAYERRECCE.LaserRelativePos = { + ["SA342M"] = { x = 1.7, y = 1.2, z = 0 }, + ["SA342Mistral"] = { x = 1.7, y = 1.2, z = 0 }, + ["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 }, + ["SA342L"] = { x = 1.7, y = 1.2, z = 0 }, +} + +--- +-- @type MaxViewDistance +-- @field #string typename Unit type name +PLAYERRECCE.MaxViewDistance = { + ["SA342M"] = 8000, + ["SA342Mistral"] = 8000, + ["SA342Minigun"] = 8000, + ["SA342L"] = 8000, +} + +--- +-- @type Cameraheight +-- @field #string typename Unit type name +PLAYERRECCE.Cameraheight = { + ["SA342M"] = 2.85, + ["SA342Mistral"] = 2.85, + ["SA342Minigun"] = 2.85, + ["SA342L"] = 2.85, +} + +--- +-- @type CanLase +-- @field #string typename Unit type name +PLAYERRECCE.CanLase = { + ["SA342M"] = true, + ["SA342Mistral"] = true, + ["SA342Minigun"] = false, -- no optics + ["SA342L"] = true, +} + +--- +-- @type SmokeColor +-- @field #string color +PLAYERRECCE.SmokeColor = { + ["highsmoke"] = SMOKECOLOR.Orange, + ["medsmoke"] = SMOKECOLOR.White, + ["lowsmoke"] = SMOKECOLOR.Green, + ["lasersmoke"] = SMOKECOLOR.Red, + ["ownsmoke"] = SMOKECOLOR.Blue, +} + +--- +-- @type FlareColor +-- @field #string color +PLAYERRECCE.FlareColor = { + ["highflare"] =FLARECOLOR.Yellow, + ["medflare"] = FLARECOLOR.White, + ["lowflare"] = FLARECOLOR.Green, + ["laserflare"] = FLARECOLOR.Red, + ["ownflare"] = FLARECOLOR.Green, +} + +--- Create and rund a new PlayerRecce instance. +-- @param #PLAYERRECCE self +-- @param #string Name The name of this instance +-- @param #number Coalition, e.g. coalition.side.BLUE +-- @param Core.Set#SET_CLIENT PlayerSet The set of pilots working as recce +-- @return #PLAYERRECCE self +function PLAYERRECCE:New(Name, Coalition, PlayerSet) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #PLAYERRECCE + + self.Name = Name or "Blue FACA" + self.Coalition = Coalition or coalition.side.BLUE + self.CoalitionName = UTILS.GetCoalitionName(Coalition) + self.PlayerSet = PlayerSet + + self.lid=string.format("PlayerForwardController %s %s | ", self.Name, self.version) + + self:SetLaserCodes( { 1688, 1130, 4785, 6547, 1465, 4578 } ) -- set self.LaserCodes + self.lasingtime = 60 + + self.minthreatlevel = 0 + + self.TForget = 600 + self.TargetCache = FIFO:New() + + -- FSM start state is STOPPED. + self:SetStartState("Stopped") + + self:AddTransition("Stopped", "Start", "Running") + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "RecceOnStation", "*") + self:AddTransition("*", "RecceOffStation", "*") + self:AddTransition("*", "TargetDetected", "*") + self:AddTransition("*", "TargetsSmoked", "*") + self:AddTransition("*", "TargetsFlared", "*") + self:AddTransition("*", "TargetLasing", "*") + self:AddTransition("*", "TargetLOSLost", "*") + self:AddTransition("*", "TargetReport", "*") + self:AddTransition("*", "TargetReportSent", "*") + self:AddTransition("Running", "Stop", "Stopped") + + -- Player Events + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:HandleEvent(EVENTS.Ejection, self._EventHandler) + self:HandleEvent(EVENTS.Crash, self._EventHandler) + self:HandleEvent(EVENTS.PilotDead, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + + self:__Start(-1) + local starttime = math.random(5,10) + self:__Status(-starttime) + + self:I(self.lid..self.version.." Started.") + + return self +end + +------------------------------------------------------------------------------------------ +-- TODO: Functions +------------------------------------------------------------------------------------------ + +--- [Internal] Event handling +-- @param #PLAYERRECCE self +-- @param Core.Event#EVENTDATA EventData +-- @return #PLAYERRECCE self +function PLAYERRECCE:_EventHandler(EventData) + self:T(self.lid.."_EventHandler: "..EventData.id) + if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then + if EventData.IniPlayerName then + self:T(self.lid.."Event for player: "..EventData.IniPlayerName) + if self.ClientMenus[EventData.IniPlayerName] then + self.ClientMenus[EventData.IniPlayerName]:Remove() + end + self.ClientMenus[EventData.IniPlayerName] = nil + self.LaserSpots[EventData.IniPlayerName] = nil + self.OnStation[EventData.IniPlayerName] = false + end + elseif EventData.id == EVENTS.PlayerEnterAircraft and EventData.IniCoalition == self.Coalition then + if EventData.IniPlayerName and EventData.IniGroup and self.UseSRS then + self:T(self.lid.."Event for player: "..EventData.IniPlayerName) + self.UnitLaserCodes[EventData.IniPlayerName] = 1688 + self.ClientMenus[EventData.IniPlayerName] = nil + self.LaserSpots[EventData.IniPlayerName] = nil + self.OnStation[EventData.IniPlayerName] = false + self:_BuildMenus() + end + end + return self +end + +--- (Internal) Function to determine clockwise direction to target. +-- @param #PLAYERRECCE self +-- @param Wrapper.Unit#UNIT unit The Helicopter +-- @param Wrapper.Unit#UNIT target The downed Group +-- @return #number direction +function PLAYERRECCE:_GetClockDirection(unit, target) + self:T(self.lid .. " _GetClockDirection") + + local _playerPosition = unit:GetCoordinate() -- get position of helicopter + local _targetpostions = target:GetCoordinate() -- get position of downed pilot + local _heading = unit:GetHeading() -- heading + local DirectionVec3 = _playerPosition:GetDirectionVec3( _targetpostions ) + local Angle = _playerPosition:GetAngleDegrees( DirectionVec3 ) + local clock = 12 + local hours = 0 + if _heading and Angle then + clock = 12 + --if angle == 0 then angle = 360 end + clock = _heading-Angle + hours = (clock/30)*-1 + clock = 12+hours + clock = UTILS.Round(clock,0) + if clock > 12 then clock = clock-12 end + end + if self.debug then + local text = string.format("Heading = %d, Angle = %d, Hours= %d, Clock = %d",_heading,Angle,hours,clock) + self:I(self.lid .. text) + end + return clock +end + +--- [User] Set a table of possible laser codes. +-- Each new RECCE can select a code from this table, default is 1688. +-- @param #PLAYERRECCE self +-- @param #list<#number> LaserCodes +-- @return #PLAYERRECCE +function PLAYERRECCE:SetLaserCodes( LaserCodes ) + self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes } + return self +end + +--- [User] Set a reference point coordinate for A2G Operations. Will be used in coordinate references. +-- @param #PLAYERRECCE self +-- @param Core.Point#COORDINATE Coordinate Coordinate of the RP +-- @param #string Name Name of the RP +-- @return #PLAYERRECCE +function PLAYERRECCE:SetReferencePoint(Coordinate,Name) + self.ReferencePoint = Coordinate + self.RPName = Name + if self.RPMarker then + self.RPMarker:Remove() + end + local text = string.format("%s RP %s\n%s\n%s\n%s",self.Name,Name,Coordinate:ToStringLLDDM(),Coordinate:ToStringLLDMS(),Coordinate:ToStringMGRS()) + self.RPMarker = MARKER:New(Coordinate,text) + self.RPMarker:ReadOnly() + self.RPMarker:ToCoalition(self.Coalition) + return self +end + +--- [User] Set PlayerTaskController. Allows to upload target reports to the controller, in turn creating tasks for other players. +-- @param #PLAYERRECCE self +-- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller +-- @return #PLAYERRECCE +function PLAYERRECCE:SetPlayerTaskController(Controller) + self.UseController = true + self.Controller = Controller + return self +end + +--- [User] Set a set of clients which will receive target reports +-- @param #PLAYERRECCE self +-- @param Core.Set#SET_CLIENT AttackSet +-- @return #PLAYERRECCE +function PLAYERRECCE:SetAttackSet(AttackSet) + self.AttackSet = AttackSet + return self +end + +--- [Internal] Get the view parameters from a Gazelle camera +-- @param #PLAYERRECCE self +-- @param Wrapper.Unit#UNIT Gazelle +-- @return #number cameraheading in degrees. +-- @return #number cameranodding in degrees. +-- @return #number maxview in meters. +-- @return #boolean cameraison If true, camera is on, else off. +function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) + self:T(self.lid.."GetGazelleVivianneSight") + local unit = Gazelle -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + local dcsunit = Unit.getByName(Gazelle:GetName()) + local vivihorizontal = dcsunit:getDrawArgumentValue(215) or 0 -- (not in MiniGun) 1 to -1 -- zero is straight ahead, 1/-1 = 180 deg + local vivivertical = dcsunit:getDrawArgumentValue(216) or 0 -- L/Mistral/Minigun model has no 216, ca 10deg up (=1) and down (=-1) + local vivioff = false + -- -1 = -180, 1 = 180 + -- Actual view -0,66 to 0,66 + -- Nick view -0,98 to 0,98 for +/- 30° + if vivihorizontal < -0.7 then + vivihorizontal = -0.7 + vivioff = true + return 0,0,0,false + elseif vivihorizontal > 0.7 then + vivihorizontal = 0.7 + vivioff = true + return 0,0,0,false + end + local horizontalview = vivihorizontal * -180 + local verticalview = vivivertical * -30 -- ca +/- 30° + local heading = unit:GetHeading() + local viviheading = (heading+horizontalview)%360 + local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff) + return viviheading, verticalview, maxview, not vivioff + end + return 0,0,0,false +end + +--- [Internal] Get the max line of sight based on unit head and camera nod via trigonometrie. Returns 0 if camera is off. +-- @param #PLAYERRECCE self +-- @param Wrapper.Unit#UNIT unit The unit which LOS we want +-- @param #number vheading Heading where the unit or camera is looking +-- @param #number vnod Nod down in degrees +-- @param #boolean vivoff Camera on or off +-- @return #number maxview Max view distance in meters +function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff) + self:T(self.lid.."_GetActualMaxLOSight") + if vivoff then return 0 end + local maxview = 0 + if unit and unit:IsAlive() then + local typename = unit:GetTypeName() + maxview = self.MaxViewDistance[typename] or 8000 + local CamHeight = self.Cameraheight[typename] or 0 + if vnod > 0 then + -- Looking down + -- determine max distance we're looking at + local beta = 90 + local gamma = math.floor(90-vnod) + local alpha = math.floor(180-beta-gamma) + local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight + local b = a / math.sin(math.rad(alpha)) + local c = b * math.sin(math.rad(gamma)) + maxview = c*1.2 -- +20% + end + end + return maxview +end + +--- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. +-- @param #PLAYERRECCE self +-- @param #boolean ShortCallsign If true, only call out the major flight number +-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. +-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized +-- callsigns from playername or group name. +-- @return #PLAYERRECCE self +function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) + if not ShortCallsign or ShortCallsign == false then + self.ShortCallsign = false + else + self.ShortCallsign = true + end + self.Keepnumber = Keepnumber or false + self.CallsignTranslations = CallsignTranslations + return self +end + +--- [Internal] Build a ZONE_POLYGON from a given viewport of a unit +-- @param #PLAYERRECCE self +-- @param Wrapper.Unit#UNIT unit The unit which is looking +-- @param #number vheading Heading where the unit or camera is looking +-- @param #number vnod Nod down in degrees +-- @param #number maxview Max line of sight, depending on height +-- @param #number angle Angle left/right to be added to heading to form a triangle +-- @param #boolean camon Camera is switched on +-- @param #boolean draw Draw the zone on the F10 map +-- @return Core.Zone#ZONE_POLYGON ViewZone or nil if camera is off +function PLAYERRECCE:_GetViewZone(unit, vheading, vnod, maxview, angle, camon, draw) + self:T(self.lid.."_GetViewZone") + local viewzone = nil + if not camon then return nil end + if unit and unit:IsAlive() then + local unitname = unit:GetName() + if self.ViewZone[unitname] then + self.ViewZone[unitname]:UndrawZone() + end + --local vheading, vnod, maxview, vivon = self:GetGazelleVivianneSight(unit) + local startpos = unit:GetCoordinate() + local heading1 = (vheading+angle)%360 + local heading2 = (vheading-angle)%360 + local pos1 = startpos:Translate(maxview,heading1) + local pos2 = startpos:Translate(maxview,heading2) + local array = {} + table.insert(array,startpos:GetVec2()) + table.insert(array,pos1:GetVec2()) + table.insert(array,pos2:GetVec2()) + viewzone = ZONE_POLYGON:NewFromPointsArray(unitname,array) + if draw then + viewzone:DrawZone(-1,{0,0,1},nil,nil,nil,1) + self.ViewZone[unitname] = viewzone + end + end + return viewzone +end + +--- [Internal] +--@param #PLAYERRECCE self +--@param Wrapper.Client#CLIENT client +--@return Core.Set#SET_UNIT Set of targets, can be empty! +--@return #number count Count of targets +function PLAYERRECCE:_GetKnownTargets(client) + self:T(self.lid.."_GetKnownTargets") + local finaltargets = SET_UNIT:New() + local targets = self.TargetCache:GetDataTable() + local playername = client:GetPlayerName() + for _,_target in pairs(targets) do + local targetdata = _target.PlayerRecceDetected -- Ops.PlayerRecce#PLAYERRECCE.PlayerRecceDetected + if targetdata.playername == playername then + finaltargets:Add(_target:GetName(),_target) + end + end + return finaltargets,finaltargets:CountAlive() +end + +--- [Internal] +--@param #PLAYERRECCE self +--@return #PLAYERRECCE self +function PLAYERRECCE:_CleanupTargetCache() + self:T(self.lid.."_CleanupTargetCache") + local cleancache = FIFO:New() + self.TargetCache:ForEach( + function(unit) + local pull = false + if unit and unit:IsAlive() then + if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then + local TNow = timer.getTime() + if TNow-unit.PlayerRecceDetected.timestamp > self.TForget then + -- Forget this unit + pull = true + unit.PlayerRecceDetected=nil + end + else + -- no timestamp + pull = true + end + else + -- dead + pull = true + end + if not pull then + cleancache:Push(unit,unit:GetName()) + end + end + ) + self.TargetCache = nil + self.TargetCache = cleancache + return self +end + +--- [Internal] +--@param #PLAYERRECCE self +--@param Wrapper.Unit#UNIT unit The FACA unit +--@param #boolean camera If true, use the unit's camera for targets in sight +--@return Core.Set#SET_UNIT Set of targets, can be empty! +--@return #number count Count of targets +function PLAYERRECCE:_GetTargetSet(unit,camera) + self:T(self.lid.."_GetTargetSet") + local finaltargets = SET_UNIT:New() + local finalcount = 0 + local heading,nod,maxview,angle = 0,30,8000,10 + local camon = true + local typename = unit:GetTypeName() + local name = unit:GetName() + if string.find(typename,"SA342") and camera then + heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit) + angle=10 + else + -- visual + heading = unit:GetHeading() + nod,maxview,camon = 10,1000,true + angle = 45 + end + local zone = self:_GetViewZone(unit,heading,nod,maxview,angle,camon) + if zone then + local redcoalition = "red" + if self.Coalition == coalition.side.RED then + redcoalition = "blue" + end + -- determine what we can see + local startpos = unit:GetCoordinate() + local targetset = SET_UNIT:New():FilterCategories("ground"):FilterActive(true):FilterZones({zone}):FilterCoalitions(redcoalition):FilterOnce() + self:T("Prefilter Target Count = "..targetset:CountAlive()) + -- TODO - Threat level filter? + -- TODO - Min distance from unit? + targetset:ForEach( + function(_unit) + local _unit = _unit -- Wrapper.Unit#UNIT + local _unitpos = _unit:GetCoordinate() + if startpos:IsLOS(_unitpos) then + self:T("Adding to final targets: ".._unit:GetName()) + finaltargets:Add(_unit:GetName(),_unit) + end + end + ) + finalcount = finaltargets:CountAlive() + self:T(string.format("%s Unit: %s | Targets in view %s",self.lid,name,finalcount)) + end + return finaltargets, finalcount, zone +end + +---[Internal] +--@param #PLAYERRECCE self +--@param Core.Set#SET_UNIT targetset Set of targets, can be empty! +--@return Wrapper.Unit#UNIT Target +function PLAYERRECCE:_GetHVTTarget(targetset) + self:T(self.lid.."_GetHVTTarget") + + -- get one target + -- local target = targetset:GetRandom() -- Wrapper.Unit#UNIT + + -- sort units + local unitsbythreat = {} + local minthreat = self.minthreatlevel or 0 + for _,_unit in pairs(targetset.Set) do + local unit = _unit -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + local threat = unit:GetThreatLevel() + if threat >= minthreat then + -- prefer radar units + if unit:HasAttribute("RADAR_BAND1_FOR_ARM") or unit:HasAttribute("RADAR_BAND2_FOR_ARM") or unit:HasAttribute("Optical Tracker") then + threat = 11 + end + table.insert(unitsbythreat,{unit,threat}) + end + end + end + + table.sort(unitsbythreat, function(a,b) + local aNum = a[2] -- Coin value of a + local bNum = b[2] -- Coin value of b + return aNum > bNum -- Return their comparisons, < for ascending, > for descending + end) + + return unitsbythreat[1][1] +end + +--- [Internal] +--@param #PLAYERRECCE self +--@param Wrapper.Client#CLIENT client The FACA unit +--@param Core.Set#SET_UNIT targetset Set of targets, can be empty! +--@return #PLAYERRECCE self +function PLAYERRECCE:_LaseTarget(client,targetset) + self:T(self.lid.."_LaseTarget") + -- get one target + local target = self:_GetHVTTarget(targetset) -- Wrapper.Unit#UNIT + local playername = client:GetPlayerName() + local laser = nil -- Core.Spot#SPOT + -- set laser + if not self.LaserSpots[playername] then + laser = SPOT:New(client) + if not self.UnitLaserCodes[playername] then + self.UnitLaserCodes[playername] = 1688 + end + laser.LaserCode = self.UnitLaserCodes[playername] or 1688 + self.LaserSpots[playername] = laser + else + laser = self.LaserSpots[playername] + end + if not laser:IsLasing() and target then + local relativecam = self.LaserRelativePos[client:GetTypeName()] + laser:SetRelativeStartPosition(relativecam) + local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688 + local lasingtime = self.lasingtime or 60 + local targettype = target:GetTypeName() + laser:LaseOn(target,lasercode,lasingtime) + self:__TargetLasing(-1,client,target,lasercode,lasingtime) + else + -- still looking at target? + local oldtarget=laser.Target + if targetset:IsNotInSet(oldtarget) then + -- lost LOS + local targettype = oldtarget:GetTypeName() + laser:LaseOff() + self:__TargetLOSLost(-1,client,oldtarget) + end + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_SetClientLaserCode(client,group,playername,code) + self:T(self.lid.."_SetClientLaserCode") + self.UnitLaserCodes[playername] = code or 1688 + if self.ClientMenus[playername] then + self.ClientMenus[playername]:Remove() + self.ClientMenus[playername]=nil + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_SwitchOnStation(client,group,playername) + self:T(self.lid.."_SwitchOnStation") + if not self.OnStation[playername] then + self.OnStation[playername] = true + self:__RecceOnStation(-1,client,playername) + else + self.OnStation[playername] = false + self:__RecceOffStation(-1,client,playername) + end + if self.ClientMenus[playername] then + self.ClientMenus[playername]:Remove() + self.ClientMenus[playername]=nil + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_SwitchLasing(client,group,playername) + self:T(self.lid.."_SwitchLasing") + if not self.AutoLase[playername] then + self.AutoLase[playername] = true + MESSAGE:New("Lasing is now ON",10,self.Name or "FACA"):ToClient(client) + else + self.AutoLase[playername] = false + MESSAGE:New("Lasing is now OFF",10,self.Name or "FACA"):ToClient(client) + end + if self.ClientMenus[playername] then + self.ClientMenus[playername]:Remove() + self.ClientMenus[playername]=nil + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_WIP(client,group,playername) + self:I(self.lid.."_WIP") + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_SmokeTargets(client,group,playername) + self:T(self.lid.."_SmokeTargets") + local cameraset = self:_GetTargetSet(client,true) -- Core.Set#SET_UNIT + local visualset = self:_GetTargetSet(client,false) -- Core.Set#SET_UNIT + cameraset:AddSet(visualset) + if cameraset:CountAlive() > 0 then + self:__TargetsSmoked(-1,client,playername,cameraset) + end + local highsmoke = self.SmokeColor.highsmoke + local medsmoke = self.SmokeColor.medsmoke + local lowsmoke = self.SmokeColor.lowsmoke + local lasersmoke = self.SmokeColor.lasersmoke + local laser = self.LaserSpots[playername] -- Core.Spot#SPOT + -- laser targer gets extra smoke + if laser and laser.Target and laser.Target:IsAlive() then + laser.Target:GetCoordinate():Smoke(lasersmoke) + if cameraset:IsInSet(laser.Target) then + cameraset:Remove(laser.Target:GetName(),true) + end + end + -- smoke everything else + for _,_unit in pairs(cameraset.Set) do + local unit = _unit --Wrapper.Unit#UNIT + if unit then + local coord = unit:GetCoordinate() + local threat = unit:GetThreatLevel() + if coord then + local color = lowsmoke + if threat > 7 then + color = medsmoke + elseif threat > 2 then + color = lowsmoke + end + coord:Smoke(color) + end + end + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_FlareTargets(client,group,playername) + self:T(self.lid.."_FlareTargets") + local cameraset = self:_GetTargetSet(client,true) -- Core.Set#SET_UNIT + local visualset = self:_GetTargetSet(client,false) -- Core.Set#SET_UNIT + cameraset:AddSet(visualset) + if cameraset:CountAlive() > 0 then + self:__TargetsFlared(-1,client,playername,cameraset) + end + local highsmoke = self.FlareColor.highflare + local medsmoke = self.FlareColor.medflare + local lowsmoke = self.FlareColor.lowflare + local lasersmoke = self.FlareColor.laserflare + local laser = self.LaserSpots[playername] -- Core.Spot#SPOT + -- laser targer gets extra smoke + if laser and laser.Target and laser.Target:IsAlive() then + laser.Target:GetCoordinate():Flare(lasersmoke) + if cameraset:IsInSet(laser.Target) then + cameraset:Remove(laser.Target:GetName(),true) + end + end + -- smoke everything else + for _,_unit in pairs(cameraset.Set) do + local unit = _unit --Wrapper.Unit#UNIT + if unit then + local coord = unit:GetCoordinate() + local threat = unit:GetThreatLevel() + if coord then + local color = lowsmoke + if threat > 7 then + color = medsmoke + elseif threat > 2 then + color = lowsmoke + end + coord:Flare(color) + end + end + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_UploadTargets(client,group,playername) + self:T(self.lid.."_UploadTargets") + local targetset, number = self:_GetTargetSet(client,true) + local vtargetset, vnumber = self:_GetTargetSet(client,false) + local totalset = SET_UNIT:New() + totalset:AddSet(targetset) + totalset:AddSet(vtargetset) + if totalset:CountAlive() > 0 then + self.Controller:AddTarget(totalset) + self:__TargetReportSent(1,client,playername,totalset) + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_ReportLaserTargets(client,group,playername) +self:T(self.lid.."_ReportLaserTargets") + local targetset, number = self:_GetTargetSet(client,true) + if number > 0 and self.AutoLase[playername] then + local Settings = ( client and _DATABASE:GetPlayerSettings( playername ) ) or _SETTINGS + local target = self:_GetHVTTarget(targetset) -- the one we're lasing + local ThreatLevel = target:GetThreatLevel() + local ThreatLevelText = "high" + if ThreatLevel > 3 and ThreatLevel < 8 then + ThreatLevelText = "medium" + elseif ThreatLevel <= 3 then + ThreatLevelText = "low" + end + local ThreatGraph = "[" .. string.rep( "■", ThreatLevel ) .. string.rep( "□", 10 - ThreatLevel ) .. "]: "..ThreatLevel + local report = REPORT:New("Lasing Report") + report:Add(string.rep("-",15)) + report:Add("Target type: "..target:GetTypeName()) + report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") + if not self.ReferencePoint then + report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) + else + report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) + end + report:Add("Laser Code: "..self.UnitLaserCodes[playername] or 1688) + report:Add(string.rep("-",15)) + local text = report:Text() + self:__TargetReport(-1,client,targetset,target,text) + else + local report = REPORT:New("Lasing Report") + report:Add(string.rep("-",15)) + report:Add("N O T A R G E T S") + report:Add(string.rep("-",15)) + local text = report:Text() + self:__TargetReport(-1,client,nil,nil,text) + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_ReportVisualTargets(client,group,playername) + self:T(self.lid.."_ReportVisualTargets") + local targetset, number = self:_GetKnownTargets(client) + if number > 0 then + local Settings = ( client and _DATABASE:GetPlayerSettings( playername ) ) or _SETTINGS + local ThreatLevel = targetset:CalculateThreatLevelA2G() + local ThreatLevelText = "high" + if ThreatLevel > 3 and ThreatLevel < 8 then + ThreatLevelText = "medium" + elseif ThreatLevel <= 3 then + ThreatLevelText = "low" + end + local ThreatGraph = "[" .. string.rep( "■", ThreatLevel ) .. string.rep( "□", 10 - ThreatLevel ) .. "]: "..ThreatLevel + local report = REPORT:New("Target Report") + report:Add(string.rep("-",15)) + report:Add("Target count: "..number) + report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") + if not self.ReferencePoint then + report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) + else + report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) + end + report:Add(string.rep("-",15)) + local text = report:Text() + self:__TargetReport(-1,client,targetset,nil,text) + else + local report = REPORT:New("Target Report") + report:Add(string.rep("-",15)) + report:Add("N O T A R G E T S") + report:Add(string.rep("-",15)) + local text = report:Text() + self:__TargetReport(-1,client,nil,nil,text) + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param #PLAYERRECCE self +function PLAYERRECCE:_BuildMenus() + self:T(self.lid.."_BuildMenus") + local clients = self.PlayerSet -- Core.Set#SET_CLIENT + local clientset = clients:GetSetObjects() + for _,_client in pairs(clientset) do + local client = _client -- Wrapper.Client#CLIENT + if client and client:IsAlive() then + local playername = client:GetPlayerName() + if not self.UnitLaserCodes[playername] then + self:_SetClientLaserCode(nil,nil,playername,1688) + end + local group = client:GetGroup() + if not self.ClientMenus[playername] then + local canlase = self.CanLase[client:GetTypeName()] + self.ClientMenus[playername] = MENU_GROUP:New(group,self.MenuName or self.Name or "RECCE") + local txtonstation = self.OnStation[playername] and "ON" or "OFF" + local text = string.format("Switch On-Station (%s)",txtonstation) + local onstationmenu = MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchOnStation,self,client,group,playername) + if self.OnStation[playername] then + local smokemenu = MENU_GROUP_COMMAND:New(group,"Smoke Targets",self.ClientMenus[playername],self._SmokeTargets,self,client,group,playername) + local smokemenu = MENU_GROUP_COMMAND:New(group,"Flare Targets",self.ClientMenus[playername],self._FlareTargets,self,client,group,playername) + if canlase then + local txtonstation = self.AutoLase[playername] and "ON" or "OFF" + local text = string.format("Switch Lasing (%s)",txtonstation) + local lasemenu = MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchLasing,self,client,group,playername) + end + local targetmenu = MENU_GROUP:New(group,"Target Report",self.ClientMenus[playername]) + if canlase then + local reportL = MENU_GROUP_COMMAND:New(group,"Laser Target",targetmenu,self._ReportLaserTargets,self,client,group,playername) + end + local reportV = MENU_GROUP_COMMAND:New(group,"Visual Targets",targetmenu,self._ReportVisualTargets,self,client,group,playername) + if self.UseController then + local text = string.format("Target Upload to %s",self.Controller.MenuName or self.Controller.Name) + local upload = MENU_GROUP_COMMAND:New(group,text,targetmenu,self._UploadTargets,self,client,group,playername) + end + if canlase then + local lasecodemenu = MENU_GROUP:New(group,"Set Laser Code",self.ClientMenus[playername]) + local codemenu = {} + for _,_code in pairs(self.LaserCodes) do + --self._SetClientLaserCode,self,client,group,playername) + if _code == self.UnitLaserCodes[playername] then + _code = tostring(_code).."(*)" + end + codemenu[playername.._code] = MENU_GROUP_COMMAND:New(group,tostring(_code),lasecodemenu,self._SetClientLaserCode,self,client,group,playername,_code) + end + end + end + end + end + end + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Core.Set#SET_UNIT targetset +-- @param Wrapper.Client#CLIENT client +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_CheckNewTargets(targetset,client,playername) + self:T(self.lid.."_CheckNewTargets") + local tempset = SET_UNIT:New() + targetset:ForEach( + function(unit) + if unit and unit:IsAlive() then + self:T("Report unit: "..unit:GetName()) + if not unit.PlayerRecceDetected then + self:T("New unit: "..unit:GetName()) + unit.PlayerRecceDetected = { + detected = true, + recce = client, + playername = playername, + timestamp = timer.getTime() + } + --self:TargetDetected(unit,client,playername) + tempset:Add(unit:GetName(),unit) + if not self.TargetCache:HasUniqueID(unit:GetName()) then + self.TargetCache:Push(unit,unit:GetName()) + end + end + if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then + local TNow = timer.getTime() + if TNow-unit.PlayerRecceDetected.timestamp > self.TForget then + unit.PlayerRecceDetected = { + detected = true, + recce = client, + playername = playername, + timestamp = timer.getTime() + } + if not self.TargetCache:HasUniqueID(unit:GetName()) then + self.TargetCache:Push(unit,unit:GetName()) + end + tempset:Add(unit:GetName(),unit) + end + end + end + end + ) + local targetsbyclock = {} + for i=1,12 do + targetsbyclock[i] = {} + end + tempset:ForEach( + function (object) + local obj=object -- Wrapper.Unit#UNIT + local clock = self:_GetClockDirection(client,obj) + table.insert(targetsbyclock[clock],obj) + end + ) + self:I("Known target Count: "..self.TargetCache:Count()) + if tempset:CountAlive() > 0 then + self:TargetDetected(targetsbyclock,client,playername) + end + return self +end + +--- [User] Set SRS TTS details - see @{Sound.SRS} for details +-- @param #PLAYERRECCE self +-- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations! +-- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies! +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- @param #string Gender (Optional) Defaults to "male" +-- @param #string Culture (Optional) Defaults to "en-US" +-- @param #number Port (Optional) Defaults to 5002 +-- @param #string Voice (Optional) Use a specifc voice with the @{Sound.SRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. +-- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS. +-- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest) +-- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS +-- @return #PLAYERRECCE self +function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) + self:T(self.lid.."SetSRS") + self.PathToSRS = PathToSRS or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- + self.Gender = Gender or "male" -- + self.Culture = Culture or "en-US" -- + self.Port = Port or 5002 -- + self.Voice = Voice -- + self.PathToGoogleKey = PathToGoogleKey -- + self.Volume = Volume or 1.0 -- + self.UseSRS = true + self.Frequency = Frequency or {127,251} -- + self.BCFrequency = self.Frequency + self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} -- + self.BCModulation = self.Modulation + -- set up SRS + self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,self.Volume) + self.SRS:SetCoalition(self.Coalition) + self.SRS:SetLabel(self.MenuName or self.Name) + self.SRS:SetGender(self.Gender) + self.SRS:SetCulture(self.Culture) + self.SRS:SetPort(self.Port) + self.SRS:SetVoice(self.Voice) + if self.PathToGoogleKey then + self.SRS:SetGoogle(self.PathToGoogleKey) + end + self.SRSQueue = MSRSQUEUE:New(self.MenuName or self.Name) + self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) + return self +end + +--- [User] For SRS - Switch to only transmit if there are players on the server. +-- @param #PLAYERRECCE self +-- @param #boolean Switch If true, only send SRS if there are alive Players. +-- @return #PLAYERRECCE self +function PLAYERRECCE:SetTransmitOnlyWithPlayers(Switch) + self.TransmitOnlyWithPlayers = Switch + if self.SRSQueue then + self.SRSQueue:SetTransmitOnlyWithPlayers(Switch) + end + return self +end + +--- [User] Set the top menu name to a custom string. +-- @param #PLAYERRECCE self +-- @param #string Name The name to use as the top menu designation. +-- @return #PLAYERRECCE self +function PLAYERRECCE:SetMenuName(Name) + self:T(self.lid.."SetMenuName: "..Name) + self.MenuName = Name + return self +end + +--- [Internal] Get text for text-to-speech. +-- Numbers are spaced out, e.g. "Heading 180" becomes "Heading 1 8 0 ". +-- @param #PLAYERRECCE self +-- @param #string text Original text. +-- @return #string Spoken text. +function PLAYERRECCE:_GetTextForSpeech(text) + + -- Space out numbers. + text=string.gsub(text,"%d","%1 ") + -- get rid of leading or trailing spaces + text=string.gsub(text,"^%s*","") + text=string.gsub(text,"%s*$","") + + return text +end + +------------------------------------------------------------------------------------------ +-- TODO: FSM Functions +------------------------------------------------------------------------------------------ + +--- [Internal] Status Loop +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterStatus(From, Event, To) + self:I({From, Event, To}) + + if not self.timestamp then + self.timestamp = timer.getTime() + else + local tNow = timer.getTime() + if tNow - self.timestamp >= 60 then + self:_CleanupTargetCache() + self.timestamp = timer.getTime() + end + end + + self:_BuildMenus() + + self.PlayerSet:ForEachClient( + function(Client) + local client = Client -- Wrapper.Client#CLIENT + local playername = client:GetPlayerName() + if client and client:IsAlive() and self.OnStation[playername] then + + -- targets on camera + local targetset, targetcount, tzone = self:_GetTargetSet(client,true) + if targetset then + if self.ViewZone[playername] then + self.ViewZone[playername]:UndrawZone() + end + if self.debug and tzone then + self.ViewZone[playername]=tzone:DrawZone(self.Coalition,{0,0,1},nil,nil,nil,1) + end + end + self:T({targetcount=targetcount}) + -- lase targets on camera + if targetcount > 0 then + if self.CanLase[client:GetTypeName()] and self.AutoLase[playername] then + -- DONE move to lase at will + self:_LaseTarget(client,targetset) + end + end + + -- visual targets + local vistargetset, vistargetcount, viszone = self:_GetTargetSet(client,false) + if vistargetset then + if self.ViewZoneVisual[playername] then + self.ViewZoneVisual[playername]:UndrawZone() + end + if self.debug and viszone then + self.ViewZoneVisual[playername]=viszone:DrawZone(self.Coalition,{1,0,0},nil,nil,nil,3) + end + end + self:T({visualtargetcount=vistargetcount}) + targetset:AddSet(vistargetset) + self:_CheckNewTargets(targetset,client,playername) + end + end + ) + + self:__Status(-10) + return self +end + +--- [Internal] Recce on station +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param #string Playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) + self:T({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + if self.ReferencePoint then + local Settings = Client and _DATABASE:GetPlayerSettings(Playername) or _SETTINGS -- Core.Settings#SETTINGS + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) + end + if self.debug then + local text = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + end + local text1 = "Party time!" + local text2 = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext) + local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext) + text2tts = self:_GetTextForSpeech(text2tts) + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2) + self.SRSQueue:NewTransmission(text2tts,nil,self.SRS,nil,2) + MESSAGE:New(text2,10,self.Name or "FACA"):ToCoalition(self.Coalition) + else + MESSAGE:New(text1,10,self.Name or "FACA"):ToClient(Client) + MESSAGE:New(text2,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + +--- [Internal] Recce off station +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param #string Playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) + self:T({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + if self.ReferencePoint then + local Settings = Client and _DATABASE:GetPlayerSettings(Playername) or _SETTINGS -- Core.Settings#SETTINGS + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) + end + local text = string.format("All stations, FACA %s leaving station\nat %s, good bye!",callsign, coordtext) + local texttts = string.format("All stations, FACA %s leaving station at %s, good bye!",callsign, coordtext) + texttts = self:_GetTextForSpeech(texttts) + if self.debug then + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + end + local text1 = "Going home!" + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2) + self.SRSQueue:NewTransmission(texttts,nil,self.SRS,nil,2) + MESSAGE:New(text,10,self.Name or "FACA"):ToCoalition(self.Coalition) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + +--- [Internal] Target Detected +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #table Targetsbyclock +-- @param Wrapper.Client#CLIENT Client +-- @param #string Playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterTargetDetected(From, Event, To, Targetsbyclock, Client, Playername) + self:T({From, Event, To}) + + local dunits = "meters" + local Settings = Client and _DATABASE:GetPlayerSettings(Playername) or _SETTINGS -- Core.Settings#SETTINGS + local clientcoord = Client:GetCoordinate() + + for i=1,12 do + local targets = Targetsbyclock[i] --#table + local targetno = #targets + if targetno == 1 then + -- only one + local targetdistance = clientcoord:Get2DDistance(targets[1]:GetCoordinate()) or 100 + local Threatlvl = targets[1]:GetThreatLevel() + local ThreatTxt = "Low" + if Threatlvl >=7 then + ThreatTxt = "Medium" + elseif Threatlvl >=3 then + ThreatTxt = "High" + end + if Settings:IsMetric() then + targetdistance = UTILS.Round(targetdistance,-2) + else + targetdistance = UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) + dunits = "feet" + end + local text = string.format("Target! %s! %s o\'clock, %d %s!", ThreatTxt,i, targetdistance, dunits) + local ttstext = string.format("Target! %s! %s oh clock, %d %s!", ThreatTxt, i, targetdistance, dunits) + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + elseif targetno > 1 then + -- multiple + local function GetNearest(TTable) + local distance = 10000000 + for _,_unit in pairs(TTable) do + local dist = clientcoord:Get2DDistance(_unit:GetCoordinate()) or 100 + if dist < distance then + distance = dist + end + end + return distance + end + local targetdistance = GetNearest(targets) + if Settings:IsMetric() then + targetdistance = UTILS.Round(targetdistance,-2) + else + targetdistance = UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) + dunits = "feet" + end + local text = string.format(" %d targets! %s o\'clock, %d %s!", targetno, i, targetdistance, dunits) + local ttstext = string.format("%d targets! %s oh clock, %d %s!", targetno, i, targetdistance, dunits) + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + end + end + return self +end + +--- [Internal] Targets Smoked +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param #string Playername +-- @param Core.Set#SET_UNIT TargetSet +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet) + self:T({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + if self.debug then + local text = string.format("All stations, %s smoked targets\nat %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + end + if self.AttackSet then + for _,_client in pairs(self.AttackSet.Set) do + local client = _client --Wrapper.Client#CLIENT + if client and client:IsAlive() then + local Settings = client and _DATABASE:GetPlayerSettings(client:GetPlayerName()) or _SETTINGS + local coordtext = coord:ToStringA2G(client,Settings) + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) + end + local text = string.format("All stations, %s smoked targets\nat %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToClient(client) + end + end + end + local text = "Smoke on!" + local ttstext = "Smoke and Mirrors!" + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + +--- [Internal] Targets Flared +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param #string Playername +-- @param Core.Set#SET_UNIT TargetSet +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet) + self:T({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + if self.debug then + local text = string.format("All stations, %s flared\ntargets at %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + end + if self.AttackSet then + for _,_client in pairs(self.AttackSet.Set) do + local client = _client --Wrapper.Client#CLIENT + if client and client:IsAlive() then + local Settings = client and _DATABASE:GetPlayerSettings(client:GetPlayerName()) or _SETTINGS + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) + end + local coordtext = coord:ToStringA2G(client,Settings) + local text = string.format("All stations, %s flared targets\nat %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToClient(client) + end + end + end + local text = "Fireworks!" + local ttstext = "Fire works!" + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + +--- [Internal] Target lasing +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param Wrapper.Unit#UNIT Target +-- @param #number Lasercode +-- @param #number Lasingtime +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime) + self:T({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition,Settings) + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) + end + local targettype = Target:GetTypeName() + if self.debug then + local text = string.format("All stations, %s lasing %s\nat %s!\nCode %d, Duration %d seconds!",callsign, targettype, coordtext, Lasercode, Lasingtime) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + end + local text = "Lasing!" + local ttstext = "Laser on!" + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + +--- [Internal] Laser lost LOS +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param Wrapper.Unit#UNIT Target +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target) + self:T({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition,Settings) + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) + end + local targettype = Target:GetTypeName() + if self.debug then + local text = string.format("All stations, %s lost sight of %s\nat %s!",callsign, targettype, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + end + local text = "Lost LOS!" + local ttstext = "Lost L O S!" + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + +--- [Internal] Target report +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param Core.Set#SET_UNIT TargetSet +-- @param Wrapper.Unit#UNIT Target +-- @param #string Text +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterTargetReport(From, Event, To, Client, TargetSet, Target, Text) + self:T({From, Event, To}) + MESSAGE:New(Text,45,self.Name or "FACA"):ToClient(Client) + if self.AttackSet then + -- send message to AttackSet + for _,_client in pairs(self.AttackSet.Set) do + local client = _client -- Wrapper.Client#CLIENT + if client and client:IsAlive() then + MESSAGE:New(Text,45,self.Name or "FACA"):ToClient(client) + end + end + end + --self:__TargetReportSent(-2,Client, TargetSet, Target, Text) + return self +end + +--- [Internal] Target data upload +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param Core.Set#SET_UNIT TargetSet +-- @param Wrapper.Unit#UNIT Target +-- @param #string Text +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterTargetReportSent(From, Event, To, Client, TargetSet) + self:T({From, Event, To}) + local text = "Upload completed!" + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,1,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + + +--- [Internal] Stop +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterStop(From, Event, To) + self:I({From, Event, To}) + -- Player Events + self:UnHandleEvent(EVENTS.PlayerLeaveUnit) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.Crash) + self:UnHandleEvent(EVENTS.PilotDead) + self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + return self +end + +------------------------------------------------------------------------------------------ +-- TODO: END PLAYERRECCE +------------------------------------------------------------------------------------------ diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 2202b73ee..63edec3aa 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -49,6 +49,7 @@ do -- @field #table conditionFailure = {}, -- @field Ops.PlayerTask#PLAYERTASKCONTROLLER TaskController -- @field #number timestamp +-- @field #number lastsmoketime -- @extends Core.Fsm#FSM @@ -76,11 +77,12 @@ PLAYERTASK = { conditionFailure = {}, TaskController = nil, timestamp = 0, + lastsmoketime = 0, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.2" +PLAYERTASK.version="0.1.3" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -112,6 +114,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) self.TaskController = nil -- Ops.PlayerTask#PLAYERTASKCONTROLLER self.timestamp = timer.getAbsTime() self.TTSType = TTSType or "close air support" + self.lastsmoketime = 0 if Repeat then self.Repeat = true @@ -392,10 +395,13 @@ end function PLAYERTASK:SmokeTarget(Color) self:T(self.lid.."SmokeTarget") local color = Color or SMOKECOLOR.Red - if self.Target then + if not self.lastsmoketime then self.lastsmoketime = 0 end + local TDiff = timer.getAbsTime() - self.lastsmoketime + if self.Target and TDiff > 299 then local coordinate = self.Target:GetCoordinate() if coordinate then coordinate:Smoke(color) + self.lastsmoketime = timer.getAbsTime() end end return self @@ -754,6 +760,8 @@ do -- @field #table PlayerFlashMenu -- @field #table PlayerJoinMenu -- @field #table PlayerInfoMenu +-- @field #boolean noflaresmokemenu +-- @field #boolean TransmitOnlyWithPlayers -- @extends Core.Fsm#FSM --- @@ -1055,6 +1063,8 @@ PLAYERTASKCONTROLLER = { PlayerFlashMenu = {}, PlayerJoinMenu = {}, PlayerInfoMenu = {}, + noflaresmokemenu = false, + TransmitOnlyWithPlayers = true, } --- @@ -1213,7 +1223,7 @@ PLAYERTASKCONTROLLER.Messages = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.36" +PLAYERTASKCONTROLLER.version="0.1.38" --- Constructor -- @param #PLAYERTASKCONTROLLER self @@ -1267,6 +1277,8 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.ShortCallsign = true self.Keepnumber = false self.CallsignTranslations = nil + + self.noflaresmokemenu = false if ClientFilter then self.ClientSet = SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterPrefixes(ClientFilter):FilterStart() @@ -1388,6 +1400,36 @@ function PLAYERTASKCONTROLLER:SetAllowFlashDirection(OnOff) return self end +--- [User] Do not show menu entries to smoke or flare targets +-- @param #PLAYERTASKCONTROLLER self +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetDisableSmokeFlareTask() + self:T(self.lid.."SetDisableSmokeFlareTask") + self.noflaresmokemenu = true + return self +end + +--- [User] For SRS - Switch to only transmit if there are players on the server. +-- @param #PLAYERTASKCONTROLLER self +-- @param #boolean Switch If true, only send SRS if there are alive Players. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetTransmitOnlyWithPlayers(Switch) + self.TransmitOnlyWithPlayers = Switch + if self.SRSQueue then + self.SRSQueue:SetTransmitOnlyWithPlayers(Switch) + end + return self +end + +--- [User] Show menu entries to smoke or flare targets (on by default!) +-- @param #PLAYERTASKCONTROLLER self +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetEnableSmokeFlareTask() + self:T(self.lid.."SetEnableSmokeFlareTask") + self.noflaresmokemenu = false + return self +end + --- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. -- @param #PLAYERTASKCONTROLLER self -- @param #boolean ShortCallsign If true, only call out the major flight number @@ -2716,8 +2758,8 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess) local active = MENU_GROUP_DELAYED:New(group,menuactive,topmenu) local info = MENU_GROUP_COMMAND_DELAYED:New(group,menuinfo,active,self._ActiveTaskInfo,self,group,client) local mark = MENU_GROUP_COMMAND_DELAYED:New(group,menumark,active,self._MarkTask,self,group,client) - if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then - -- no smoking/flaring here if A2A + if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A or self.noflaresmokemenu then + -- no smoking/flaring here if A2A or designer has set to false local smoke = MENU_GROUP_COMMAND_DELAYED:New(group,menusmoke,active,self._SmokeTask,self,group,client) local flare = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._FlareTask,self,group,client) end @@ -3006,6 +3048,7 @@ function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Cultu self.SRS:SetGoogle(self.PathToGoogleKey) end self.SRSQueue = MSRSQUEUE:New(self.MenuName or self.Name) + self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) return self end diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index bc1bb67dc..a5ce049f0 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -882,6 +882,8 @@ MSRSQUEUE = { -- @field #boolean isplaying If true, transmission is currently playing. -- @field #number Tplay Mission time (abs) in seconds when the transmission should be played. -- @field #number interval Interval in seconds before next transmission. +-- @field #boolean TransmitOnlyWithPlayers If true, only transmit if there are alive Players. +-- @field Core.Set#SET_CLIENT PlayerSet PlayerSet created when TransmitOnlyWithPlayers == true --- Create a new MSRSQUEUE object for a given radio frequency/modulation. -- @param #MSRSQUEUE self @@ -932,6 +934,23 @@ function MSRSQUEUE:AddTransmission(transmission) return self end +--- Switch to only transmit if there are players on the server. +-- @param #MSRSQUEUE self +-- @param #boolean Switch If true, only send SRS if there are alive Players. +-- @return #MSRSQUEUE self +function MSRSQUEUE:SetTransmitOnlyWithPlayers(Switch) + self.TransmitOnlyWithPlayers = Switch + if Switch == false or Switch==nil then + if self.PlayerSet then + self.PlayerSet:FilterStop() + end + self.PlayerSet = nil + else + self.PlayerSet = SET_CLIENT:New():FilterStart() + end + return self +end + --- Create a new transmission and add it to the radio queue. -- @param #MSRSQUEUE self -- @param #string text Text to play. @@ -946,7 +965,13 @@ end -- @param #number modulation Radio modulation if other then MSRS default. -- @return #MSRSQUEUE.Transmission Radio transmission table. function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation) - + + if self.TransmitOnlyWithPlayers then + if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then + return self + end + end + -- Sanity checks. if not text then self:E(self.lid.."ERROR: No text specified.") diff --git a/Moose Development/Moose/Utilities/Socket.lua b/Moose Development/Moose/Utilities/Socket.lua index a5626600e..78d7c6e44 100644 --- a/Moose Development/Moose/Utilities/Socket.lua +++ b/Moose Development/Moose/Utilities/Socket.lua @@ -58,7 +58,7 @@ SOCKET.DataType={ --- SOCKET class version. -- @field #string version -SOCKET.version="0.1.0" +SOCKET.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -121,6 +121,10 @@ end -- @return #SOCKET self function SOCKET:SendTable(Table) + -- Add server name for DCS + Table.server_name=BASE.ServerName or "Unknown" + + -- Encode json table. local json= self.json:encode(Table) -- Debug info. diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 4eea18a66..1a123ec76 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -100,6 +100,7 @@ Ops/Awacs.lua Ops/Operation.lua Ops/FlightControl.lua Ops/PlayerTask.lua +Ops/PlayerRecce.lua AI/AI_Balancer.lua AI/AI_Air.lua