From dddb9ff713f03e86e0206bec0800d82045a7395f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 28 Sep 2022 11:49:06 +0200 Subject: [PATCH 01/19] #ZONE_CAPTURE_COALITION - allow zone to be a ZONE_POLYGON --- Moose Development/Moose/Core/Zone.lua | 318 +++++++++++++++++- .../Moose/Functional/ZoneCaptureCoalition.lua | 2 +- .../Moose/Functional/ZoneGoal.lua | 17 +- 3 files changed, 314 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 1ec83be66..0919d6e7d 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 @@ -2417,6 +2413,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/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() From de415384f3fb1ff58be2a3a075d050f988f36b04 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 28 Sep 2022 13:07:37 +0200 Subject: [PATCH 02/19] #PLAYERTASK - a target can only be smoked again after 5 mins (that's how long smoke lasts) #PLAYERTASKCONTROLLER - added option to hide smoke&flare menus --- Moose Development/Moose/Ops/PlayerTask.lua | 38 +++++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 2202b73ee..18ed99d5e 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,7 @@ do -- @field #table PlayerFlashMenu -- @field #table PlayerJoinMenu -- @field #table PlayerInfoMenu +-- @field #boolean noflaresmokemenu -- @extends Core.Fsm#FSM --- @@ -1055,6 +1062,7 @@ PLAYERTASKCONTROLLER = { PlayerFlashMenu = {}, PlayerJoinMenu = {}, PlayerInfoMenu = {}, + noflaresmokemenu = false, } --- @@ -1213,7 +1221,7 @@ PLAYERTASKCONTROLLER.Messages = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.36" +PLAYERTASKCONTROLLER.version="0.1.37" --- Constructor -- @param #PLAYERTASKCONTROLLER self @@ -1267,6 +1275,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 +1398,24 @@ 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] 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 +2744,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 From e8ace49e8b8126fd18e9cc2a4d032f877920dd38 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 29 Sep 2022 16:43:40 +0200 Subject: [PATCH 03/19] #SPOT - Set relative position to start lasing #ZONE_POLYGON - Added `ZONE_POLYGON:NewFromPointsArray( ZoneName, PointsArray )` --- Moose Development/Moose/Core/Spot.lua | 20 +++++++++++++++++--- Moose Development/Moose/Core/Zone.lua | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) 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 0919d6e7d..296dbb3ba 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -2381,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. From a8a92c00fe473e7ca8ed9483ebeb65307f569c24 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 30 Sep 2022 14:41:38 +0200 Subject: [PATCH 04/19] #AUTOLASE * Don't forget min threatlevel 0 on unit targets --- Moose Development/Moose/Functional/Autolase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2fc7139f6bd97e6f4ba980a5aac80e7db2576d74 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 30 Sep 2022 14:47:51 +0200 Subject: [PATCH 05/19] #SET * Added code to`SET:IsInSet(Object)` to be functional --- Moose Development/Moose/Core/Set.lua | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) 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. From 43856341e640a22396d0c7a4461b4a55716fd174 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 30 Sep 2022 18:49:08 +0200 Subject: [PATCH 06/19] #PLAYERRECCE * Initial Release --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/PlayerRecce.lua | 1041 +++++++++++++++++++ Moose Setup/Moose.files | 1 + 3 files changed, 1043 insertions(+) create mode 100644 Moose Development/Moose/Ops/PlayerRecce.lua 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/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua new file mode 100644 index 000000000..9d4e771f0 --- /dev/null +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -0,0 +1,1041 @@ +--- **Ops** - Defines an extensive API to manage 3D points in the DCS World 3D simulation space. +-- +-- ## 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 +-- +-- === +-- +-- # 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 +-- TODO: A lot... +------------------------------------------------------------------------------------------------------------------- + +--- 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 +-- @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 +-- +-- * Simplifies defining, executing and controlling of Player tasks +-- * TBD +-- +-- 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.1", + ViewZone = {}, + ViewZoneVisual = {}, + PlayerSet = nil, + debug = true, + LaserSpots = {}, + UnitLaserCodes = {}, + LaserCodes = {}, + ClientMenus = {}, + OnStation = {}, + minthreatlevel = 0, + lasingtime = 60, + AutoLase = {}, + AttackSet = nil, +} + +--- +-- @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"] = 5000, + ["SA342Mistral"] = 5000, + ["SA342Minigun"] = 5000, + ["SA342L"] = 5000, +} + +--- +-- @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, + ["SA342L"] = true, +} + +--- +-- @param #PLAYERRECCE self +-- @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 + + -- 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) + + return self +end + +--- [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:I(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:I(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 + +--- [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 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:I(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:I(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 5000 + 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 + +--- [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:I(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.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:I(self.lid.."_GetTargetSet") + local finaltargets = SET_UNIT:New() + local finalcount = 0 + local heading,nod,maxview,angle = 0,30,5000,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:I("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:I("Adding to final targets: ".._unit:GetName()) + finaltargets:Add(_unit:GetName(),_unit) + end + end + ) + finalcount = finaltargets:CountAlive() + self:I(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:I(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:I(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 + --function laser:OnAfterLaseOff(From,Event,To) + --MESSAGE:New("Finished lasing",15,"Info"):ToClient(client) + --end + 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) + --MESSAGE:New(string.format("Lasing Target %s with Code %d",targettype,lasercode),15,"Info"):ToClient(client) + 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) + --MESSAGE:New(string.format("Lost LOS on target %s!",targettype),15,"Info"):ToClient(client) + 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:I(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:I(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:I(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:I(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) + self:__TargetsSmoked(-1,client,playername,cameraset) + local highsmoke = SMOKECOLOR.Orange + local medsmoke = SMOKECOLOR.White + local lowsmoke = SMOKECOLOR.Green + local lasersmoke = SMOKECOLOR.Red + 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:I(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) + self:__TargetsFlared(-1,client,playername,cameraset) + local highsmoke = FLARECOLOR.Yellow + local medsmoke = FLARECOLOR.White + local lowsmoke = FLARECOLOR.Green + local lasersmoke = FLARECOLOR.Red + 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:_ReportLaserTargets(client,group,playername) +self:I(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..")") + report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) + --report:Add("Location: "..target:GetCoordinate():ToStringA2G(client,Settings)) + 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:I(self.lid.."_ReportVisualTargets") + local targetset, number = self:_GetTargetSet(client,false) + 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..")") + report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) + --report:Add("Location: "..client:GetCoordinate():ToStringA2G(client,Settings)) + 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:I(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.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 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:I(self.lid.."_CheckNewTargets") + targetset:ForEachUnit( + function(unit) + if unit and unit:IsAlive() then + if not unit.PlayerRecceDetected then + unit.PlayerRecceDetected = { + detected = true, + recce = client, + playername = playername, + timestamp = timer.getTime() + } + self:__TargetDetected(-1,unit,client,playername) + end + end + end + ) + return self +end + +--- [Internal] +-- @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}) + + 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:I({targetcount=targetcount}) + -- 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:I({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 + -- Report new targets + targetset:AddSet(vistargetset) + self:_CheckNewTargets(targetset,client,playername) + end + end + ) + + self:__Status(-10) + return self +end + +--- [Internal] +-- @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:I({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(true,true) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + 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) + return self +end + +--- [Internal] +-- @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:I({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(true,true) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + local text = string.format("All stations, FACA %s leaving station\nat %s, going home!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + return self +end + +--- [Internal] +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Unit#UNIT Target +-- @param Wrapper.Client#CLIENT Client +-- @param #string Playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterTargetDetected(From, Event, To, Target, Client, Playername) + self:I({From, Event, To}) + if self.debug then + local text = string.format("New target %s detected by %s!",Target:GetTypeName(),Playername) + MESSAGE:New(text,10,self.Name or "FACA"):ToCoalition(self.Coalition) + end + return self +end + +--- [Internal] +-- @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:I({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(true,true) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + local text = string.format("All stations, FACA %s smoked targets\nat %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + return self +end + +--- [Internal] +-- @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:I({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(true,true) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + local text = string.format("All stations, FACA %s flared\ntargets at %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + return self +end + +--- [Internal] +-- @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:I({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(true,true) + local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition,Settings) + local targettype = Target:GetTypeName() + local text = string.format("All stations, FACA %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) + return self +end + +--- [Internal] +-- @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:I({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(true,true) + local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition,Settings) + local targettype = Target:GetTypeName() + local text = string.format("All stations, FACA %s lost sight of %s\nat %s!",callsign, targettype, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + return self +end + +--- [Internal] +-- @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:I({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] +-- @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, Target, Text) + self:I({From, Event, To}) + return self +end + + +--- [Internal] +-- @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 + +--[[ test script +local PlayerSet = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterCategories("helicopter"):FilterStart() +local Attackers = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterStart() +local myrecce = PLAYERRECCE:New("1st Forward FACA",coalition.side.BLUE,PlayerSet) +myrecce:SetAttackSet(Attackers) +--]] \ No newline at end of file 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 From 0ee2baadce9de11a96a807075a81544f748946e1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 30 Sep 2022 19:03:26 +0200 Subject: [PATCH 07/19] #AWACS * Make Markers and Drawings strictly coalition specific --- Moose Development/Moose/Ops/Awacs.lua | 64 +++++++++++++-------------- 1 file changed, 32 insertions(+), 32 deletions(-) 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 From ca92d7d5693c996968557e574d50141ccdeecd57 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 30 Sep 2022 19:07:12 +0200 Subject: [PATCH 08/19] #PLAYERRECCE * Some nicefications --- Moose Development/Moose/Ops/PlayerRecce.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 9d4e771f0..c808a9f83 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -1,4 +1,4 @@ ---- **Ops** - Defines an extensive API to manage 3D points in the DCS World 3D simulation space. +--- **Ops** - Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others. -- -- ## Features: -- @@ -62,8 +62,9 @@ -- -- # PLAYERRECCE -- --- * Simplifies defining, executing and controlling of Player tasks --- * TBD +-- * 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. -- From f2ed9202142b314477e63cdc6bb95e36c0c5c602 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 1 Oct 2022 11:57:22 +0200 Subject: [PATCH 09/19] #SRSQUEUE, ATIS * Added option to only transmit via SRS if there are active players --- Moose Development/Moose/Ops/ATIS.lua | 17 ++++++++++++++++- Moose Development/Moose/Sound/SRS.lua | 27 ++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) 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/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.") From a42ff854063af7cf9fbe8833d4345f66e6d136a2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 1 Oct 2022 16:03:05 +0200 Subject: [PATCH 10/19] #PLAYERRECCE * Integrated SRS * Integrated PLAYERTASKCONTROLLER (optional), can upload target data from Recce --- Moose Development/Moose/Ops/PlayerRecce.lua | 459 ++++++++++++++++---- 1 file changed, 373 insertions(+), 86 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index c808a9f83..64ad35b8f 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -5,6 +5,7 @@ -- * 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 -- -- === -- @@ -52,6 +53,14 @@ -- @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 -- @extends Core.Fsm#FSM --- @@ -74,7 +83,7 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.0.1", + version = "0.0.5", ViewZone = {}, ViewZoneVisual = {}, PlayerSet = nil, @@ -88,6 +97,12 @@ PLAYERRECCE = { lasingtime = 60, AutoLase = {}, AttackSet = nil, + TransmitOnlyWithPlayers = true, + UseController = false, + Controller = nil, + ShortCallsign = true, + Keepnumber = true, + CallsignTranslations = nil, } --- @@ -130,8 +145,11 @@ PLAYERRECCE.CanLase = { ["SA342L"] = true, } ---- +--- 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) @@ -177,9 +195,15 @@ function PLAYERRECCE:New(Name, Coalition, PlayerSet) 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 @@ -188,7 +212,7 @@ 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:I(self.lid.."Event for player: "..EventData.IniPlayerName) + self:T(self.lid.."Event for player: "..EventData.IniPlayerName) if self.ClientMenus[EventData.IniPlayerName] then self.ClientMenus[EventData.IniPlayerName]:Remove() end @@ -198,7 +222,7 @@ function PLAYERRECCE:_EventHandler(EventData) end elseif EventData.id == EVENTS.PlayerEnterAircraft and EventData.IniCoalition == self.Coalition then if EventData.IniPlayerName and EventData.IniGroup and self.UseSRS then - self:I(self.lid.."Event for player: "..EventData.IniPlayerName) + self:T(self.lid.."Event for player: "..EventData.IniPlayerName) self.UnitLaserCodes[EventData.IniPlayerName] = 1688 self.ClientMenus[EventData.IniPlayerName] = nil self.LaserSpots[EventData.IniPlayerName] = nil @@ -209,6 +233,37 @@ function PLAYERRECCE:_EventHandler(EventData) 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 @@ -219,6 +274,16 @@ function PLAYERRECCE:SetLaserCodes( LaserCodes ) 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 @@ -236,7 +301,7 @@ end -- @return #number maxview in meters. -- @return #boolean cameraison If true, camera is on, else off. function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) - self:I(self.lid.."GetGazelleVivianneSight") + self:T(self.lid.."GetGazelleVivianneSight") local unit = Gazelle -- Wrapper.Unit#UNIT if unit and unit:IsAlive() then local dcsunit = Unit.getByName(Gazelle:GetName()) @@ -273,7 +338,7 @@ end -- @param #boolean vivoff Camera on or off -- @return #number maxview Max view distance in meters function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff) - self:I(self.lid.."_GetActualMaxLOSight") + self:T(self.lid.."_GetActualMaxLOSight") if vivoff then return 0 end local maxview = 0 if unit and unit:IsAlive() then @@ -295,6 +360,24 @@ function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff) 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 @@ -306,7 +389,7 @@ end -- @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:I(self.lid.."_GetViewZone") + self:T(self.lid.."_GetViewZone") local viewzone = nil if not camon then return nil end if unit and unit:IsAlive() then @@ -340,7 +423,7 @@ end --@return Core.Set#SET_UNIT Set of targets, can be empty! --@return #number count Count of targets function PLAYERRECCE:_GetTargetSet(unit,camera) - self:I(self.lid.."_GetTargetSet") + self:T(self.lid.."_GetTargetSet") local finaltargets = SET_UNIT:New() local finalcount = 0 local heading,nod,maxview,angle = 0,30,5000,10 @@ -365,7 +448,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera) -- determine what we can see local startpos = unit:GetCoordinate() local targetset = SET_UNIT:New():FilterCategories("ground"):FilterActive(true):FilterZones({zone}):FilterCoalitions(redcoalition):FilterOnce() - self:I("Prefilter Target Count = "..targetset:CountAlive()) + self:T("Prefilter Target Count = "..targetset:CountAlive()) -- TODO - Threat level filter? -- TODO - Min distance from unit? targetset:ForEach( @@ -373,13 +456,13 @@ function PLAYERRECCE:_GetTargetSet(unit,camera) local _unit = _unit -- Wrapper.Unit#UNIT local _unitpos = _unit:GetCoordinate() if startpos:IsLOS(_unitpos) then - self:I("Adding to final targets: ".._unit:GetName()) + self:T("Adding to final targets: ".._unit:GetName()) finaltargets:Add(_unit:GetName(),_unit) end end ) finalcount = finaltargets:CountAlive() - self:I(string.format("%s Unit: %s | Targets in view %s",self.lid,name,finalcount)) + self:T(string.format("%s Unit: %s | Targets in view %s",self.lid,name,finalcount)) end return finaltargets, finalcount, zone end @@ -389,7 +472,7 @@ end --@param Core.Set#SET_UNIT targetset Set of targets, can be empty! --@return Wrapper.Unit#UNIT Target function PLAYERRECCE:_GetHVTTarget(targetset) - self:I(self.lid.."_GetHVTTarget") + self:T(self.lid.."_GetHVTTarget") -- get one target -- local target = targetset:GetRandom() -- Wrapper.Unit#UNIT @@ -426,7 +509,7 @@ end --@param Core.Set#SET_UNIT targetset Set of targets, can be empty! --@return #PLAYERRECCE self function PLAYERRECCE:_LaseTarget(client,targetset) - self:I(self.lid.."_LaseTarget") + self:T(self.lid.."_LaseTarget") -- get one target local target = self:_GetHVTTarget(targetset) -- Wrapper.Unit#UNIT local playername = client:GetPlayerName() @@ -475,7 +558,7 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_SetClientLaserCode(client,group,playername,code) - self:I(self.lid.."_SetClientLaserCode") + self:T(self.lid.."_SetClientLaserCode") self.UnitLaserCodes[playername] = code or 1688 if self.ClientMenus[playername] then self.ClientMenus[playername]:Remove() @@ -491,7 +574,7 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_SwitchOnStation(client,group,playername) - self:I(self.lid.."_SwitchOnStation") + self:T(self.lid.."_SwitchOnStation") if not self.OnStation[playername] then self.OnStation[playername] = true self:__RecceOnStation(-1,client,playername) @@ -513,7 +596,7 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_SwitchLasing(client,group,playername) - self:I(self.lid.."_SwitchLasing") + 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) @@ -546,7 +629,7 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_SmokeTargets(client,group,playername) - self:I(self.lid.."_SmokeTargets") + 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) @@ -590,7 +673,7 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_FlareTargets(client,group,playername) - self:I(self.lid.."_SmokeTargets") + 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) @@ -627,6 +710,26 @@ function PLAYERRECCE:_FlareTargets(client,group,playername) 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 @@ -634,7 +737,7 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_ReportLaserTargets(client,group,playername) -self:I(self.lid.."_ReportLaserTargets") +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 @@ -675,7 +778,7 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_ReportVisualTargets(client,group,playername) - self:I(self.lid.."_ReportVisualTargets") + self:T(self.lid.."_ReportVisualTargets") local targetset, number = self:_GetTargetSet(client,false) if number > 0 then local Settings = ( client and _DATABASE:GetPlayerSettings( playername ) ) or _SETTINGS @@ -711,7 +814,7 @@ end -- @param #PLAYERRECCE self -- @param #PLAYERRECCE self function PLAYERRECCE:_BuildMenus() - self:I(self.lid.."_BuildMenus") + self:T(self.lid.."_BuildMenus") local clients = self.PlayerSet -- Core.Set#SET_CLIENT local clientset = clients:GetSetObjects() for _,_client in pairs(clientset) do @@ -724,7 +827,7 @@ function PLAYERRECCE:_BuildMenus() local group = client:GetGroup() if not self.ClientMenus[playername] then local canlase = self.CanLase[client:GetTypeName()] - self.ClientMenus[playername] = MENU_GROUP:New(group,self.Name or "RECCE") + 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) @@ -741,6 +844,10 @@ function PLAYERRECCE:_BuildMenus() 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 = {} @@ -766,18 +873,20 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_CheckNewTargets(targetset,client,playername) - self:I(self.lid.."_CheckNewTargets") - targetset:ForEachUnit( + self:T(self.lid.."_CheckNewTargets") + 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(-1,unit,client,playername) + self:TargetDetected(unit,client,playername) end end end @@ -785,7 +894,92 @@ function PLAYERRECCE:_CheckNewTargets(targetset,client,playername) return self end ---- [Internal] +--- [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 @@ -801,6 +995,7 @@ function PLAYERRECCE:onafterStatus(From, Event, To) 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 @@ -811,7 +1006,17 @@ function PLAYERRECCE:onafterStatus(From, Event, To) self.ViewZone[playername]=tzone:DrawZone(self.Coalition,{0,0,1},nil,nil,nil,1) end end - self:I({targetcount=targetcount}) + 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 + -- Report new targets + self:_CheckNewTargets(targetset,client,playername) + -- visual targets local vistargetset, vistargetcount, viszone = self:_GetTargetSet(client,false) if vistargetset then @@ -822,17 +1027,8 @@ function PLAYERRECCE:onafterStatus(From, Event, To) self.ViewZoneVisual[playername]=viszone:DrawZone(self.Coalition,{1,0,0},nil,nil,nil,3) end end - self:I({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 - -- Report new targets - targetset:AddSet(vistargetset) - self:_CheckNewTargets(targetset,client,playername) + self:T({visualtargetcount=vistargetcount}) + self:_CheckNewTargets(vistargetset,client,playername) end end ) @@ -841,7 +1037,7 @@ function PLAYERRECCE:onafterStatus(From, Event, To) return self end ---- [Internal] +--- [Internal] Recce on station -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -850,16 +1046,30 @@ end -- @param #string Playername -- @return #PLAYERRECCE self function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) - self:I({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(true,true) + 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) - 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) + 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,{grp},text1,10) + self.SRSQueue:NewTransmission(text2tts,nil,self.SRS,nil,1,{grp},text2,10) + 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] +--- [Internal] Recce off station -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -868,16 +1078,28 @@ end -- @param #string Playername -- @return #PLAYERRECCE self function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) - self:I({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(true,true) + 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) - local text = string.format("All stations, FACA %s leaving station\nat %s, going home!",callsign, coordtext) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + 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,{grp},text1,10) + self.SRSQueue:NewTransmission(texttts,nil,self.SRS,nil,2,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end return self end ---- [Internal] +--- [Internal] Target Detected -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -887,15 +1109,36 @@ end -- @param #string Playername -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetDetected(From, Event, To, Target, Client, Playername) - self:I({From, Event, To}) - if self.debug then - local text = string.format("New target %s detected by %s!",Target:GetTypeName(),Playername) - MESSAGE:New(text,10,self.Name or "FACA"):ToCoalition(self.Coalition) + self:T({From, Event, To}) + local dunits = "meters" + local targetdirection = self:_GetClockDirection(Client,Target) + local targetdistance = Client:GetCoordinate():Get2DDistance(Target:GetCoordinate()) or 100 + local Settings = Client and _DATABASE:GetPlayerSettings(Playername) or _SETTINGS -- Core.Settings#SETTINGS + local Threatlvl = Target: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,targetdirection, targetdistance, dunits) + local ttstext = string.format("Target! %s! %s oh clock, %d %s!", ThreatTxt, targetdirection, 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 return self end ---- [Internal] +--- [Internal] Targets Smoked -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -905,16 +1148,26 @@ end -- @param Core.Set#SET_UNIT TargetSet -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet) - self:I({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(true,true) + 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) - local text = string.format("All stations, FACA %s smoked targets\nat %s!",callsign, coordtext) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + if self.debug then + local text = string.format("All stations, FACA %s smoked targets\nat %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + 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] +--- [Internal] Targets Flared -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -924,16 +1177,26 @@ end -- @param Core.Set#SET_UNIT TargetSet -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet) - self:I({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(true,true) + 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) - local text = string.format("All stations, FACA %s flared\ntargets at %s!",callsign, coordtext) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + if self.debug then + local text = string.format("All stations, FACA %s flared\ntargets at %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + 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] +--- [Internal] Target lasing -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -944,18 +1207,28 @@ end -- @param #number Lasingtime -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime) - self:I({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(true,true) + 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) local targettype = Target:GetTypeName() - local text = string.format("All stations, FACA %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) + if self.debug then + local text = string.format("All stations, FACA %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] +--- [Internal] Laser lost LOS -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -964,18 +1237,28 @@ end -- @param Wrapper.Unit#UNIT Target -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target) - self:I({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(true,true) + 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) local targettype = Target:GetTypeName() - local text = string.format("All stations, FACA %s lost sight of %s\nat %s!",callsign, targettype, coordtext) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + if self.debug then + local text = string.format("All stations, FACA %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] +--- [Internal] Target report -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -986,7 +1269,7 @@ end -- @param #string Text -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetReport(From, Event, To, Client, TargetSet, Target, Text) - self:I({From, Event, To}) + self:T({From, Event, To}) MESSAGE:New(Text,45,self.Name or "FACA"):ToClient(Client) if self.AttackSet then -- send message to AttackSet @@ -997,11 +1280,11 @@ function PLAYERRECCE:onafterTargetReport(From, Event, To, Client, TargetSet, Tar end end end - self:__TargetReportSent(-2,Client, TargetSet, Target, Text) + --self:__TargetReportSent(-2,Client, TargetSet, Target, Text) return self end ---- [Internal] +--- [Internal] Target data upload -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -1011,13 +1294,20 @@ end -- @param Wrapper.Unit#UNIT Target -- @param #string Text -- @return #PLAYERRECCE self -function PLAYERRECCE:onafterTargetReportSent(From, Event, To, Client, TargetSet, Target, Text) - self:I({From, Event, To}) +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] +--- [Internal] Stop -- @param #PLAYERRECCE self -- @param #string From -- @param #string Event @@ -1034,9 +1324,6 @@ function PLAYERRECCE:onafterStop(From, Event, To) return self end ---[[ test script -local PlayerSet = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterCategories("helicopter"):FilterStart() -local Attackers = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterStart() -local myrecce = PLAYERRECCE:New("1st Forward FACA",coalition.side.BLUE,PlayerSet) -myrecce:SetAttackSet(Attackers) ---]] \ No newline at end of file +------------------------------------------------------------------------------------------ +-- TODO: END PLAYERRECCE +------------------------------------------------------------------------------------------ From 405e66ea72f65c44425194b8f9205cf85aebdd72 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 1 Oct 2022 16:36:29 +0200 Subject: [PATCH 11/19] #PLAYERTASK * SRS transmit option only when players are on --- Moose Development/Moose/Ops/PlayerTask.lua | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 18ed99d5e..63edec3aa 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -761,6 +761,7 @@ do -- @field #table PlayerJoinMenu -- @field #table PlayerInfoMenu -- @field #boolean noflaresmokemenu +-- @field #boolean TransmitOnlyWithPlayers -- @extends Core.Fsm#FSM --- @@ -1063,6 +1064,7 @@ PLAYERTASKCONTROLLER = { PlayerJoinMenu = {}, PlayerInfoMenu = {}, noflaresmokemenu = false, + TransmitOnlyWithPlayers = true, } --- @@ -1221,7 +1223,7 @@ PLAYERTASKCONTROLLER.Messages = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.37" +PLAYERTASKCONTROLLER.version="0.1.38" --- Constructor -- @param #PLAYERTASKCONTROLLER self @@ -1407,6 +1409,18 @@ function PLAYERTASKCONTROLLER:SetDisableSmokeFlareTask() 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 @@ -3034,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 From 30aba1258de75ecc3f2b5f32c44a376a5485685c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Oct 2022 13:14:57 +0200 Subject: [PATCH 12/19] #POINT * Added COORDINATE:ToStringFromRPShort --- Moose Development/Moose/Core/Point.lua | 37 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) 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 From 165a5d7364dd253fd5bedb96a33ae5d1edb516c5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Oct 2022 13:16:00 +0200 Subject: [PATCH 13/19] #PLAYERRECCE * Message improvements * Laser Distance 8km * Option to set RP Reference Point --- Moose Development/Moose/Ops/PlayerRecce.lua | 128 ++++++++++++++++---- 1 file changed, 104 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 64ad35b8f..c2fabce1a 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -28,7 +28,11 @@ ------------------------------------------------------------------------------------------------------------------- -- PLAYERRECCE -- TODO: PLAYERRECCE --- TODO: A lot... +-- 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 ------------------------------------------------------------------------------------------------------------------- --- PLAYERRECCE class. @@ -61,6 +65,9 @@ -- @field #boolean ShortCallsign -- @field #boolean Keepnumber -- @field #table CallsignTranslations +-- @field Core.Point#COORDINATE ReferencePoint +-- @field #string RPName +-- @field Wrapper.Marker#MARKER RPMarker -- @extends Core.Fsm#FSM --- @@ -83,11 +90,11 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.0.5", + version = "0.0.6", ViewZone = {}, ViewZoneVisual = {}, PlayerSet = nil, - debug = true, + debug = false, LaserSpots = {}, UnitLaserCodes = {}, LaserCodes = {}, @@ -103,6 +110,7 @@ PLAYERRECCE = { ShortCallsign = true, Keepnumber = true, CallsignTranslations = nil, + ReferencePoint = nil, } --- @@ -119,10 +127,10 @@ PLAYERRECCE.LaserRelativePos = { -- @type MaxViewDistance -- @field #string typename Unit type name PLAYERRECCE.MaxViewDistance = { - ["SA342M"] = 5000, - ["SA342Mistral"] = 5000, - ["SA342Minigun"] = 5000, - ["SA342L"] = 5000, + ["SA342M"] = 8000, + ["SA342Mistral"] = 8000, + ["SA342Minigun"] = 8000, + ["SA342L"] = 8000, } --- @@ -141,7 +149,7 @@ PLAYERRECCE.Cameraheight = { PLAYERRECCE.CanLase = { ["SA342M"] = true, ["SA342Mistral"] = true, - ["SA342Minigun"] = false, + ["SA342Minigun"] = false, -- no optics ["SA342L"] = true, } @@ -274,6 +282,24 @@ function PLAYERRECCE:SetLaserCodes( 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 @@ -343,7 +369,7 @@ function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff) local maxview = 0 if unit and unit:IsAlive() then local typename = unit:GetTypeName() - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 local CamHeight = self.Cameraheight[typename] or 0 if vnod > 0 then -- Looking down @@ -426,7 +452,7 @@ 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,5000,10 + local heading,nod,maxview,angle = 0,30,8000,10 local camon = true local typename = unit:GetTypeName() local name = unit:GetName() @@ -633,7 +659,9 @@ function PLAYERRECCE:_SmokeTargets(client,group,playername) local cameraset = self:_GetTargetSet(client,true) -- Core.Set#SET_UNIT local visualset = self:_GetTargetSet(client,false) -- Core.Set#SET_UNIT cameraset:AddSet(visualset) - self:__TargetsSmoked(-1,client,playername,cameraset) + if cameraset:CountAlive() > 0 then + self:__TargetsSmoked(-1,client,playername,cameraset) + end local highsmoke = SMOKECOLOR.Orange local medsmoke = SMOKECOLOR.White local lowsmoke = SMOKECOLOR.Green @@ -677,7 +705,9 @@ function PLAYERRECCE:_FlareTargets(client,group,playername) local cameraset = self:_GetTargetSet(client,true) -- Core.Set#SET_UNIT local visualset = self:_GetTargetSet(client,false) -- Core.Set#SET_UNIT cameraset:AddSet(visualset) - self:__TargetsFlared(-1,client,playername,cameraset) + if cameraset:CountAlive() > 0 then + self:__TargetsFlared(-1,client,playername,cameraset) + end local highsmoke = FLARECOLOR.Yellow local medsmoke = FLARECOLOR.White local lowsmoke = FLARECOLOR.Green @@ -754,8 +784,11 @@ self:T(self.lid.."_ReportLaserTargets") report:Add(string.rep("-",15)) report:Add("Target type: "..target:GetTypeName()) report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") - report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) - --report:Add("Location: "..target:GetCoordinate():ToStringA2G(client,Settings)) + 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() @@ -794,8 +827,11 @@ function PLAYERRECCE:_ReportVisualTargets(client,group,playername) report:Add(string.rep("-",15)) report:Add("Target count: "..number) report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") - report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) - --report:Add("Location: "..client:GetCoordinate():ToStringA2G(client,Settings)) + 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) @@ -1050,6 +1086,10 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) 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) @@ -1060,8 +1100,9 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) text2tts = self:_GetTextForSpeech(text2tts) if self.UseSRS then local grp = Client:GetGroup() - self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2,{grp},text1,10) - self.SRSQueue:NewTransmission(text2tts,nil,self.SRS,nil,1,{grp},text2,10) + 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) @@ -1082,6 +1123,10 @@ function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) 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) @@ -1091,8 +1136,9 @@ function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) local text1 = "Going home!" if self.UseSRS then local grp = Client:GetGroup() - self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2,{grp},text1,10) - self.SRSQueue:NewTransmission(texttts,nil,self.SRS,nil,2,{grp},text,10) + 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 @@ -1153,9 +1199,23 @@ function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, T local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.debug then - local text = string.format("All stations, FACA %s smoked targets\nat %s!",callsign, coordtext) + 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 @@ -1182,9 +1242,23 @@ function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, T local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.debug then - local text = string.format("All stations, FACA %s flared\ntargets at %s!",callsign, coordtext) + 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 @@ -1212,9 +1286,12 @@ function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Laserc 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, FACA %s lasing %s\nat %s!\nCode %d, Duration %d seconds!",callsign, targettype, coordtext, Lasercode, Lasingtime) + 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!" @@ -1242,9 +1319,12 @@ function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target) 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, FACA %s lost sight of %s\nat %s!",callsign, targettype, coordtext) + 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!" From 60cf7506f893eccaf89d49254d799ad10ee14e91 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 2 Oct 2022 16:10:11 +0200 Subject: [PATCH 14/19] Update Airboss.lua - Added `landingdist` as carrier parameter and into LSO result - Case III does not check groove time for unicorns --- Moose Development/Moose/Ops/Airboss.lua | 131 +++++++++++++----------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index bfa135ab5..805e0cdb4 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1332,6 +1332,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. @@ -1978,7 +1979,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,7 +1992,7 @@ 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. @@ -2006,8 +2007,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() @@ -4286,6 +4287,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 +4440,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 +4472,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,6 +4504,12 @@ 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. @@ -4534,6 +4550,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 +4596,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 +4642,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 +4660,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. @@ -5396,7 +5440,6 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 300 ) -- ? elseif harrier then alt=UTILS.FeetToMeters(312)-- 300-325 ft - end aoa = aoaac.OnSpeed @@ -11134,28 +11177,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 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 +11209,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 +11220,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 @@ -11796,7 +11806,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 @@ -17961,6 +17971,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 From c0f82eabb2a4b7f97501b988c3c0100d56ffdd27 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 2 Oct 2022 16:54:48 +0200 Subject: [PATCH 15/19] Server Name **GLOBALS** - Read out server name from `Config/serverSettings.lua` **SOCKET** - Added `server_name` to transmitted UDP data --- Moose Development/Moose/Globals.lua | 12 ++++++++++++ Moose Development/Moose/Utilities/Socket.lua | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) 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/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. From 42baf6c8d224ccc0750509fc8318128d9efb03da Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 2 Oct 2022 19:13:51 +0200 Subject: [PATCH 16/19] AIRBOSS v1.3.0 - Added Invincible parameters from master branch - Increased version number --- Moose Development/Moose/Ops/Airboss.lua | 106 +++++++++++++++++------- 1 file changed, 77 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 805e0cdb4..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", @@ -1732,7 +1736,7 @@ AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version = "1.2.1" +AIRBOSS.version = "1.3.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1997,6 +2001,9 @@ function AIRBOSS:New( carriername, alias ) 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() @@ -2099,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 ) @@ -2832,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 @@ -2869,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 @@ -3024,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 @@ -4514,8 +4520,54 @@ function AIRBOSS:_InitHermes() 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 @@ -5346,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 @@ -6384,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 ) @@ -8223,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 ) @@ -8258,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() @@ -9297,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 @@ -9384,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 @@ -10080,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) @@ -10130,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. @@ -10722,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 @@ -10827,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 @@ -10879,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 ) @@ -11169,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() @@ -11184,7 +11232,7 @@ function AIRBOSS:_GetOptLandingCoordinate() 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 @@ -11280,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 @@ -12143,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 @@ -14026,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 @@ -14034,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 @@ -17482,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() From bf2ce3c4afc3d7d6d7bd6848d2cc870a5a8ae28d Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 2 Oct 2022 19:20:25 +0200 Subject: [PATCH 17/19] AIRBOSS v1.3.0 - Copy from `develop` branch. - Disabled AI handling as this is bugged (needs `FLIGHTGROUP` class) --- Moose Development/Moose/Ops/Airboss.lua | 381 ++++++++++++++++++------ 1 file changed, 292 insertions(+), 89 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 1bd4c98da..313b8b154 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -117,6 +117,7 @@ -- * [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) -- -- === -- @@ -1202,6 +1203,8 @@ AIRBOSS = { NmaxSection = nil, NmaxStack = nil, handleai = nil, + xtVoiceOvers = nil, + xtVoiceOversAI = nil, tanker = nil, Corientation = nil, Corientlast = nil, @@ -1333,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. @@ -1732,8 +1736,7 @@ AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version = "1.2.1" - +AIRBOSS.version = "1.3.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1903,6 +1906,12 @@ function AIRBOSS:New( carriername, alias ) -- Set AI handling On. self:SetHandleAION() + -- No extra voiceover/calls from player by default + self:SetExtraVoiceOvers(false) + + -- No extra voiceover/calls from AI by default + self:SetExtraVoiceOversAI(false) + -- Airboss is a nice guy. self:SetAirbossNiceGuy() @@ -1974,7 +1983,8 @@ 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() elseif self.carriertype == AIRBOSS.CarrierType.LINCOLN then @@ -1986,7 +1996,7 @@ 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. @@ -2004,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() @@ -3234,6 +3244,24 @@ function AIRBOSS:SetHandleAION() return self end +--- Will play the inbound calls, commencing, initial, etc. from the player when requesteing marshal +-- @param #AIRBOSS self +-- @param #AIRBOSS status Boolean to activate (true) / deactivate (false) the radio inbound calls (default is ON) +-- @return #AIRBOSS self +function AIRBOSS:SetExtraVoiceOvers(status) + self.xtVoiceOvers=status + return self +end + +--- Will simulate the inbound call, commencing, initial, etc from the AI when requested by Airboss +-- @param #AIRBOSS self +-- @param #AIRBOSS status Boolean to activate (true) / deactivate (false) the radio inbound calls (default is ON) +-- @return #AIRBOSS self +function AIRBOSS:SetExtraVoiceOversAI(status) + self.xtVoiceOversAI=status + return self +end + --- Do not handle AI aircraft. -- @param #AIRBOSS self -- @return #AIRBOSS self @@ -3340,6 +3368,20 @@ function AIRBOSS:SetDebugModeOFF() return self end + +--- Set FunkMan socket. LSO grades and trap sheets will be send to your Discord bot. +-- **Requires running FunkMan program**. +-- @param #AIRBOSS self +-- @param #number Port Port. Default `10042`. +-- @param #string Host Host. Default `"127.0.0.1"`. +-- @return #AIRBOSS self +function AIRBOSS:SetFunkManOn(Port, Host) + + self.funkmanSocket=SOCKET:New(Port, Host) + + return self +end + --- Get next time the carrier will start recovering aircraft. -- @param #AIRBOSS self -- @param #boolean InSeconds If true, abs. mission time seconds is returned. Default is a clock #string. @@ -4251,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. @@ -4401,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. @@ -4430,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. @@ -4459,6 +4510,12 @@ 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. @@ -4499,6 +4556,12 @@ function AIRBOSS:_InitInvincible() 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. @@ -4539,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. @@ -4579,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. @@ -4619,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. @@ -4631,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. @@ -5353,16 +5444,12 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) aoa = aoaac.OnSpeed - if harrier then - -- 0.8 to 1.0 NM - dist = UTILS.NMToMeters( 0.9 ) - else - dist = UTILS.NMToMeters( 1.2 ) - end - if goshawk then -- 0.9 to 1.1 NM per natops ch.4 page 48 dist = UTILS.NMToMeters( 0.9 ) + elseif harrier then + -- 0.8 to 1.0 NM + dist = UTILS.NMToMeters( 0.9 ) else dist = UTILS.NMToMeters( 1.1 ) end @@ -5404,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 @@ -5622,6 +5708,12 @@ function AIRBOSS:_ClearForLanding( flight ) -- Cleared for Case X recovery. self:_MarshalCallClearedForRecovery( flight.onboard, flight.case ) + -- Voice over of the commencing simulated call from AI + if self.xtVoiceOversAI then + local leader = flight.group:GetUnits()[1] + self:_CommencingCall(leader, flight.onboard) + end + else -- Cleared for Case X recovery. @@ -5721,12 +5813,12 @@ function AIRBOSS:_ScanCarrierZone() if knownflight then -- Check if flight is AI and if we want to handle it at all. - if knownflight.ai and knownflight.flag == -100 and self.handleai then + if knownflight.ai and knownflight.flag == -100 and self.handleai and false then --Disabled AI handling because of incorrect OPSGROUP reference! local putintomarshal = false -- Get flight group. - local flight = _DATABASE:GetFlightGroup( groupname ) + local flight = _DATABASE:GetOpsGroup( groupname ) if flight and flight:IsInbound() and flight.destbase:GetName() == self.carrier:GetName() then if flight.ishelo then @@ -5772,7 +5864,6 @@ function AIRBOSS:_ScanCarrierZone() if not self:_IsHuman( group ) then self:_CreateFlightGroup( group ) end - end end @@ -5986,7 +6077,12 @@ function AIRBOSS:_MarshalAI( flight, nstack, respawn ) end -- Check if flight is already in Marshal queue. - if not self:_InQueue( self.Qmarshal, flight.group ) then + if not self:_InQueue(self.Qmarshal,flight.group) then + -- Simulate inbound call + if self.xtVoiceOversAI then + local leader = flight.group:GetUnits()[1] + self:_MarshallInboundCall(leader, flight.onboard) + end -- Add group to marshal stack queue. self:_AddMarshalGroup( flight, nstack ) end @@ -6068,7 +6164,7 @@ function AIRBOSS:_MarshalAI( flight, nstack, respawn ) local radial = self:GetRadial( case, false, true ) -- Point in the middle of the race track and a 5 NM more port perpendicular. - p0 = p2:Translate( UTILS.NMToMeters( 5 ), radial + 90 ):Translate( UTILS.NMToMeters( 5 ), radial, true ) + p0 = p2:Translate( UTILS.NMToMeters( 5 ), radial + 90, true ):Translate( UTILS.NMToMeters( 5 ), radial, true ) -- Entering Case II/III marshal pattern waypoint. wp[#wp + 1] = p0:WaypointAirTurningPoint( nil, speedTransit, { TaskArrivedHolding }, "Entering Case II/III Marshal Pattern" ) @@ -10832,7 +10928,6 @@ function AIRBOSS:_GetZoneCommence( case, stack ) local Three = self:GetCoordinate():Translate( D, hdg + 275 ) 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 ) @@ -11130,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.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)) - -- Atlitude 120 ft. - self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) end else @@ -11159,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. @@ -11171,61 +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.INVINCIBLE then - - -- Using spot 3 as the default - local hdg = self:GetHeading() - - self.landingspotcoord:Translate( 69, hdg, true, true ):SetAltitude( self.carrierparam.deckheight ) - -- This location looks good. - 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 @@ -11273,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 15 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 @@ -11290,7 +11345,7 @@ end --- Get wind speed on carrier deck parallel and perpendicular to runway. -- @param #AIRBOSS self --- @param #number alt Altitude in meters. Default 15 m. (change made from 50m from Discord discussion from Sickdog) +-- @param #number alt Altitude in meters. Default 18 m. -- @return #number Wind component parallel to runway im m/s. -- @return #number Wind component perpendicular to runway in m/s. -- @return #number Total wind strength in m/s. @@ -11313,7 +11368,7 @@ function AIRBOSS:GetWindOnDeck( alt ) zc = UTILS.Rotate2D( zc, -self.carrierparam.rwyangle ) -- Wind (from) vector - local vw = cv:GetWindWithTurbulenceVec3( alt or 15 ) + local vw = cv:GetWindWithTurbulenceVec3( alt or 18 ) --(change made from 50m to 15m from Discord discussion from Sickdog, next change to 18m due to SC higher deck discord) -- Total wind velocity vector. -- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total. @@ -11799,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 @@ -12798,19 +12853,23 @@ function AIRBOSS:_Debrief( playerData ) end mygrade.case = playerData.case local windondeck = self:GetWindOnDeck() - mygrade.wind = tostring( UTILS.Round( UTILS.MpsToKnots( windondeck ), 1 ) ) + mygrade.wind = UTILS.Round( UTILS.MpsToKnots( windondeck ), 1 ) mygrade.modex = playerData.onboard mygrade.airframe = playerData.actype mygrade.carriertype = self.carriertype mygrade.carriername = self.alias + mygrade.carrierrwy = self.carrierparam.rwyangle mygrade.theatre = self.theatre - mygrade.mitime = UTILS.SecondsToClock( timer.getAbsTime() ) + mygrade.mitime = UTILS.SecondsToClock( timer.getAbsTime(), true ) mygrade.midate = UTILS.GetDCSMissionDate() mygrade.osdate = "n/a" if os then mygrade.osdate = os.date() -- os.date("%d.%m.%Y") end + -- Add last grade to playerdata for FunkMan. + playerData.grade=mygrade + -- Save trap sheet. if playerData.trapon and self.trapsheet then self:_SaveTrapSheet( playerData, mygrade ) @@ -15121,6 +15180,86 @@ function AIRBOSS:_Number2Radio( radio, number, delay, interval, pilotcall ) return wait end +--- Aircraft request marshal (Inbound call both for players and AI). +-- @param #AIRBOSS self +-- @return Wrapper.Unit#UNIT Unit of player or nil. +-- @param #string modex Tail number. +function AIRBOSS:_MarshallInboundCall(unit, modex) + + -- Calculate + local vectorCarrier = self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate()) + local bearing = UTILS.Round(unit:GetCoordinate():GetAngleDegrees( vectorCarrier ), 0) + local distance = UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0) + local angels = UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0) + local state = UTILS.Round(self:_GetFuelState(unit)/1000,1) + + -- Pilot: "Marshall, [modex], marking mom's [bearing] for [distance], angels [XX], state [X.X]" + local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f", modex, bearing, distance, angels, state) + -- Debug message. + self:T(self.lid..text) + + -- Fuel state. + local FS=UTILS.Split(string.format("%.1f", state), ".") + + -- Create new call to display complete subtitle. + local inboundcall=self:_NewRadioCall(self.MarshalCall.CLICK, unit.UnitName:upper() , text, self.Tmessage, nil, unit.UnitName:upper()) + + -- CLICK! + self:RadioTransmission(self.MarshalRadio, inboundcall) + -- Marshal .. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.MARSHAL, nil, nil, nil, nil, true) + -- Modex.. + self:_Number2Radio(self.MarshalRadio, modex, nil, nil, true) + -- Marking Mom's, + self:RadioTransmission(self.MarshalRadio, self.PilotCall.MARKINGMOMS, nil, nil, nil, nil, true) + -- Bearing .. + self:_Number2Radio(self.MarshalRadio, tostring(bearing), nil, nil, true) + -- For .. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.FOR, nil, nil, nil, nil, true) + -- Distance .. + self:_Number2Radio(self.MarshalRadio, tostring(distance), nil, nil, true) + -- Angels .. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.ANGELS, nil, nil, nil, nil, true) + -- Angels Number .. + self:_Number2Radio(self.MarshalRadio, tostring(angels), nil, nil, true) + -- State .. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.STATE, nil, nil, nil, nil, true) + -- X.. + self:_Number2Radio(self.MarshalRadio, FS[1], nil, nil, true) + -- Point.. + self:RadioTransmission(self.MarshalRadio, self.PilotCall.POINT, nil, nil, nil, nil, true) + -- Y. + self:_Number2Radio(self.MarshalRadio, FS[2], nil, nil, true) + -- CLICK! + self:RadioTransmission(self.MarshalRadio, self.MarshalRadio.CLICK, nil, nil, nil, nil, true) + +end + +--- Aircraft commencing call (both for players and AI). +-- @param #AIRBOSS self +-- @return Wrapper.Unit#UNIT Unit of player or nil. +-- @param #string modex Tail number. +function AIRBOSS:_CommencingCall(unit, modex) + + -- Pilot: "[modex], commencing" + local text=string.format("%s, commencing", modex) + -- Debug message. + self:T(self.lid..text) + + -- Create new call to display complete subtitle. + local commencingCall=self:_NewRadioCall(self.MarshalCall.CLICK, unit.UnitName:upper() , text, self.Tmessage, nil, unit.UnitName:upper()) + + -- Click + self:RadioTransmission(self.MarshalRadio, commencingCall) + -- Modex.. + self:_Number2Radio(self.MarshalRadio, modex, nil, nil, true) + -- Commencing + self:RadioTransmission(self.MarshalRadio, self.PilotCall.COMMENCING, nil, nil, nil, nil, true) + -- CLICK! + self:RadioTransmission(self.MarshalRadio, self.MarshalRadio.CLICK, nil, nil, nil, nil, true) + +end + --- AI aircraft calls the ball. -- @param #AIRBOSS self -- @param #string modex Tail number. @@ -15170,6 +15309,7 @@ function AIRBOSS:_MarshalCallGasAtTanker( modex ) -- Debug message. self:I( self.lid .. text ) + -- Create new call to display complete subtitle. local call = self:_NewRadioCall( self.PilotCall.BINGOFUEL, modex, text, self.Tmessage, nil, modex ) @@ -15876,6 +16016,11 @@ function AIRBOSS:_RequestMarshal( _unitName ) if playerData then + -- Voice over of inbound call (regardless of airboss rejecting it or not) + if self.xtVoiceOvers then + self:_MarshallInboundCall(_unit, playerData.onboard) + end + -- Check if player is in CCA local inCCA = playerData.unit:IsInZone( self.zoneCCA ) @@ -16123,7 +16268,12 @@ function AIRBOSS:_RequestCommence( _unitName ) local playerData = self.players[_playername] -- #AIRBOSS.PlayerData if playerData then - + + -- Voice over of Commencing call (regardless of Airboss will rejected or not) + if self.xtVoiceOvers then + self:_CommencingCall(_unit, playerData.onboard) + end + -- Check if unit is in CCA. local text = "" local cleared = false @@ -17835,6 +17985,59 @@ function AIRBOSS:onafterLoad( From, Event, To, path, filename ) end +--- On after "LSOGrade" event. +-- @param #AIRBOSS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #AIRBOSS.PlayerData playerData Player Data. +-- @param #AIRBOSS.LSOgrade grade LSO grade. +function AIRBOSS:onafterLSOGrade(From, Event, To, playerData, grade) + + if self.funkmanSocket then + + -- Extract used info for FunkMan. We need to be careful with the amount of data send via UDP socket. + local trapsheet={} ; trapsheet.X={} ; trapsheet.Z={} ; trapsheet.AoA={} ; trapsheet.Alt={} + + -- Loop over trapsheet and extract used values. + for i = 1, #playerData.trapsheet do + local ts=playerData.trapsheet[i] --#AIRBOSS.GrooveData + table.insert(trapsheet.X, UTILS.Round(ts.X, 1)) + table.insert(trapsheet.Z, UTILS.Round(ts.Z, 1)) + table.insert(trapsheet.AoA, UTILS.Round(ts.AoA, 2)) + table.insert(trapsheet.Alt, UTILS.Round(ts.Alt, 1)) + end + + local result={} + result.command=SOCKET.DataType.LSOGRADE + result.name=playerData.name + result.trapsheet=trapsheet + result.airframe=grade.airframe + result.mitime=grade.mitime + result.midate=grade.midate + result.wind=grade.wind + 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 + result.wire=grade.wire + result.grade=grade.grade + result.points=grade.points + result.details=grade.details + + -- Debug info. + self:T(self.lid.."Result onafterLSOGrade") + self:T(result) + + -- Send result. + self.funkmanSocket:SendTable(result) + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ From 39bb95f12718c1ea55c0226ebd1e885fd3e2b5ac Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Oct 2022 19:34:04 +0200 Subject: [PATCH 18/19] #PLAYERRECCE -- DONE: Sort for multiple targets in one direction -- DONE: Targets with forget timeout, also report --- Moose Development/Moose/Ops/PlayerRecce.lua | 257 ++++++++++++++++---- 1 file changed, 212 insertions(+), 45 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index c2fabce1a..fcef0bcb9 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -33,6 +33,8 @@ -- 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. @@ -68,6 +70,8 @@ -- @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 --- @@ -90,11 +94,11 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.0.6", + version = "0.0.8", ViewZone = {}, ViewZoneVisual = {}, PlayerSet = nil, - debug = false, + debug = true, LaserSpots = {}, UnitLaserCodes = {}, LaserCodes = {}, @@ -111,8 +115,17 @@ PLAYERRECCE = { 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 @@ -153,6 +166,28 @@ PLAYERRECCE.CanLase = { ["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 @@ -176,6 +211,9 @@ function PLAYERRECCE:New(Name, Coalition, PlayerSet) self.minthreatlevel = 0 + self.TForget = 600 + self.TargetCache = FIFO:New() + -- FSM start state is STOPPED. self:SetStartState("Stopped") @@ -442,6 +480,60 @@ function PLAYERRECCE:_GetViewZone(unit, vheading, vnod, maxview, angle, camon, d 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 @@ -547,9 +639,6 @@ function PLAYERRECCE:_LaseTarget(client,targetset) self.UnitLaserCodes[playername] = 1688 end laser.LaserCode = self.UnitLaserCodes[playername] or 1688 - --function laser:OnAfterLaseOff(From,Event,To) - --MESSAGE:New("Finished lasing",15,"Info"):ToClient(client) - --end self.LaserSpots[playername] = laser else laser = self.LaserSpots[playername] @@ -561,7 +650,6 @@ function PLAYERRECCE:_LaseTarget(client,targetset) local lasingtime = self.lasingtime or 60 local targettype = target:GetTypeName() laser:LaseOn(target,lasercode,lasingtime) - --MESSAGE:New(string.format("Lasing Target %s with Code %d",targettype,lasercode),15,"Info"):ToClient(client) self:__TargetLasing(-1,client,target,lasercode,lasingtime) else -- still looking at target? @@ -571,7 +659,6 @@ function PLAYERRECCE:_LaseTarget(client,targetset) local targettype = oldtarget:GetTypeName() laser:LaseOff() self:__TargetLOSLost(-1,client,oldtarget) - --MESSAGE:New(string.format("Lost LOS on target %s!",targettype),15,"Info"):ToClient(client) end end return self @@ -662,10 +749,10 @@ function PLAYERRECCE:_SmokeTargets(client,group,playername) if cameraset:CountAlive() > 0 then self:__TargetsSmoked(-1,client,playername,cameraset) end - local highsmoke = SMOKECOLOR.Orange - local medsmoke = SMOKECOLOR.White - local lowsmoke = SMOKECOLOR.Green - local lasersmoke = SMOKECOLOR.Red + 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 @@ -708,10 +795,10 @@ function PLAYERRECCE:_FlareTargets(client,group,playername) if cameraset:CountAlive() > 0 then self:__TargetsFlared(-1,client,playername,cameraset) end - local highsmoke = FLARECOLOR.Yellow - local medsmoke = FLARECOLOR.White - local lowsmoke = FLARECOLOR.Green - local lasersmoke = FLARECOLOR.Red + 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 @@ -812,7 +899,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:_ReportVisualTargets(client,group,playername) self:T(self.lid.."_ReportVisualTargets") - local targetset, number = self:_GetTargetSet(client,false) + local targetset, number = self:_GetKnownTargets(client) if number > 0 then local Settings = ( client and _DATABASE:GetPlayerSettings( playername ) ) or _SETTINGS local ThreatLevel = targetset:CalculateThreatLevelA2G() @@ -910,6 +997,7 @@ end -- @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 @@ -922,11 +1010,45 @@ function PLAYERRECCE:_CheckNewTargets(targetset,client,playername) playername = playername, timestamp = timer.getTime() } - self:TargetDetected(unit,client,playername) + --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 @@ -1024,6 +1146,16 @@ end 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( @@ -1050,9 +1182,7 @@ function PLAYERRECCE:onafterStatus(From, Event, To) self:_LaseTarget(client,targetset) end end - -- Report new targets - self:_CheckNewTargets(targetset,client,playername) - + -- visual targets local vistargetset, vistargetcount, viszone = self:_GetTargetSet(client,false) if vistargetset then @@ -1064,7 +1194,8 @@ function PLAYERRECCE:onafterStatus(From, Event, To) end end self:T({visualtargetcount=vistargetcount}) - self:_CheckNewTargets(vistargetset,client,playername) + targetset:AddSet(vistargetset) + self:_CheckNewTargets(targetset,client,playername) end end ) @@ -1150,36 +1281,72 @@ end -- @param #string From -- @param #string Event -- @param #string To --- @param Wrapper.Unit#UNIT Target +-- @param #table Targetsbyclock -- @param Wrapper.Client#CLIENT Client -- @param #string Playername -- @return #PLAYERRECCE self -function PLAYERRECCE:onafterTargetDetected(From, Event, To, Target, Client, Playername) +function PLAYERRECCE:onafterTargetDetected(From, Event, To, Targetsbyclock, Client, Playername) self:T({From, Event, To}) + local dunits = "meters" - local targetdirection = self:_GetClockDirection(Client,Target) - local targetdistance = Client:GetCoordinate():Get2DDistance(Target:GetCoordinate()) or 100 local Settings = Client and _DATABASE:GetPlayerSettings(Playername) or _SETTINGS -- Core.Settings#SETTINGS - local Threatlvl = Target: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,targetdirection, targetdistance, dunits) - local ttstext = string.format("Target! %s! %s oh clock, %d %s!", ThreatTxt, targetdirection, 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) + 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 From 1e04aaa77dcd22fb36a0cf6754c911c101bc63f2 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 2 Oct 2022 19:45:48 +0200 Subject: [PATCH 19/19] Update Airboss.lua - reenabled ai handling --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 313b8b154..118b9e642 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -5813,7 +5813,7 @@ function AIRBOSS:_ScanCarrierZone() if knownflight then -- Check if flight is AI and if we want to handle it at all. - if knownflight.ai and knownflight.flag == -100 and self.handleai and false then --Disabled AI handling because of incorrect OPSGROUP reference! + if knownflight.ai and knownflight.flag == -100 and self.handleai then local putintomarshal = false