From 60c78da0f652a77775373e8e4d5c544a62ffdccc Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 21 Apr 2022 12:56:40 +0200 Subject: [PATCH 01/16] Utils/Point - Corrections --- Moose Development/Moose/Core/Point.lua | 2 +- Moose Development/Moose/Utilities/FiFo.lua | 40 +++++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 2e96efb67..2d5375dbb 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2788,7 +2788,7 @@ do -- COORDINATE local alt = UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)--*1000 - local track = Utils.BearingToCardinal(bearing) + local track = UTILS.BearingToCardinal(bearing) if rangeNM > 3 then BRAANATO = string.format("BRAA, %s, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) diff --git a/Moose Development/Moose/Utilities/FiFo.lua b/Moose Development/Moose/Utilities/FiFo.lua index 6d8654931..38b318a2d 100644 --- a/Moose Development/Moose/Utilities/FiFo.lua +++ b/Moose Development/Moose/Utilities/FiFo.lua @@ -38,7 +38,7 @@ do FIFO = { ClassName = "FIFO", lid = "", - version = "0.0.3", + version = "0.0.5", counter = 0, pointer = 0, stackbypointer = {}, @@ -141,7 +141,11 @@ function FIFO:ReadByPointer(Pointer) self:T(self.lid.."ReadByPointer " .. tostring(Pointer)) if self.counter == 0 or not Pointer or not self.stackbypointer[Pointer] then return nil end local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry - return object.data + if object then + return object.data + else + return nil + end end --- FIFO Read, not Pull, Object from Stack by UniqueID @@ -152,7 +156,11 @@ function FIFO:ReadByID(UniqueID) self:T(self.lid.."ReadByID " .. tostring(UniqueID)) if self.counter == 0 or not UniqueID or not self.stackbyid[UniqueID] then return nil end local object = self.stackbyid[UniqueID] -- #FIFO.IDEntry - return object.data + if object then + return object.data + else + return nil + end end --- FIFO Pull Object from Stack by UniqueID @@ -164,7 +172,11 @@ function FIFO:PullByID(UniqueID) if self.counter == 0 then return nil end local object = self.stackbyid[UniqueID] -- #FIFO.IDEntry --self.stackbyid[UniqueID] = nil - return self:PullByPointer(object.pointer) + if object then + return self:PullByPointer(object.pointer) + else + return nil + end end --- FIFO Housekeeping @@ -410,7 +422,7 @@ do LIFO = { ClassName = "LIFO", lid = "", - version = "0.0.2", + version = "0.0.5", counter = 0, pointer = 0, stackbypointer = {}, @@ -512,7 +524,11 @@ function LIFO:ReadByPointer(Pointer) self:T(self.lid.."ReadByPointer " .. tostring(Pointer)) if self.counter == 0 or not Pointer or not self.stackbypointer[Pointer] then return nil end local object = self.stackbypointer[Pointer] -- #LIFO.IDEntry - return object.data + if object then + return object.data + else + return nil + end end --- LIFO Read, not Pull, Object from Stack by UniqueID @@ -523,7 +539,11 @@ function LIFO:ReadByID(UniqueID) self:T(self.lid.."ReadByID " .. tostring(UniqueID)) if self.counter == 0 or not UniqueID or not self.stackbyid[UniqueID] then return nil end local object = self.stackbyid[UniqueID] -- #LIFO.IDEntry - return object.data + if object then + return object.data + else + return nil + end end --- LIFO Pull Object from Stack by UniqueID @@ -535,7 +555,11 @@ function LIFO:PullByID(UniqueID) if self.counter == 0 then return nil end local object = self.stackbyid[UniqueID] -- #LIFO.IDEntry --self.stackbyid[UniqueID] = nil - return self:PullByPointer(object.pointer) + if object then + return self:PullByPointer(object.pointer) + else + return nil + end end --- LIFO Housekeeping From 380731a244d5bdddd15b50714bebe56d2382faa2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 21 Apr 2022 18:56:09 +0200 Subject: [PATCH 02/16] AWACS 0.0.6 --- Moose Development/Moose/Ops/Awacs.lua | 795 ++++++++++++++++++-------- 1 file changed, 552 insertions(+), 243 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 995402c63..e29e9e6ee 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -50,10 +50,13 @@ do -- @field Utilities.FiFo#FIFO TaskedCAPAI -- @field Utilities.FiFo#FIFO TaskedCAPHuman -- @field Utilities.FiFo#FIFO OpenTasks --- @field Utilities.FiFo#FIFO AssignedTasks +-- @field Utilities.FiFo#FIFO ManagedTasks -- @field Utilities.FiFo#FIFO PictureAO -- @field Utilities.FiFo#FIFO PictureEWR -- @field Utilities.FiFo#FIFO Contacts +-- @field #table CatchAllMissions +-- @field #table CatchAllFGs +-- @field #number Countactcounter -- @field Utilities.FiFo#FIFO ContactsAO -- @field Utilities.FiFo#FIFO RadioQueue -- @field Utilities.FiFo#FIFO CAPAirwings @@ -75,6 +78,10 @@ do -- @field #boolean ShiftChangeEscortsFlag -- @field #boolean ShiftChangeAwacsRequested -- @field #boolean ShiftChangeEscortsRequested +-- @field #AWACS.MonitoringData MonitoringData +-- @field #boolean MonitoringOn +-- @field Core.Set#SET_GROUP clientset +-- @field Utilities.FiFo#FIFO FlightGroups -- @extends Core.Fsm#FSM --- @@ -82,7 +89,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "alpha 0.0.3", -- #string + version = "alpha 0.0.6", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -107,16 +114,18 @@ AWACS = { TaskedCAPAI = {}, TaskedCAPHuman = {}, OpenTasks = {}, -- Utilities.FiFo#FIFO - AssignedTasks = {}, -- Utilities.FiFo#FIFO + ManagedTasks = {}, -- Utilities.FiFo#FIFO PictureAO = {}, -- Utilities.FiFo#FIFO PictureEWR = {}, -- Utilities.FiFo#FIFO Contacts = {}, -- Utilities.FiFo#FIFO + Countactcounter = 0, ContactsAO = {}, -- Utilities.FiFo#FIFO RadioQueue = {}, -- Utilities.FiFo#FIFO AwacsTimeOnStation = 1, AwacsTimeStamp = 0, EscortsTimeOnStation = 0.5, EscortsTimeStamp = 0, + CAPTimeOnStation = 4, AwacsROE = "", AwacsROT = "", MenuStrict = true, @@ -128,6 +137,14 @@ AWACS = { ShiftChangeAwacsRequested = false, ShiftChangeEscortsRequested = false, CAPAirwings = {}, -- Utilities.FiFo#FIFO + MonitoringData = {}, + MonitoringOn = true, + FlightGroups = {}, + AwacsMission = nil, + AwacsInZone = false, -- not yet arrived or gone again + AwacsReady = false, + CatchAllMissions = {}, + CatchAllFGs = {}, } --- @@ -155,6 +172,47 @@ AWACS.AnchorNames = { [10] = "Ten", } +--- +-- @field Phonetic +AWACS.Phonetic = +{ + [1] = 'Alpha', + [2] = 'Bravo', + [3] = 'Charlie', + [4] = 'Delta', + [5] = 'Echo', + [6] = 'Foxtrot', + [7] = 'Golf', + [8] = 'Hotel', + [9] = 'India', + [10] = 'Juliett', + [11] = 'Kilo', + [12] = 'Lima', + [13] = 'Mike', + [14] = 'November', + [15] = 'Oscar', + [16] = 'Papa', + [17] = 'Quebec', + [18] = 'Romeo', + [19] = 'Sierra', + [20] = 'Tango', + [21] = 'Uniform', + [22] = 'Victor', + [23] = 'Whiskey', + [24] = 'Xray', + [25] = 'Yankee', + [26] = 'Zulu', +} + +--- +-- @field Shipsize +AWACS.Shipsize = +{ + [1] = "Singleton", + [2] = "Two-Ship", + [3] = "Heavy", +} + --- -- @field ROE AWACS.ROE = { @@ -182,6 +240,20 @@ AWACS.THREATLEVEL = { RED = 10, } +--- +-- @type AWACS.MonitoringData +-- @field #string AwacsStateMission +-- @field #string AwacsStateFG +-- @field #boolean AwacsShiftChange +-- @field #string EscortsStateMission +-- @field #string EscortsStateFG +-- @field #boolean EscortsShiftChange +-- @field #number AICAPMax +-- @field #number AICAPCurrent +-- @field #number Airwings +-- @field #number Players +-- @field #number PlayersCheckedin + --- -- @type AWACS.MenuStructure -- @field #boolean menuset @@ -202,17 +274,33 @@ AWACS.THREATLEVEL = { -- @field #boolean IsPlayer -- @field #boolean IsAI -- @field #string CallSign +-- @field #number CurrentAuftrag -- @field #number CurrentTask -- @field #boolean HasAssignedTask -- @field #number GID -- @field #number AnchorStackNo -- @field #number AnchorStackAngels +-- @field #number ContactCID + +--- Contact Data +-- @type AWACS.ManagedContact +-- @field #number CID +-- @field Ops.Intelligence#INTEL.Contact Contact +-- @field Ops.Intelligence#INTEL.Cluster Cluster +-- @field #string IFF -- ID'ed or not +-- @field Ops.Target#TARGET Target +-- @field #number LinkedTask --> TID +-- @field #number LinkedGroup --> GID +-- @field #string Status - AWACS.TaskStatus.... +-- @field #string TargetGroupNaming --- -- @type AWACS.TaskDescription AWACS.TaskDescription = { ANCHOR = "Anchor", REANCHOR = "Re-Anchor", + VID = "VID", + IFF = "IFF", INTERCEPT = "Intercept", SWEEP = "Sweep", RTB = "RTB", @@ -222,10 +310,13 @@ AWACS.TaskDescription = { -- @type AWACS.TaskStatus AWACS.TaskStatus = { IDLE = "Idle", + UNASSIGNED = "Unassigned", + REQUESTED = "Requested", ASSIGNED = "Assigned", EXECUTING = "Executing", SUCCESS = "Success", FAILED = "Failed", + DEAD = "Dead", } --- @@ -241,6 +332,7 @@ AWACS.TaskStatus = { -- @field #string ScreenText Long descrition -- @field Ops.Intelligence#INTEL.Contact Contact -- @field Ops.Intelligence#INTEL.Cluster Cluster +-- @field #number CurrentAuftrag --- -- @type AWACS.AnchorAssignedEntry @@ -369,8 +461,11 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.CallSignNo = 1 -- #number self.NoHelos = true self.MaxAIonCAP = 4 + self.AIRequested = 0 self.AIonCAP = 0 self.AICAPMissions = FIFO:New() -- Utilities.FiFo#FIFO + self.FlightGroups = FIFO:New() -- Utilities.FiFo#FIFO + self.Countactcounter = 0 local speed = 250 self.SpeedBase = speed @@ -389,6 +484,11 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.ShiftChangeTime = 0.25 -- 15mins self.ShiftChangeAwacsFlag = false self.ShiftChangeEscortsFlag = false + self.CAPTimeOnStation = 4 + + self.AwacsMission = nil + self.AwacsInZone = false -- not yet arrived or gone again + self.AwacsReady = false self.AwacsROE = AWACS.ROE.POLICE self.AwacsROT = AWACS.ROT.PASSIVE @@ -419,7 +519,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.ManagedGrps = {} -- #table of #AWACS.ManagedGroup entries self.ManagedGrpID = 0 - self.AICAPCAllName = CALLSIGN.F16.Viper + self.AICAPCAllName = CALLSIGN.Aircraft.Colt self.AICAPCAllNumber = 0 -- Anchor stacks init @@ -435,21 +535,34 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self:_CreateAnchorStack() -- Task lists - self.AssignedTasks = FIFO:New() -- Utilities.FiFo#FIFO - self.OpenTasks = FIFO:New() -- Utilities.FiFo#FIFO + self.ManagedTasks = FIFO:New() -- Utilities.FiFo#FIFO + --self.OpenTasks = FIFO:New() -- Utilities.FiFo#FIFO - -- Pilot lists - --[[ ToDo - Maybe only 2? Move to managedgroups - self.CAPIdleAI = FIFO:New() -- Utilities.FiFo#FIFO - self.CAPIdleHuman = FIFO:New() -- Utilities.FiFo#FIFO - self.TaskedCAPAI = FIFO:New() -- Utilities.FiFo#FIFO - self.TaskedCAPHuman = FIFO:New() -- Utilities.FiFo#FIFO - --]] + -- Monitoring, init + local MonitoringData = {} -- #AWACS.MonitoringData + MonitoringData.AICAPCurrent = 0 + MonitoringData.AICAPMax = self.MaxAIonCAP + MonitoringData.Airwings = 1 + MonitoringData.PlayersCheckedin = 0 + MonitoringData.Players = 0 + MonitoringData.AwacsShiftChange = false + MonitoringData.AwacsStateFG = "unknown" + MonitoringData.AwacsStateMission = "unknown" + MonitoringData.EscortsShiftChange = false + MonitoringData.EscortsStateFG= "unknown" + MonitoringData.EscortsStateMission = "unknown" + self.MonitoringOn = true -- #boolean + self.MonitoringData = MonitoringData + + self.CatchAllMissions = {} + self.CatchAllFGs = {} -- Picture, Contacts, Bogeys self.PictureAO = FIFO:New() -- Utilities.FiFo#FIFO self.PictureEWR = FIFO:New() -- Utilities.FiFo#FIFO self.Contacts = FIFO:New() -- Utilities.FiFo#FIFO + self.ManagedContacts = FIFO:New() + self.CID = 0 self.ContactsAO = FIFO:New() -- Utilities.FiFo#FIFO -- SET for Intel Detection @@ -463,7 +576,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("Stopped", "Start", "StartUp") -- Start FSM. + self:AddTransition("StartUp", "Started", "Running") self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "CheckedIn", "*") self:AddTransition("*", "CheckedOut", "*") @@ -583,11 +697,13 @@ end -- @param #boolean Shiftchange This is a shift change call -- @return #AWACS self function AWACS:_StartEscorts(Shiftchange) - self:T(self.lid.."_StartEscorts") + self:I(self.lid.."_StartEscorts") local AwacsFG = self.AwacsFG -- Ops.FlightGroup#FLIGHTGROUP local group = AwacsFG:GetGroup() local mission = AUFTRAG:NewESCORT(group,{x=-100, y=0, z=200},30,{"Air"}) + self.CatchAllMissions[#self.CatchAllMissions+1] = mission + mission:SetRequiredAssets(self.EscortNumber) local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds @@ -612,14 +728,12 @@ end function AWACS:_StartSettings(FlightGroup,Mission) self:T(self.lid.."_StartSettings") - -- called by AW OnafterFlightOnMission(...) - local Mission = Mission -- Ops.Auftrag#AUFTRAG local AwacsFG = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP -- Is this our Awacs mission? if self.AwacsMission:GetName() == Mission:GetName() then - + self:I("Setting up Awacs") AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) AwacsFG:SwitchRadio(self.Frequency,self.Modulation) AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) @@ -643,7 +757,8 @@ function AWACS:_StartSettings(FlightGroup,Mission) AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil) self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) - local text = string.format("%s starting for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") + --local text = string.format("%s starting for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") + local text = string.format("%s. All stations, SUNRISE SUNRISE SUNRISE, %s.",self.callsigntxt,self.callsigntxt) self:T(self.lid..text) AwacsFG:RadioTransmission(text,1,false) @@ -660,8 +775,12 @@ function AWACS:_StartSettings(FlightGroup,Mission) self.AwacsTimeStamp = timer.getTime() self.EscortsTimeStamp = timer.getTime() - elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName() == Mission:GetName() then + self.AwacsReady = true + -- set FSM to started + self:Started() + elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName() == Mission:GetName() then + self:I("Setting up Awacs Replacement") -- manage AWACS Replacement AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) AwacsFG:SwitchRadio(self.Frequency,self.Modulation) @@ -704,6 +823,8 @@ function AWACS:_StartSettings(FlightGroup,Mission) self.AwacsTimeStamp = timer.getTime() self.EscortsTimeStamp = timer.getTime() + self.AwacsReady = true + end return self end @@ -732,16 +853,24 @@ end --- [Internal] AWACS Get TTS compatible callsign -- @param #AWACS self -- @param Wrapper.Group#GROUP Group Group to use +-- @param #number GID GID to use -- @return #string Callsign -function AWACS:_GetCallSign(Group) - self:T(self.lid.."_GetCallSign") +function AWACS:_GetCallSign(Group,GID) + self:I(self.lid.."_GetCallSign - GID "..tostring(GID)) + + if GID and type(GID) == "number" and GID > 0 then + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + self:T("Saved Callsign for TTS = " .. tostring(managedgroup.CallSign)) + return managedgroup.CallSign + end + local callsign = "" - local shortcallsign = Group:GetCallsign() -- e.g.Uzi11, but we want Uzi 1 1 - local callnumber = string.match(shortcallsign, "(%d+)$" ) + local shortcallsign = Group:GetCallsign() or "unknown11"-- e.g.Uzi11, but we want Uzi 1 1 + local callnumber = string.match(shortcallsign, "(%d+)$" ) or "unknown11" local callnumbermajor = string.char(string.byte(callnumber,1)) local callnumberminor = string.char(string.byte(callnumber,2)) callsign = string.gsub(shortcallsign,callnumber,"").." "..callnumbermajor.." "..callnumberminor - self:T("Callsign for TTS = " .. callsign) + self:I("Generated Callsign for TTS = " .. callsign) return callsign end @@ -779,7 +908,8 @@ function AWACS:_CreatePicture(AO,Callsign,GID) self:T({cluster}) if cluster and cluster.coordinate then local clustercoord = cluster.coordinate -- Core.Point#COORDINATE - local refBRAA = clustercoord:ToStringBRA(groupcoord) + --local refBRAA = clustercoord:ToStringBRA(groupcoord) + local refBRAA = clustercoord:ToStringBRAANATO(groupcoord,true) text = text .. " "..refBRAA.."." textScreen = textScreen .." "..refBRAA..".\n" if counter < maxentries then @@ -851,10 +981,11 @@ function AWACS:_Picture(Group) self:T(self.lid.."_Picture") local text = "Picture WIP" local textScreen = text - + local GID, Outcome = self:_GetManagedGrpID(Group) + if not self.intel then -- no intel yet! - text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -866,8 +997,6 @@ function AWACS:_Picture(Group) self.RadioQueue:Push(RadioEntry) return self end - - local GID, Outcome = self:_GetManagedGrpID(Group) if Outcome then -- Pilot is checked in @@ -890,7 +1019,7 @@ function AWACS:_Picture(Group) if clustersAO == 0 and clustersEWR == 0 then -- clear - text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -905,8 +1034,8 @@ function AWACS:_Picture(Group) else if clustersAO > 0 then - text = string.format("%s. %s. Picture A O. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") - textScreen = string.format("%s. %s. Picture AO. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. %s. Picture A O. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + textScreen = string.format("%s. %s. Picture AO. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") if clustersAO == 1 then text = text .. "One group. " textScreen = textScreen .. "One group.\n" @@ -924,12 +1053,12 @@ function AWACS:_Picture(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreatePicture(true,self:_GetCallSign(Group) or "Unknown 1 1",GID) + self:_CreatePicture(true,self:_GetCallSign(Group,GID) or "Unknown 1 1",GID) end if clustersEWR > 0 then - text = string.format("%s. %s. Picture Early Warning. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") - textScreen = string.format("%s. %s. Picture EWR. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. %s. Picture Early Warning. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + textScreen = string.format("%s. %s. Picture EWR. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") if clustersEWR == 1 then text = text .. "One group. " textScreen = textScreen .. "One group.\n" @@ -947,13 +1076,13 @@ function AWACS:_Picture(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreatePicture(false,self:_GetCallSign(Group) or "Unknown 1 1",GID) + self:_CreatePicture(false,self:_GetCallSign(Group,GID) or "Unknown 1 1",GID) end end elseif self.AwacsFG then -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text @@ -976,10 +1105,11 @@ function AWACS:_BogeyDope(Group) self:T(self.lid.."_BogeyDope") local text = "BogeyDope WIP" local textScreen = "BogeyDope WIP" - + local GID, Outcome = self:_GetManagedGrpID(Group) + if not self.intel then -- no intel yet! - text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -993,8 +1123,6 @@ function AWACS:_BogeyDope(Group) self.RadioQueue:Push(RadioEntry) return self end - - local GID, Outcome = self:_GetManagedGrpID(Group) if Outcome then -- Pilot is checked in @@ -1023,7 +1151,7 @@ function AWACS:_BogeyDope(Group) if contactsAO == 0 then -- clear - text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1038,13 +1166,13 @@ function AWACS:_BogeyDope(Group) else if contactsAO > 0 then - text = string.format("%s. %s. Bogey Dope. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. %s. Bogey Dope. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") if contactsAO == 1 then text = text .. "One group. " textScreen = text .. "\n" else text = text .. contactsAO .. " groups. " - textScreen = text .. contactsAO .. " groups.\n" + textScreen = textScreen .. contactsAO .. " groups.\n" end local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1056,13 +1184,13 @@ function AWACS:_BogeyDope(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreateBogeyDope(self:_GetCallSign(Group) or "Unknown 1 1",GID) + self:_CreateBogeyDope(self:_GetCallSign(Group,GID) or "Unknown 1 1",GID) end end elseif self.AwacsFG then -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text @@ -1093,7 +1221,7 @@ function AWACS:_Declare(Group) --]] elseif self.AwacsFG then -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1128,16 +1256,16 @@ function AWACS:_Showtask(Group) if managedgroup.IsPlayer and self.TaskedCAPHuman:HasUniqueID(GID) then - if managedgroup.CurrentTask >0 and self.AssignedTasks:HasUniqueID(managedgroup.CurrentTask) then + if managedgroup.CurrentAuftrag >0 and self.ManagedTasks:HasUniqueID(managedgroup.CurrentAuftrag) then -- get task structure - local currenttask = self.AssignedTasks:ReadByID(managedgroup.CurrentTask) -- #AWACS.ManagedTask + local currenttask = self.ManagedTasks:ReadByID(managedgroup.CurrentAuftrag) -- #AWACS.ManagedTask if currenttask then local status = currenttask.Status local targettype = currenttask.Target:GetCategory() local targetstatus = currenttask.Target:GetState() local ToDo = currenttask.ToDo local description = currenttask.ScreenText - local callsign = self:_GetCallSign(Group) + local callsign = self:_GetCallSign(Group,GID) if self.debug then local taskreport = REPORT:New("AWACS Tasking Display") @@ -1158,7 +1286,7 @@ function AWACS:_Showtask(Group) elseif self.AwacsFG then -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1190,8 +1318,8 @@ function AWACS:_CheckIn(Group) managedgroup.GroupName = Group:GetName() managedgroup.IsPlayer = true managedgroup.IsAI = false - managedgroup.CallSign = self:_GetCallSign(Group) or "Unknown 1 1" - managedgroup.CurrentTask = 0 + managedgroup.CallSign = self:_GetCallSign(Group,GID) or "Unknown 1 1" + managedgroup.CurrentAuftrag = 0 managedgroup.HasAssignedTask = false managedgroup.GID = self.ManagedGrpID GID = managedgroup.GID @@ -1200,7 +1328,7 @@ function AWACS:_CheckIn(Group) self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) elseif self.AwacsFG then - text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1224,7 +1352,7 @@ end -- @param #number AuftragsNr Ops.Auftrag#AUFTRAG.auftragsnummer -- @return #AWACS self function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) - self:I(self.lid.."_CheckInAI "..Group:GetName()) + self:I(self.lid.."_CheckInAI "..Group:GetName() .. " to Auftrag Nr "..AuftragsNr) -- check if already known local GID, Outcome = self:_GetManagedGrpID(Group) local text = "" @@ -1236,8 +1364,15 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) managedgroup.FlightGroup = FlightGroup managedgroup.IsPlayer = false managedgroup.IsAI = true - managedgroup.CallSign = self:_GetCallSign(Group) or "Unknown 1 1" - managedgroup.CurrentTask = AuftragsNr + --managedgroup.CallSign = self:_GetCallSign(Group,GID) or "AI 1 1" + -- self.AICAPCAllName = CALLSIGN.F16.Viper (number) + -- self.AICAPCAllNumber + local callsignstring = UTILS.GetCallsignName(self.AICAPCAllName) + local callsignmajor = math.fmod(self.AICAPCAllNumber,9) + local callsign = string.format("%s %d 1",callsignstring,callsignmajor) + self:I("Assigned Callsign: ".. callsign) + managedgroup.CallSign = callsign + managedgroup.CurrentAuftrag = AuftragsNr managedgroup.HasAssignedTask = false managedgroup.GID = self.ManagedGrpID @@ -1246,15 +1381,27 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) FlightGroup:SetDefaultRadio(self.Frequency,self.Modulation,false) FlightGroup:SwitchRadio(self.Frequency,self.Modulation) + --FlightGroup:SetDefaultCallsign(self.AICAPCAllName,callsignmajor) FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,self.CAPVoice,self.Port,nil) + + text = string.format("%s. %s. Check in for CAP. Expected playtime %d hours.",managedgroup.CallSign, self.callsigntxt,self.CAPTimeOnStation) + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.ToScreen = false + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.FromAI = true + RadioEntry.GroupID = managedgroup.GID + + self.RadioQueue:Push(RadioEntry) self.ManagedGrps[self.ManagedGrpID]=managedgroup text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign) self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) else - text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1271,8 +1418,9 @@ end --- [Internal] AWACS Menu for Check Out -- @param #AWACS self -- @param Wrapper.Group#GROUP Group Group to use +-- @param #number GID GroupID -- @return #AWACS self -function AWACS:_CheckOut(Group) +function AWACS:_CheckOut(Group,GID) self:I(self.lid.."_CheckOut") -- check if already known @@ -1280,7 +1428,8 @@ function AWACS:_CheckOut(Group) local text = "" if Outcome then -- yes, known - text = string.format("%s. Copy %s. Have a safe flight home.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. Copy %s. Have a safe flight home.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + self:I(text) -- grab some data before we nil the entry local AnchorAssigned = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local Stack = AnchorAssigned.AnchorStackNo @@ -1289,7 +1438,7 @@ function AWACS:_CheckOut(Group) self:__CheckedOut(1,GID,Stack,Angels) else -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1311,16 +1460,20 @@ function AWACS:_SetClientMenus() local clientset = self.clientset -- Core.Set#SET_GROUP local aliveset = clientset:GetAliveSet() -- #table of #GROUP objects --local aliveobjects = aliveset:GetSetObjects() or {} - local clientmenus = {} + local clientmenus = {} + local clientcount = 0 + local clientcheckedin = 0 for _,_group in pairs(aliveset) do -- go through set and build the menu local grp = _group -- Wrapper.Group#GROUP if self.MenuStrict then -- check if pilot has checked in if grp and grp:IsAlive() and grp:GetUnit(1):IsPlayer() then + clientcount = clientcount + 1 local GID, checkedin = self:_GetManagedGrpID(grp) if checkedin then -- full menu minus checkin + clientcheckedin = clientcheckedin + 1 local hasclientmenu = self.clientmenus[grp:GetName()] -- #AWACS.MenuStructure --local checkinmenu = hasclientmenu.checkin -- Core.Menu#MENU_GROUP_COMMAND --checkinmenu:Remove(nil,grp:GetName()) @@ -1385,7 +1538,11 @@ function AWACS:_SetClientMenus() end end end - self.clientmenus = clientmenus + + self.clientmenus = clientmenus + self.MonitoringData.Players = clientcount or 0 + self.MonitoringData.PlayersCheckedin = clientcheckedin or 0 + return self end @@ -1514,16 +1671,22 @@ end -- @param #number Angels -- @return #AWACS self function AWACS:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels) - self:I(self.lid.."_RemoveIDFromAnchor for GID="..GID.." Stack="..AnchorStackNo.." Angels="..Angels) + local gid = GID or 0 + local stack = AnchorStackNo or 0 + local angels = Angels or 0 + local debugstring = string.format("%s_RemoveIDFromAnchor for GID=%d Stack=%d Angels=%d",self.lid,gid,stack,angels) + self:I(debugstring) -- pull correct anchor - local AnchorStackNo = AnchorStackNo or 1 - local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData - -- pull GID from stack - local removedID = Anchor.AnchorAssignedID:PullByID(GID) - -- push free angels to stack - Anchor.Anchors:Push(Angels) - -- push back AnchorStack - self.AnchorStacks:Push(Anchor) + if stack > 0 and angels > 0 then + local AnchorStackNo = AnchorStackNo or 1 + local Anchor = self.AnchorStacks:ReadByPointer(AnchorStackNo) -- #AWACS.AnchorData + -- pull GID from stack + local removedID = Anchor.AnchorAssignedID:PullByID(GID) + -- push free angels to stack + Anchor.Anchors:Push(Angels) + -- push back AnchorStack + --self.AnchorStacks:Push(Anchor) + end return self end @@ -1584,6 +1747,7 @@ end -- @param #AWACS self -- @param #number size -- @return #number adjusted size +-- @return #string AWACS.Shipsize entry for size 1..3 function AWACS:_GetBlurredSize(size) self:T(self.lid.."_GetBlurredSize") local threatsize = 0 @@ -1592,7 +1756,15 @@ function AWACS:_GetBlurredSize(size) local blurmax = 100 + blur local actblur = math.random(blurmin,blurmax) / 100 threatsize = math.floor(size * actblur) - return threatsize + if threatsize == 0 then threatsize = 1 end + if threatsize then end + local threatsizetext = AWACS.Shipsize[1] + if threatsize == 2 then + threatsizetext = AWACS.Shipsize[2] + elseif threatsize >2 then + threatsizetext = AWACS.Shipsize[3] + end + return threatsize, threatsizetext end --- [Internal] Get threat level as clear test @@ -1639,6 +1811,7 @@ end -- @return #AWACS self function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object) self:I(self.lid.."_CreateTaskForGroup "..GroupID .." Description: "..Description) + local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup local task = {} -- #AWACS.ManagedTask self.ManagedTaskID = self.ManagedTaskID + 1 @@ -1651,16 +1824,14 @@ function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object) if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then task.Target.Type = TARGET.ObjectType.ZONE end - self.AssignedTasks:Push(task,task.TID) - --self:I({task}) + + self.ManagedTasks:Push(task,task.TID) + managedgroup.HasAssignedTask = true managedgroup.CurrentTask = task.TID + self.ManagedGrps[GroupID] = managedgroup - --if managedgroup.IsPlayer then - --self.TaskedCAPHuman:Push(GroupID) - --elseif managedgroup.IsAI then - --self.TaskedCAPAI:Push(GroupID) - --end + return self end @@ -1673,8 +1844,8 @@ function AWACS:_ReadAssignedTaskFromGID(GroupID) local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup if managedgroup and managedgroup.HasAssignedTask then local TaskID = managedgroup.CurrentTask - if self.AssignedTasks:HasUniqueID(TaskID) then - return self.AssignedTasks:ReadByID(TaskID) + if self.ManagedTasks:HasUniqueID(TaskID) then + return self.ManagedTasks:ReadByID(TaskID) end end return nil @@ -1686,8 +1857,8 @@ end -- @return #AWACS.ManagedGroup Group structure or nil if n/e function AWACS:_ReadAssignedGroupFromTID(TaskID) self:T(self.lid.."_ReadAssignedGroupFromTID "..TaskID) - if self.AssignedTasks:HasUniqueID(TaskID) then - local task = self.AssignedTasks:ReadByID(TaskID) -- #AWACS.ManagedTask + if self.ManagedTasks:HasUniqueID(TaskID) then + local task = self.ManagedTasks:ReadByID(TaskID) -- #AWACS.ManagedTask if task and task.AssignedGroupID and task.AssignedGroupID > 0 then return self.ManagedGrps[task.AssignedGroupID] end @@ -1711,12 +1882,12 @@ function AWACS:_CreateIdleTaskForContact(Description,Object,Contact) task.ToDo = Description task.Target = TARGET:New(Object) task.Contact = Contact - task.IsContact = true + --task.IsContact = true task.ScreenText = Description if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then task.Target.Type = TARGET.ObjectType.ZONE end - self.OpenTasks:Push(task,task.TID) + self.ManagedTasks:Push(task,task.TID) return self end @@ -1738,12 +1909,12 @@ function AWACS:_CreateIdleTaskForCluster(Description,Object,Cluster) --task.Target = TARGET:New(Cluster.Contacts[1]) task.Target = TARGET:New(self.intel:GetClusterCoordinate(Cluster)) task.Cluster = Cluster - task.IsCluster = true + --task.IsCluster = true task.ScreenText = Description if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then task.Target.Type = TARGET.ObjectType.ZONE end - self.OpenTasks:Push(task,task.TID) + self.ManagedTasks:Push(task,task.TID) return self end @@ -1777,59 +1948,16 @@ end -- @param #AWACS self -- @return #AWACS self function AWACS:_CheckTaskQueue() - self:I(self.lid.."_CheckTaskQueue") + self:T(self.lid.."_CheckTaskQueue") local opentasks = 0 local assignedtasks = 0 - if self.OpenTasks:IsNotEmpty() then - opentasks = self.OpenTasks:GetSize() - self:I("Open Tasks: " .. opentasks) - local taskstack = self.OpenTasks:GetPointerStack() - for _id,_entry in pairs(taskstack) do - local data = _entry -- Utilities.FiFo#FIFO.IDEntry - local entry = data.data -- #AWACS.ManagedTask - local target = entry.Target -- Ops.Target#TARGET - local description = entry.ToDo - self:I("ToDo = "..description) - if description == AWACS.TaskDescription.ANCHOR or description == AWACS.TaskDescription.REANCHOR then - self:I("Open Tasks ANCHOR/REANCHOR") - -- see if we have reached the anchor zone - local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup - if managedgroup then - local group = managedgroup.Group - local groupcoord = group:GetCoordinate() - local zone = target:GetObject() -- Core.Zone#ZONE - self:T({zone}) - if group:IsInZone(zone) then - self:I("Open Tasks ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) - -- made it - target:Stop() - -- pull task from OpenTasks - self.OpenTasks:PullByPointer(_id) - --[[ add group to idle stack - if managedgroup.IsAI then - self.TaskedCAPAI:PullByPointer(entry.AssignedGroupID) - self.CAPIdleAI:Push(entry.AssignedGroupID) - elseif managedgroup.IsPlayer then - self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID) - self.CAPIdleHuman:Push(entry.AssignedGroupID) - end - --]] - else - -- not there yet - end - end - elseif description == AWACS.TaskDescription.INTERCEPT then - -- TODO - elseif description == AWACS.TaskDescription.RTB then - -- TODO - end - end - end - if self.AssignedTasks:IsNotEmpty() then - opentasks = self.AssignedTasks:GetSize() - self:I("Assigned Tasks: " .. opentasks) - local taskstack = self.AssignedTasks:GetPointerStack() + --- INTERNAL TASKS + + if self.ManagedTasks:IsNotEmpty() then + opentasks = self.ManagedTasks:GetSize() + self:T("Assigned Tasks: " .. opentasks) + local taskstack = self.ManagedTasks:GetPointerStack() for _id,_entry in pairs(taskstack) do local data = _entry -- Utilities.FiFo#FIFO.IDEntry local entry = data.data -- #AWACS.ManagedTask @@ -1837,7 +1965,7 @@ function AWACS:_CheckTaskQueue() local description = entry.ToDo self:I("ToDo = "..description) if description == AWACS.TaskDescription.ANCHOR or description == AWACS.TaskDescription.REANCHOR then - self:I("Open Tasks ANCHOR/REANCHOR") + self:T("Open Tasks ANCHOR/REANCHOR") -- see if we have reached the anchor zone local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup if managedgroup then @@ -1850,14 +1978,14 @@ function AWACS:_CheckTaskQueue() -- made it target:Stop() -- pull task from OpenTasks - self.AssignedTasks:PullByPointer(_id) + self.ManagedTasks:PullByPointer(_id) -- add group to idle stack if managedgroup.IsAI then -- message AI on station self:_MessageAIReadyForTasking(managedgroup.GID) elseif managedgroup.IsPlayer then --self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID) - ---self.CAPIdleHuman:Push(entry.AssignedGroupID) + --self.CAPIdleHuman:Push(entry.AssignedGroupID) end else -- not there yet @@ -1879,6 +2007,14 @@ end -- @return #AWACS self function AWACS:_LogStatistics() self:T(self.lid.."_LogStatistics") + local text = string.gsub(UTILS.OneLineSerialize(self.MonitoringData),",","\n") + local text = string.gsub(text,"{","\n") + local text = string.gsub(text,"}","") + local text = string.gsub(text,"="," = ") + self:T(text) + if self.MonitoringOn then + MESSAGE:New(text,20,"AWACS",true):ToAll() + end return self end @@ -1890,9 +2026,10 @@ function AWACS:AddCAPAirWing(AirWing) self:I(self.lid.."AddCAPAirWing") if AirWing then -- TODO - Test Install callback - -- TODO - add distance to AO as UniqueID + -- DONE - add distance to AO as UniqueID AirWing:SetUsingOpsAwacs(self) - self.CAPAirwings:Push(AirWing) + local distance = self.AOCoordinate:Get2DDistance(AirWing:GetCoordinate()) + self.CAPAirwings:Push(AirWing,distance) end return self end @@ -1942,7 +2079,7 @@ function AWACS:RecruitAssets(MissionType, NassetsMin, NassetsMax) -- Recruit assets. local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2) - + return recruited, assets, legions end @@ -1962,12 +2099,12 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) if Group and Group:IsAlive() then GID, isGroup = self:_GetManagedGrpID(Group) self:T("GID="..GID.." CheckedIn = "..tostring(isGroup)) - grpcallsign = self:_GetCallSign(Group) or "Unknown 1 1" + grpcallsign = self:_GetCallSign(Group,GID) or "Unknown 1 1" end local contact = Contact -- Ops.Intelligence#INTEL.Contact local intel = self.intel -- Ops.Intelligence#INTEL local size = contact.group:CountAliveUnits() - local threatsize = self:_GetBlurredSize(size) + local threatsize, threatsizetext = self:_GetBlurredSize(size) local threatlevel = contact.threatlevel local threattext = self:_GetThreatLevelText(threatlevel) local clustercoordinate = contact.position @@ -1981,8 +2118,6 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) if self.OpsZone:IsVec2InZone(clustercoordinate:GetVec2()) and not IsBogeyDope then Warnlevel = "Warning." - elseif IsBogeyDope then - Warnlevel = "" end if IsNew then @@ -1994,12 +2129,17 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) if isGroup then Warnlevel = string.format("%s. %s",grpcallsign,Warnlevel) end - + -- TTS - local TextTTS = string.format("%s. %s %d ship contact. %s",self.callsigntxt,Warnlevel,threatsize,BRAfromBulls) + local TextTTS = string.format("%s. %s %s %s",self.callsigntxt,Warnlevel,threatsizetext,BRAfromBulls) -- TextOutput - local TextScreen = string.format("%s. %s %d ship contact.\n%s\nThreatlevel %s",self.callsigntxt,Warnlevel,threatsize,BRAfromBulls,threattext) + local TextScreen = string.format("%s. %s. %s\n%s\nThreatlevel %s",self.callsigntxt,Warnlevel,threatsizetext,BRAfromBulls,threattext) + + if IsBogeyDope then + TextTTS = string.format("%s. %s. %s. %s.",self.callsigntxt,grpcallsign,threatsizetext,BRAfromBulls) + TextScreen = string.format("%s. %s. %s.\n%s\nThreatlevel %s",self.callsigntxt,grpcallsign,threatsizetext,BRAfromBulls,threattext) + end local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.TextTTS = TextTTS @@ -2024,81 +2164,179 @@ function AWACS:_GetAliveOpsGroupFromTable(OpsGroups) local handback = nil for _,_OG in pairs(OpsGroups or {}) do local OG = _OG -- Ops.OpsGroup#OPSGROUP - if OG and OG:IsFlightgroup() and OG:IsAlive() then + if OG and OG:IsAlive() then handback = OG - self:T("Handing back OG: " .. OG:GetName()) + --self:I("Handing back OG: " .. OG:GetName()) break end end return handback end +--- [Internal] Clean up mission stack +-- @param #AWACS self +-- @return #number CAPMissions +-- @return #number Alert5Missions +function AWACS:_CleanUpAIMissionStack() + self:I(self.lid.."_CleanUpAIMissionStack") + + local CAPMissions = 0 + local Alert5Missions = 0 + + local MissionStack = FIFO:New() + + self:I("Checking MissionStack") + for _,_mission in pairs(self.CatchAllMissions) do + -- looking for missions of type CAP and ALERT5 + local mission = _mission -- Ops.Auftrag#AUFTRAG + local type = mission:GetType() + if type == AUFTRAG.Type.ALERT5 then + MissionStack:Push(mission,mission.auftragsnummer) + Alert5Missions = Alert5Missions + 1 + elseif type == AUFTRAG.Type.CAP then + MissionStack:Push(mission,mission.auftragsnummer) + CAPMissions = CAPMissions + 1 + end + end + + self.AICAPMissions = nil + self.AICAPMissions = MissionStack + + return CAPMissions, Alert5Missions + +end + +function AWACS:_ConsistencyCheck() + self:I(self.lid.."_ConsistencyCheck") + if self.debug then + self:I("CatchAllMissions") + local catchallm = {} + local report1 = REPORT:New("CatchAll") + report1:Add("====================") + report1:Add("CatchAllMissions") + report1:Add("====================") + for _,_mission in pairs(self.CatchAllMissions) do + local mission = _mission -- Ops.Auftrag#AUFTRAG + local nummer = mission.auftragsnummer or 0 + local type = mission:GetType() + local state = mission:GetState() + local FG = mission:GetOpsGroups() + local OG = self:_GetAliveOpsGroupFromTable(FG) + local OGName = "UnknownFromMission" + if OG then + OGName=OG:GetName() + end + report1:Add(string.format("Auftrag Nr %d Type %s State %s FlightGroup %s",nummer,type,state,OGName)) + if mission:IsNotOver() then + catchallm[#catchallm+1] = mission + end + end + + self.CatchAllMissions = nil + self.CatchAllMissions = catchallm + + local catchallfg = {} + + self:I("CatchAllFGs") + report1:Add("====================") + report1:Add("CatchAllFGs") + report1:Add("====================") + for _,_fg in pairs(self.CatchAllFGs) do + local FG = _fg -- Ops.FlightGroup#FLIGHTGROUP + local mission = FG:GetMissionCurrent() + local OGName = FG:GetName() or "UnknownFromFG" + local nummer = 0 + local type = "No Type" + local state = "None" + if mission then + type = mission:GetType() + nummer = mission.auftragsnummer or 0 + state = mission:GetState() + end + report1:Add(string.format("Auftrag Nr %d Type %s State %s FlightGroup %s",nummer,type,state,OGName)) + if FG:IsAlive() then + catchallfg[#catchallfg+1] = FG + end + end + report1:Add("====================") + self:I(report1:Text()) + + self.CatchAllFGs = nil + self.CatchAllFGs = catchallfg + + end + return self +end + --- [Internal] Check Enough AI CAP on Station -- @param #AWACS self -- @return #AWACS self function AWACS:_CheckAICAPOnStation() self:I(self.lid.."_CheckAICAPOnStation") + + self:_ConsistencyCheck() + + local capmissions, alert5missions = self:_CleanUpAIMissionStack() + self:I({capmissions, alert5missions}) + if self.MaxAIonCAP > 0 then local onstation = self.AICAPMissions:Count() -- control number of AI CAP Flights - if onstation < self.MaxAIonCAP then + if self.AIRequested < self.MaxAIonCAP then -- not enough local AnchorStackNo,free = self:_GetFreeAnchorStack() if free then - -- recruit assets (thanks to FF!) - local recruited, assets, airwings = self:RecruitAssets(AUFTRAG.Type.CAP,1,1) - if recruited then - -- yes, there should be ONE asset and ONE AW - - self:I(self.lid..string.format("Recruited %d assets for mission type CAP", #assets)) - local mission = AUFTRAG:NewCAP(self.AnchorZone,20000,350,self.AnchorZone:GetCoordinate(),nil,nil,{}) - - -- Add asset to mission. - if mission then - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - mission:AddAsset(asset) - asset.legion:AddMission(mission) - -- Debug info. - self:I(self.lid..string.format("Assigning mission \"%s\" [%s] to AW \"%s\"", mission.name, mission.type, asset.legion.alias)) - end - end - - -- TODO - necessary? - LEGION.UnRecruitAssets(assets) - - --self.AirWing:AddMission(mission) - self.AICAPMissions:Push(mission,mission.auftragsnummer) - - else - -- no - self:I(self.lid.."Could NOT recruit assets for mission type CAP") - end + -- create Alert5 and assign to ONE of our AWs + -- TODO better selection due to resource shortage? + local mission = AUFTRAG:NewALERT5(AUFTRAG.Type.CAP) + self.CatchAllMissions[#self.CatchAllMissions+1] = mission + local availableAWS = self.CAPAirwings:Count() + local AWS = self.CAPAirwings:GetDataTable() + -- random + local selectedAW = AWS[math.random(1,availableAWS)] + selectedAW:AddMission(mission) + self.AIRequested = self.AIRequested + 1 + self:I("CAP="..capmissions.." ALERT5="..alert5missions.." Requested="..self.AIRequested) end - elseif onstation > self.MaxAIonCAP then + end + + if self.AIRequested > self.MaxAIonCAP then -- too many, send one home + self:I(string.format("*** Onstation %d > MaxAIOnCAP %d",onstation,self.MaxAIonCAP)) local mission = self.AICAPMissions:Pull() -- Ops.Auftrag#AUFTRAG local Groups = mission:GetOpsGroups() local OpsGroup = self:_GetAliveOpsGroupFromTable(Groups) - mission:__Cancel(5) - self:_CheckOut(OpsGroup) + local GID,checkedin = self:_GetManagedGrpID(OpsGroup) + mission:__Cancel(30) + self.AIRequested = self.AIRequested - 1 + if checkedin then + self:_CheckOut(OpsGroup,GID) + end end + -- Check CAP Mission states if onstation > 0 then - local missionIDs = self.AICAPMissions:GetIDStackSorted() - --self:_CheckInAI(FlightGroup,FlightGroup:GetGroup(),Mission.auftragsnummer) + local missions = self.AICAPMissions:GetDataTable() -- get mission type and state - for _,_MissionID in pairs(missionIDs) do - local mission = self.AICAPMissions:ReadByID(_MissionID) -- Ops.Auftrag#AUFTRAG - self:T("Looking at AuftragsNr " .. mission.auftragsnummer) + for _,_Mission in pairs(missions) do + --local mission = self.AICAPMissions:ReadByID(_MissionID) -- Ops.Auftrag#AUFTRAG + local mission = _Mission -- Ops.Auftrag#AUFTRAG + self:I("Looking at AuftragsNr " .. mission.auftragsnummer) local type = mission:GetType() local state = mission:GetState() - if type == AUFTRAG.Type.CAP then + --if type == AUFTRAG.Type.CAP or type == AUFTRAG.Type.ALERT5 or type == AUFTRAG.Type.ORBIT then + if type == AUFTRAG.Type.ALERT5 then + -- parked up for CAP local OpsGroups = mission:GetOpsGroups() local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) local FGstate = mission:GetGroupStatus(OpsGroup) + if OpsGroup then + FGstate = OpsGroup:GetState() + self:I("FG Object in state: " .. FGstate) + end -- FG ready? - if FGstate == AUFTRAG.Status.STARTED or FGstate == AUFTRAG.Status.EXECUTING then + -- if OpsGroup and (state == AUFTRAG.Status.STARTED or FGstate == AUFTRAG.Status.EXECUTING or FGstate == AUFTRAG.Status.SCHEDULED) then + if OpsGroup and (FGstate == "Parking" or FGstate == "Cruising") then -- has this group checked in already? Avoid double tasking local GID, CheckedInAlready = self:_GetManagedGrpID(OpsGroup:GetGroup()) if not CheckedInAlready then @@ -2109,16 +2347,19 @@ function AWACS:_CheckAICAPOnStation() end end end + -- cycle mission status if onstation > 0 then local report = REPORT:New("CAP Mission Status") report:Add("===============") - local missionIDs = self.AICAPMissions:GetIDStackSorted() + --local missionIDs = self.AICAPMissions:GetIDStackSorted() + local missions = self.AICAPMissions:GetDataTable() local i = 1 - for _,_MissionID in pairs(missionIDs) do + for _,_Mission in pairs(missions) do --for i=1,self.MaxAIonCAP do - local mission = self.AICAPMissions:ReadByID(_MissionID) -- Ops.Auftrag#AUFTRAG + --local mission = self.AICAPMissions:ReadByID(_MissionID) -- Ops.Auftrag#AUFTRAG --local mission = self.AICAPMissions:ReadByPointer(i) -- Ops.Auftrag#AUFTRAG + local mission = _Mission -- Ops.Auftrag#AUFTRAG if mission then i = i + 1 report:Add(string.format("Entry %d",i)) @@ -2128,8 +2369,8 @@ function AWACS:_CheckAICAPOnStation() local OpsGroups = mission:GetOpsGroups() local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP if OpsGroup then - local OpsName = OpsGroup:GetName() or "Unknown" - local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown" + local OpsName = OpsGroup:GetName() or "Unknown" + local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown" report:Add(string.format("Mission FG %s",OpsName)) report:Add(string.format("Callsign %s",OpsCallSign)) report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) @@ -2166,11 +2407,12 @@ function AWACS:_SetAIROE(FlightGroup,Group) Group:OptionROTEvadeFire() Group:OptionRTBBingoFuel(true) Group:OptionKeepWeaponsOnThreat() - local callname = self.AICAPCAllName or CALLSIGN.F16.Viper + local callname = self.AICAPCAllName or CALLSIGN.Aircraft.Colt self.AICAPCAllNumber = self.AICAPCAllNumber + 1 Group:CommandSetCallsign(callname,math.fmod(self.AICAPCAllNumber,9)) -- FG level FlightGroup:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) + FlightGroup:SetDefaultCallsign(callname,math.fmod(self.AICAPCAllNumber,9)) FlightGroup:SetDefaultROE(ENUMS.ROE.WeaponHold) FlightGroup:SetDefaultROT(ENUMS.ROT.EvadeFire) FlightGroup:SetFuelLowRTB(true) @@ -2206,21 +2448,13 @@ function AWACS:onafterStart(From, Event, To) local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 mission:SetTime(nil,timeonstation) - + self.CatchAllMissions[#self.CatchAllMissions+1] = mission + AwacsAW:AddMission(mission) - --[[ callback functions - local function StartSettings(FlightGroup,Mission) - self:_StartSettings(FlightGroup,Mission) - end - - function AwacsAW:OnAfterFlightOnMission(From,Event,To,FlightGroup,Mission) - StartSettings(FlightGroup,Mission) - end - --]] - self.AwacsMission = mission self.AwacsInZone = false -- not yet arrived or gone again + self.AwacsReady = false self:__Status(-30) return self @@ -2241,6 +2475,9 @@ function AWACS:onafterStatus(From, Event, To) if self.AwacsFG then awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP end + + local monitoringdata = self.MonitoringData -- #AWACS.MonitoringData + if awacs and awacs:IsAlive() and not self.AwacsInZone then -- check if we arrived local orbitzone = self.OrbitZone -- Core.Zone#ZONE @@ -2259,12 +2496,13 @@ function AWACS:onafterStatus(From, Event, To) self.RadioQueue:Push(RadioEntry) self:_StartIntel(awacs) + end end - -------------------------------- - -- AWACS - -------------------------------- + -------------------------------- + -- AWACS + -------------------------------- if (awacs and awacs:IsAlive()) then @@ -2308,7 +2546,7 @@ function AWACS:onafterStatus(From, Event, To) -- Check for replacement mission - if any if self.ShiftChangeAwacsFlag and self.ShiftChangeAwacsRequested then -- Ops.Auftrag#AUFTRAG - AWmission = self.EscortMissionReplacement + AWmission = self.AwacsMissionReplacement local esstatus = AWmission:GetState() local ESmissiontime = (timer.getTime() - self.AwacsTimeStamp) local ESTOSLeft = UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds @@ -2383,6 +2621,8 @@ function AWACS:onafterStatus(From, Event, To) report:Add(string.format("Mission FG %s",OpsName)) report:Add(string.format("Callsign %s",OpsCallSign)) report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) + monitoringdata.EscortsStateMission = esstatus + monitoringdata.EscortsStateFG = OpsGroup:GetState() else report:Add("***** Cannot obtain (yet) this missions OpsGroup!") end @@ -2435,19 +2675,44 @@ function AWACS:onafterStatus(From, Event, To) end if self.debug then - self:I(report:Text()) + self:T(report:Text()) end + -- Check on AUFTRAG status for CAP AI - self:_CheckAICAPOnStation() + if self:Is("Running") then + self:_CheckAICAPOnStation() + end + else -- do other stuff end -- Check task queue (both) - self:_CheckTaskQueue() - + if self:Is("Running") then + self:_CheckTaskQueue() + end + + monitoringdata.AwacsShiftChange = self.ShiftChangeAwacsFlag + if self.AwacsFG then + monitoringdata.AwacsStateFG = self.AwacsFG:GetState() + end + monitoringdata.AwacsStateMission = self.AwacsMission:GetState() + --monitoringdata.EscortsStateMission = self.Escor + monitoringdata.EscortsShiftChange = self.ShiftChangeEscortsFlag + monitoringdata.AICAPCurrent = self.AICAPMissions:Count() + monitoringdata.AICAPMax = self.MaxAIonCAP + --monitoringdata.Players = self.clientset:CountAlive() + monitoringdata.Airwings = self.CAPAirwings:Count() + + self.MonitoringData = monitoringdata + + if self.debug then + self:_LogStatistics() + end + self:__Status(30) + return self end @@ -2516,6 +2781,10 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo self:I({From, Event, To, "GID=" .. GID, "Stack=" .. AnchorStackNo}) -- TODO local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + if not managedgroup then + self:E(self.lid .. "**** GID "..GID.." Not Registered!") + return self + end managedgroup.AnchorStackNo = AnchorStackNo managedgroup.AnchorStackAngels = AnchorAngels self.ManagedGrps[GID] = managedgroup @@ -2527,7 +2796,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo local AnchorCoordTxt = Anchor.AnchorZoneCoordinateText or "unknown" local Angels = AnchorAngels or 25 local AnchorSpeed = self.CapSpeedBase or 220 - local AuftragsNr = managedgroup.CurrentTask + local AuftragsNr = managedgroup.CurrentAuftrag local textTTS = string.format("%s. %s. Anchor at %s at angels %d doing %d knots. Wait for task assignment.",self.callsigntxt,CallSign,AnchorName,Angels,AnchorSpeed) local ROEROT = self.AwacsROE.." "..self.AwacsROT @@ -2547,19 +2816,27 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.AnchorZone) - if isAI and AuftragsNr and AuftragsNr > 0 and self.AICAPMissions:HasUniqueID(AuftragsNr) then - -- Change current Auftrag to Orbit at Anchor - local capalt = Angels*1000 - local capspeed = UTILS.KnotsToAltKIAS(self.CapSpeedBase,capalt) - local AnchorMission = AUFTRAG:NewORBIT(Anchor.AnchorZoneCoordinate,capalt,capspeed,0,15) - managedgroup.FlightGroup:AddMission(AnchorMission) - managedgroup.FlightGroup:GetMissionCurrent():__Cancel(5) - self.AICAPMissions:PullByID(AuftragsNr) - self.AICAPMissions:Push(AnchorMission,AnchorMission.auftragsnummer) - managedgroup.CurrentTask = AnchorMission.auftragsnummer - self.ManagedGrps[GID] = managedgroup - end - + -- if isAI and AuftragsNr and AuftragsNr > 0 and self.AICAPMissions:HasUniqueID(AuftragsNr) then + + -- if it's a Alert5, we want to push CAP instead + if isAI then + local auftrag = managedgroup.FlightGroup:GetMissionCurrent() -- Ops.Auftrag#AUFTRAG + if auftrag then + local auftragtype = auftrag:GetType() + if auftragtype == AUFTRAG.Type.ALERT5 then + -- all correct + local capauftrag = AUFTRAG:NewCAP(Anchor.AnchorZone,Angels*1000,AnchorSpeed,Anchor.AnchorZone:GetCoordinate(),0,15,{}) + capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) + self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag + managedgroup.FlightGroup:AddMission(capauftrag) + auftrag:Cancel() + else + self:E("**** AssignedAnchor but Auftrag NOT ALERT5!") + end + else + self:E("**** AssignedAnchor but NO Auftrag!") + end + end return self end @@ -2584,8 +2861,28 @@ end -- @return #AWACS self function AWACS:onafterNewContact(From,Event,To,Contact) self:T({From, Event, To, Contact}) - self.Contacts:Push(Contact) + + self.CID = self.CID + 1 + self.Countactcounter = self.Countactcounter + 1 + + local managedcontact = {} -- #AWACS.ManagedContact + managedcontact.CID = self.CID + managedcontact.Contact = Contact + -- TODO set as per tech age + managedcontact.IFF = "Spades" -- no IFF yet + managedcontact.Target = TARGET:New(Contact.group) + managedcontact.LinkedGroup = 0 + managedcontact.LinkedTask = 0 + managedcontact.Status = AWACS.TaskStatus.IDLE + local phoneid = math.fmod(self.Countactcounter,27) + if phoneid == 0 then phoneid = 1 end + managedcontact.TargetGroupNaming = AWACS.Phonetic[phoneid] + + self.Contacts:Push(Contact,self.CID) + self.ManagedContacts:Push(Contact,self.CID) + self:_AnnounceContact(Contact,true,nil,false) + return self end @@ -2635,12 +2932,14 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) if not RadioEntry.FromAI then -- AI AWACS Speaking self.AwacsFG:RadioTransmission(RadioEntry.TextTTS,1,false) + self:I(RadioEntry.TextTTS) else -- CAP AI speaking if RadioEntry.GroupID and RadioEntry.GroupID ~= 0 then local managedgroup = self.ManagedGrps[RadioEntry.GroupID] -- #AWACS.ManagedGroup if managedgroup and managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive() then managedgroup.FlightGroup:RadioTransmission(RadioEntry.TextTTS,1,false) + self:I(RadioEntry.TextTTS) end end end @@ -2651,6 +2950,7 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) local managedgroup = self.ManagedGrps[RadioEntry.GroupID] -- #AWACS.ManagedGroup if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group) + self:I(RadioEntry.TextScreen) end else MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition) @@ -2707,6 +3007,7 @@ function AWACS:onafterAwacsShiftChange(From,Event,To) -- set up the AWACS and let it orbit local AwacsAW = self.AirWing -- Ops.AirWing#AIRWING local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) + self.CatchAllMissions[#self.CatchAllMissions+1] = mission local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 mission:SetTime(nil,timeonstation) @@ -2729,8 +3030,17 @@ end function AWACS:onafterFlightOnMission(From, Event, To, FlightGroup, Mission) self:I({From, Event, To}) -- coming back from AW, set up the flight - if self:Is("Running") then - self:_StartSettings(FlightGroup,Mission) + self:I("FlightGroup " .. FlightGroup:GetName() .. " Mission " .. Mission:GetName() .. " Type "..Mission:GetType()) + self.CatchAllFGs[#self.CatchAllFGs+1] = FlightGroup + if not self:Is("Stopped") then + if not self.AwacsReady or self.ShiftChangeAwacsFlag or self.ShiftChangeEscortsFlag then + self:_StartSettings(FlightGroup,Mission) + elseif Mission and (Mission:GetType() == AUFTRAG.Type.CAP or Mission:GetType() == AUFTRAG.Type.ALERT5 or Mission:GetType() == AUFTRAG.Type.ORBIT) then + if not self.FlightGroups:HasUniqueID(FlightGroup:GetName()) then + self:I("Pushing FG " .. FlightGroup:GetName() .. " to stack!") + self.FlightGroups:Push(FlightGroup,FlightGroup:GetName()) + end + end end return self end @@ -2751,7 +3061,7 @@ AwacsAW:SetReportOn() AwacsAW:SetMarker(true) AwacsAW:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Kutaisi)) AwacsAW:SetRespawnAfterDestroyed(900) -AwacsAW:Start() +AwacsAW:__Start(2) -- And a couple of Squads -- AWACS itself @@ -2774,25 +3084,24 @@ AwacsAW:NewPayload("Escorts",-1,{AUFTRAG.Type.ESCORT},100) -- CAP local Squad_Three = SQUADRON:New("CAP",2,"CAP North") -Squad_Three:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) +Squad_Three:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},80) Squad_Three:SetFuelLowRefuel(true) Squad_Three:SetFuelLowThreshold(0.3) Squad_Three:SetTurnoverTime(10,20) AwacsAW:AddSquadron(Squad_Three) AwacsAW:NewPayload("Aerial-1-2",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},100) - -- We need a secondary AirWing for testing local AwacsAW2 = AIRWING:New("AirForce WH-2","AirForce Two") AwacsAW2:SetReportOn() AwacsAW2:SetMarker(true) -AwacsAW2:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Beslan)) +AwacsAW2:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Batumi)) AwacsAW2:SetRespawnAfterDestroyed(900) -AwacsAW2:Start() +AwacsAW2:__Start(2) -- CAP2 local Squad_ThreeOne = SQUADRON:New("CAP2",4,"CAP West") -Squad_ThreeOne:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) +Squad_ThreeOne:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},80) Squad_ThreeOne:SetFuelLowRefuel(true) Squad_ThreeOne:SetFuelLowThreshold(0.3) Squad_ThreeOne:SetTurnoverTime(10,20) @@ -2802,7 +3111,7 @@ AwacsAW2:NewPayload("CAP 2-1",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG. -- Get AWACS started local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("NW Zone"),"Anchor One",255,radio.modulation.AM ) testawacs:SetEscort(2) -testawacs:SetAwacsDetails(CALLSIGN.AWACS.Darkstar,1,22,230,61,15) +testawacs:SetAwacsDetails(CALLSIGN.AWACS.Darkstar,1,300,300,60,20) testawacs:SetSRS("E:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010,nil) testawacs:AddCAPAirWing(AwacsAW2) testawacs:__Start(5) From 83491f535aa554e53317585ae84e94599ce49f90 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 21 Apr 2022 18:56:51 +0200 Subject: [PATCH 03/16] Point - BRAANATO - will return BRA only if no aspect can be generated --- Moose Development/Moose/Core/Point.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 2d5375dbb..d4dbc4d31 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2765,7 +2765,7 @@ do -- COORDINATE return "BRA, " .. self:GetBRAText( AngleRadians, Distance, Settings, Language ) end - --- Create a BRAA NATO call string to this COORDINATE from the FromCOORDINATE. + --- Create a BRAA NATO call string to this COORDINATE from the FromCOORDINATE. Note - BRA delivered if no aspect can be obtained and "Merged" if range < 3nm -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. -- @param #boolean Spades Add "Spades" at the end if true (no IFF/VID ID yet known) @@ -2788,10 +2788,14 @@ do -- COORDINATE local alt = UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)--*1000 - local track = UTILS.BearingToCardinal(bearing) + local track = UTILS.BearingToCardinal(bearing) or "North" if rangeNM > 3 then - BRAANATO = string.format("BRAA, %s, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + if aspect == "" then + BRAANATO = string.format("BRA, %s, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + else + BRAANATO = string.format("BRAA, %s, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + end if Spades then BRAANATO = BRAANATO..", Spades." else From 51d924c1b8107597cec783dc0a15ca7f2b4d528c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 22 Apr 2022 13:32:34 +0200 Subject: [PATCH 04/16] BRAANato - make bearing a 3digit number --- Moose Development/Moose/Core/Point.lua | 4 ++-- Moose Development/Moose/Utilities/Utils.lua | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index d4dbc4d31..36f467284 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2792,9 +2792,9 @@ do -- COORDINATE if rangeNM > 3 then if aspect == "" then - BRAANATO = string.format("BRA, %s, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) + BRAANATO = string.format("BRA, %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track) else - BRAANATO = string.format("BRAA, %s, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) + BRAANATO = string.format("BRAA, %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) end if Spades then BRAANATO = BRAANATO..", Spades." diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f45cd7ab5..4850c263d 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2436,7 +2436,11 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local alt = UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0)--*1000 local track = UTILS.BearingToCardinal(hdg) if rangeNM > 3 then - BRAANATO = string.format("%s, BRAA, %s, %d miles, Angels %d, %s, Track %s, Spades.",GroupWords,bearing, rangeNM, alt, aspect, track) + if aspect == "" then + BRAANATO = string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing, rangeNM, alt, track) + else + BRAANATO = string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords, bearing, rangeNM, alt, aspect, track) + end end return BRAANATO end \ No newline at end of file From ad3a3c52663d71c7a1f0b8874fc042c47db4593b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 22 Apr 2022 20:34:24 +0200 Subject: [PATCH 05/16] AWACS a0.0.7 - cleaned up AWACS calls, implemented DECLARE --- Moose Development/Moose/Ops/Awacs.lua | 409 +++++++++++++++++++++----- 1 file changed, 332 insertions(+), 77 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index e29e9e6ee..5c6491ad7 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -89,7 +89,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "alpha 0.0.6", -- #string + version = "alpha 0.0.7", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -138,7 +138,7 @@ AWACS = { ShiftChangeEscortsRequested = false, CAPAirwings = {}, -- Utilities.FiFo#FIFO MonitoringData = {}, - MonitoringOn = true, + MonitoringOn = false, FlightGroups = {}, AwacsMission = nil, AwacsInZone = false, -- not yet arrived or gone again @@ -172,6 +172,16 @@ AWACS.AnchorNames = { [10] = "Ten", } +--- +-- @field IFF +AWACS.IFF = +{ + SPADES = "Spades", + NEUTRAL = "Neutral", + FRIENDLY = "Friendly", + ENEMY = "Enemy", +} + --- -- @field Phonetic AWACS.Phonetic = @@ -281,6 +291,7 @@ AWACS.THREATLEVEL = { -- @field #number AnchorStackNo -- @field #number AnchorStackAngels -- @field #number ContactCID +-- @field Ultilities.FiFo#FIFO TaskQueue --- Contact Data -- @type AWACS.ManagedContact @@ -363,28 +374,31 @@ AWACS.TaskStatus = { -- TODO-List ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DEBUG - Escorts via AirWing not staying on --- TODO - Link (multiple) AWs to the AWACS Controller +-- TODO - System for Players to VID contacts? And put data into contacst fifo +-- TODO - TripWire +-- DEBUG - Multiple AIRWING connection? Can't really get recruit to work, switched to random round robin +-- TODO - LotATC / IFF +-- TODO - Player & AI tasking +-- TODO - (WIP) Reporting +-- TODO - Missile launch callout +-- TODO - Localization +-- DEBUG - Shift Change, Change on asset RTB or dead or mission done +-- TODO - Borders for INTEL. Necessary? +-- TODO - Event detection, Player joining, eject, crash, dead, leaving; AI shot -> DEFEND +-- -- DONE - Use AO as Anchor of Bulls, AO as default -- DONE - SRS TTS output -- DONE - Check-In/Out Humans -- DONE - Check-In/Out AI -- DONE - Picture --- TODO - TripWire +-- DONE - Declare +-- DONE - Bogey Dope -- DONE - Radio Menu -- DONE - Intel Detection --- TODO - CHIEF / COMMANDER / AIRWING connection? --- TODO - LotATC / IFF -- DONE - ROE --- TODO - Player & AI tasking -- DONE - Anchor Stack Management --- TODO - Reporting --- TODO - Missile launch callout --- TODO - Localization -- DONE - Shift Length AWACS/AI --- DEBUG - Shift Change, Change on asset RTB or dead or mission done --- TODO - Borders for INTEL --- TODO - FIFO for checkin/checkout and tasking --- TODO - Event detection + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -486,6 +500,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.ShiftChangeEscortsFlag = false self.CAPTimeOnStation = 4 + self.DeclareRadius = 5 -- NM + self.AwacsMission = nil self.AwacsInZone = false -- not yet arrived or gone again self.AwacsReady = false @@ -755,8 +771,8 @@ function AWACS:_StartSettings(FlightGroup,Mission) --group:CommandSetCallsign(CALLSIGN.Aircraft.Pig,self.CallSignNo,2) AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil) - self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) - + --self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) + self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) --local text = string.format("%s starting for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") local text = string.format("%s. All stations, SUNRISE SUNRISE SUNRISE, %s.",self.callsigntxt,self.callsigntxt) self:T(self.lid..text) @@ -804,7 +820,8 @@ function AWACS:_StartSettings(FlightGroup,Mission) -- group:CommandSetCallsign(CALLSIGN.Aircraft.Pig,self.CallSignNo,2) AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil) - self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) + --self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) + self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) local text = string.format("%s shift change for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") self:T(self.lid..text) @@ -834,10 +851,12 @@ end -- @param Wrapper.Group#GROUP Group Group to check -- @return #number ID -- @return #boolean CheckedIn +-- @return #string CallSign function AWACS:_GetManagedGrpID(Group) self:T(self.lid.."_GetManagedGrpID for "..Group:GetName()) local GID = 0 local Outcome = false + local CallSign = "Ghost 1 1" local nametocheck = Group:GetName() local managedgrps = self.ManagedGrps or {} for _,_managed in pairs (managedgrps) do @@ -845,9 +864,10 @@ function AWACS:_GetManagedGrpID(Group) if managed.GroupName == nametocheck then GID = managed.GID Outcome = true + CallSign = managed.CallSign end end - return GID, Outcome + return GID, Outcome, CallSign end --- [Internal] AWACS Get TTS compatible callsign @@ -963,6 +983,7 @@ function AWACS:_CreateBogeyDope(Callsign,GID) local cluster = fifo:PullByID(sortedIDs[counter]) -- Ops.Intelligence#INTEL.Contact self:T({cluster}) if cluster and cluster.position then + -- TODO - add tag self:_AnnounceContact(cluster,false,group,true) end end @@ -979,13 +1000,14 @@ end -- @return #AWACS self function AWACS:_Picture(Group) self:T(self.lid.."_Picture") - local text = "Picture WIP" + local text = "" local textScreen = text - local GID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome = self:_GetManagedGrpID(Group) + local gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" if not self.intel then -- no intel yet! - text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Clear.",gcallsign, self.callsigntxt) textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1019,7 +1041,7 @@ function AWACS:_Picture(Group) if clustersAO == 0 and clustersEWR == 0 then -- clear - text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Clear.",gcallsign, self.callsigntxt) textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1034,8 +1056,8 @@ function AWACS:_Picture(Group) else if clustersAO > 0 then - text = string.format("%s. %s. Picture A O. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") - textScreen = string.format("%s. %s. Picture AO. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Picture A O. ",gcallsign, self.callsigntxt) + textScreen = string.format("%s. %s. Picture AO. ",gcallsign, self.callsigntxt) if clustersAO == 1 then text = text .. "One group. " textScreen = textScreen .. "One group.\n" @@ -1053,12 +1075,14 @@ function AWACS:_Picture(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreatePicture(true,self:_GetCallSign(Group,GID) or "Unknown 1 1",GID) + self:_CreatePicture(true,gcallsign,GID) + + self.PictureAO:Clear() end if clustersEWR > 0 then - text = string.format("%s. %s. Picture Early Warning. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") - textScreen = string.format("%s. %s. Picture EWR. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Picture Early Warning. ",gcallsign, self.callsigntxt) + textScreen = string.format("%s. %s. Picture EWR. ",gcallsign, self.callsigntxt) if clustersEWR == 1 then text = text .. "One group. " textScreen = textScreen .. "One group.\n" @@ -1076,13 +1100,15 @@ function AWACS:_Picture(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreatePicture(false,self:_GetCallSign(Group,GID) or "Unknown 1 1",GID) + self:_CreatePicture(false,gcallsign,GID) + + self.PictureEWR:Clear() end end elseif self.AwacsFG then -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Negative. You are not checked in.",gcallsign, self.callsigntxt) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text @@ -1106,10 +1132,11 @@ function AWACS:_BogeyDope(Group) local text = "BogeyDope WIP" local textScreen = "BogeyDope WIP" local GID, Outcome = self:_GetManagedGrpID(Group) + local gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" if not self.intel then -- no intel yet! - text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Clear.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1151,7 +1178,7 @@ function AWACS:_BogeyDope(Group) if contactsAO == 0 then -- clear - text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Clear.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1166,7 +1193,7 @@ function AWACS:_BogeyDope(Group) else if contactsAO > 0 then - text = string.format("%s. %s. Bogey Dope. ",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Bogey Dope. ",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) if contactsAO == 1 then text = text .. "One group. " textScreen = text .. "\n" @@ -1184,13 +1211,13 @@ function AWACS:_BogeyDope(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreateBogeyDope(self:_GetCallSign(Group,GID) or "Unknown 1 1",GID) + self:_CreateBogeyDope(self:_GetCallSign(Group,GID) or "Ghost 1 1",GID) end end elseif self.AwacsFG then -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text @@ -1211,17 +1238,186 @@ end -- @param Wrapper.Group#GROUP Group Group to use -- @return #AWACS self function AWACS:_Declare(Group) - self:T(self.lid.."_Declare") + self:I(self.lid.."_Declare") - local GID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome, Callsign = self:_GetManagedGrpID(Group) local text = "Declare Not yet implemented" + local TextTTS = "" + + if Outcome then + --yes, known + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + local group = managedgroup.Group + local position = group:GetCoordinate() + local radius = UTILS.NMToMeters(self.DeclareRadius) or UTILS.NMToMeters(5) + -- find contacts nearby + local groupzone = ZONE_GROUP:New(group:GetName(),group, radius) + local Coalitions = {"red","neutral"} + if self.coalition == coalition.side.NEUTRAL then + Coalitions = {"red","blue"} + elseif self.coalition == coalition.side.RED then + Coalitions = {"blue","neutral"} + end + local contactset = SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(Coalitions):FilterZones({groupzone}):FilterOnce() + local numbercontacts = contactset:CountAlive() or 0 + local foundcontacts = {} + if numbercontacts > 0 then + -- we have some around + -- sort by distance + contactset:ForEach( + function (airpl) + local distance = position:Get2DDistance(airpl:GetCoordinate()) + distance = UTILS.Round(distance,0) + 1 + foundcontacts[distance] = airpl + end + ,{} + ) + for _dist,_contact in UTILS.spairs(foundcontacts) do + local distanz = _dist + local contact = _contact -- Wrapper.Group#GROUP + local ccoalition = contact:GetCoalition() + local ctypename = contact:GetTypeName() + + local friendorfoe = "Neutral" + if self.self.ModernEra then + if ccoalition == self.coalition then + friendorfoe = "Friendly" + elseif ccoalition == coalition.side.NEUTRAL then + friendorfoe = "Neutral" + elseif ccoalition ~= self.coalition then + friendorfoe = "Hostile" + end + else + friendorfoe = "Spades" + end + -- AWACS - “Uzi 1-1, Magic, hostile/friendly” + + -- see if that works + self:I(string.format("Distance %d ContactName %s Coalition %d (%s) TypeName %s",distanz,contact:GetName(),ccoalition,friendorfoe,ctypename)) + + text = string.format("%s. %s. %s.",Callsign,self.callsigntxt,friendorfoe) + TextTTS = text + if self.ModernEra then + text = string.format("%s %s.",text,ctypename) + end + break + end + else + -- clear + text = string.format("%s. %s. %s.",Callsign,self.callsigntxt,"Clear") + TextTTS = text + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = TextTTS + RadioEntry.TextScreen = text + RadioEntry.GroupID = GID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + -- + elseif self.AwacsFG then + -- no, unknown + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = GID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + end + return self +end + +--- [Internal] AWACS Menu for Judy +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_Judy(Group) + self:T(self.lid.."_Judy") + + local GID, Outcome = self:_GetManagedGrpID(Group) + local text = "Judy Not yet implemented" if Outcome then --[[ yes, known --]] elseif self.AwacsFG then -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = GID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + return self +end + +--- [Internal] AWACS Menu for Unable +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_Unable(Group) + self:T(self.lid.."_Unable") + + local GID, Outcome = self:_GetManagedGrpID(Group) + local text = "Unable Not yet implemented" + if Outcome then + --[[ yes, known + + --]] + elseif self.AwacsFG then + -- no, unknown + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = GID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + return self +end + +--- [Internal] AWACS Menu for Abort +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_TaskAbort(Group) + self:T(self.lid.."_TaskAbort") + + local GID, Outcome = self:_GetManagedGrpID(Group) + local text = "Abort Not yet implemented" + if Outcome then + --[[ yes, known + + --]] + elseif self.AwacsFG then + -- no, unknown + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1245,7 +1441,7 @@ end function AWACS:_Showtask(Group) self:T(self.lid.."_Showtask") - local GID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome, Callsign = self:_GetManagedGrpID(Group) local text = "Showtask WIP" if Outcome then @@ -1254,23 +1450,23 @@ function AWACS:_Showtask(Group) -- Do we have a task? local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup - if managedgroup.IsPlayer and self.TaskedCAPHuman:HasUniqueID(GID) then + if managedgroup.IsPlayer then - if managedgroup.CurrentAuftrag >0 and self.ManagedTasks:HasUniqueID(managedgroup.CurrentAuftrag) then + if managedgroup.CurrentTask >0 and self.ManagedTasks:HasUniqueID(managedgroup.CurrentTask) then -- get task structure - local currenttask = self.ManagedTasks:ReadByID(managedgroup.CurrentAuftrag) -- #AWACS.ManagedTask + local currenttask = self.ManagedTasks:ReadByID(managedgroup.CurrentTask) -- #AWACS.ManagedTask if currenttask then local status = currenttask.Status local targettype = currenttask.Target:GetCategory() local targetstatus = currenttask.Target:GetState() local ToDo = currenttask.ToDo local description = currenttask.ScreenText - local callsign = self:_GetCallSign(Group,GID) + local callsign = Callsign if self.debug then local taskreport = REPORT:New("AWACS Tasking Display") taskreport:Add("===============") - taskreport:Add(string.format("Task for Callsign: %s",callsign)) + taskreport:Add(string.format("Task for Callsign: %s",Callsign)) taskreport:Add(string.format("Task: %s with Status: %s",ToDo,status)) taskreport:Add(string.format("Target of Type: %s",targettype)) taskreport:Add(string.format("Target in State: %s",targetstatus)) @@ -1286,7 +1482,7 @@ function AWACS:_Showtask(Group) elseif self.AwacsFG then -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1318,17 +1514,19 @@ function AWACS:_CheckIn(Group) managedgroup.GroupName = Group:GetName() managedgroup.IsPlayer = true managedgroup.IsAI = false - managedgroup.CallSign = self:_GetCallSign(Group,GID) or "Unknown 1 1" + managedgroup.CallSign = self:_GetCallSign(Group,GID) or "Ghost 1 1" managedgroup.CurrentAuftrag = 0 managedgroup.HasAssignedTask = false managedgroup.GID = self.ManagedGrpID + managedgroup.TaskQueue = FIFO:New() + GID = managedgroup.GID self.ManagedGrps[self.ManagedGrpID]=managedgroup - text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign) + text = string.format("%s. %s. Copy. Await tasking.",managedgroup.CallSign,self.callsigntxt) self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) elseif self.AwacsFG then - text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Negative. You are already checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1375,6 +1573,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) managedgroup.CurrentAuftrag = AuftragsNr managedgroup.HasAssignedTask = false managedgroup.GID = self.ManagedGrpID + managedgroup.TaskQueue = FIFO:New() -- SRS voice for CAP --FlightGroup:SetSRS(PathToSRS,Gender,Culture,Voice,Port,PathToGoogleKey) @@ -1397,11 +1596,11 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) self.RadioQueue:Push(RadioEntry) self.ManagedGrps[self.ManagedGrpID]=managedgroup - text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign) + text = string.format("%s. %s. Copy. Await tasking.",managedgroup.CallSign,self.callsigntxt) self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) else - text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Negative. You are already checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1428,7 +1627,7 @@ function AWACS:_CheckOut(Group,GID) local text = "" if Outcome then -- yes, known - text = string.format("%s. Copy %s. Have a safe flight home.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Copy. Have a safe flight home.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) self:I(text) -- grab some data before we nil the entry local AnchorAssigned = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -1438,7 +1637,7 @@ function AWACS:_CheckOut(Group,GID) self:__CheckedOut(1,GID,Stack,Angels) else -- no, unknown - text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group,GID) or "Unknown 1 1") + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1483,18 +1682,24 @@ function AWACS:_SetClientMenus() local picture = MENU_GROUP_COMMAND:New(grp,"Picture",basemenu,self._Picture,self,grp) local bogeydope = MENU_GROUP_COMMAND:New(grp,"Bogey Dope",basemenu,self._BogeyDope,self,grp) local declare = MENU_GROUP_COMMAND:New(grp,"Declare",basemenu,self._Declare,self,grp) - local showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",basemenu,self._Showtask,self,grp) + local tasking = MENU_GROUP:New(grp,"Tasking",basemenu) + local showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",tasking,self._Showtask,self,grp) + local judy = MENU_GROUP_COMMAND:New(grp,"Judy",tasking,self._Judy,self,grp) + local unable = MENU_GROUP_COMMAND:New(grp,"Unable",tasking,self._Unable,self,grp) + local abort = MENU_GROUP_COMMAND:New(grp,"Abort",tasking,self._TaskAbort,self,grp) local checkout = MENU_GROUP_COMMAND:New(grp,"Check Out",basemenu,self._CheckOut,self,grp):Refresh() clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure groupname = grp:GetName(), menuset = true, basemenu = basemenu, - --checkin = checkin, checkout= checkout, picture = picture, bogeydope = bogeydope, declare = declare, showtask = showtask, + judy = judy, + unable = unable, + abort = abort, } elseif not clientmenus[grp:GetName()] then -- check in only @@ -1507,11 +1712,6 @@ function AWACS:_SetClientMenus() menuset = true, basemenu = basemenu, checkin = checkin, - --checkout= checkout, - --picture = picture, - --bogeydope = bogeydope, - --declare = declare, - --showtask = showtask, } end end @@ -1521,7 +1721,11 @@ function AWACS:_SetClientMenus() local picture = MENU_COALITION_COMMAND:New(self.coalition,"Picture",basemenu,self._Picture,self,grp) local bogeydope = MENU_COALITION_COMMAND:New(self.coalition,"Bogey Dope",basemenu,self._BogeyDope,self,grp) local declare = MENU_COALITION_COMMAND:New(self.coalition,"Declare",basemenu,self._Declare,self,grp) - local showtask = MENU_COALITION_COMMAND:New(self.coalition,"Showtask",basemenu,self._Showtask,self,grp) + local tasking = MENU_GROUP:New(grp,"Tasking",basemenu) + local showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",tasking,self._Showtask,self,grp) + local judy = MENU_GROUP_COMMAND:New(grp,"Judy",tasking,self._Judy,self,grp) + local unable = MENU_GROUP_COMMAND:New(grp,"Unable",tasking,self._Unable,self,grp) + local abort = MENU_GROUP_COMMAND:New(grp,"Abort",tasking,self._TaskAbort,self,grp) local checkin = MENU_COALITION_COMMAND:New(self.coalition,"Check In",basemenu,self._CheckIn,self,grp) local checkout = MENU_COALITION_COMMAND:New(self.coalition,"Check Out",basemenu,self._CheckOut,self,grp):Refresh() clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure @@ -1534,6 +1738,9 @@ function AWACS:_SetClientMenus() bogeydope = bogeydope, declare = declare, showtask = showtask, + judy = judy, + unable = unable, + abort = abort, } end end @@ -1829,6 +2036,7 @@ function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object) managedgroup.HasAssignedTask = true managedgroup.CurrentTask = task.TID + managedgroup.TaskQueue:Push(task.TID) self.ManagedGrps[GroupID] = managedgroup @@ -2089,17 +2297,27 @@ end -- @param #boolean IsNew -- @param Wrapper.Group#GROUP Group Announce to Group if not nil -- @param #boolean IsBogeyDope If true, this is a bogey dope announcement +-- @param #string Tag Tag name for this contact -- @return #AWACS self -function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) +function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag) self:T(self.lid.."_AnnounceContact") -- do we have a group to talk to? + local tag = "" + local Tag = Tag + local CID = 0 + if not Tag then + -- injected data available? + CID = Contact.CID or 0 + Tag = Contact.TargetGroupNaming or "" + --self:I({CID,Tag}) + end local isGroup = false local GID = 0 - local grpcallsign = "Unknown 1 1" + local grpcallsign = "Ghost 1 1" if Group and Group:IsAlive() then GID, isGroup = self:_GetManagedGrpID(Group) self:T("GID="..GID.." CheckedIn = "..tostring(isGroup)) - grpcallsign = self:_GetCallSign(Group,GID) or "Unknown 1 1" + grpcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" end local contact = Contact -- Ops.Intelligence#INTEL.Contact local intel = self.intel -- Ops.Intelligence#INTEL @@ -2111,19 +2329,51 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) local BRAfromBulls = self:_GetBRAfromBullsOrAO(clustercoordinate) if isGroup then - BRAfromBulls = clustercoordinate:ToStringBRA(Group:GetCoordinate()) + --BRAfromBulls = clustercoordinate:ToStringBRA(Group:GetCoordinate()) + BRAfromBulls = clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),IsNew) end - local Warnlevel = "Early Warning." + --local Warnlevel = "Early Warning." - if self.OpsZone:IsVec2InZone(clustercoordinate:GetVec2()) and not IsBogeyDope then - Warnlevel = "Warning." + --if self.OpsZone:IsVec2InZone(clustercoordinate:GetVec2()) and not IsBogeyDope then + --Warnlevel = "Warning." + --end + + -- "Uzi 1-1, Magic, BRA, 183 for 10 at 2000, hot" + -- ", , /, , , BRA, for at angels , " + + local BRAText = "" + local TextScreen = "" + + if isGroup then + BRAText = string.format("%s, %s.",grpcallsign,self.callsigntxt) + TextScreen = string.format("%s, %s.",grpcallsign,self.callsigntxt) + else + BRAText = string.format("%s.",self.callsigntxt) + TextScreen = string.format("%s.",self.callsigntxt) end if IsNew then - Warnlevel = Warnlevel .. " New" + BRAText = BRAText .. " New contact." + TextScreen = TextScreen .. " New contact." + else + BRAText = BRAText .. " Contact." + TextScreen = TextScreen .. " Contact." end + if Tag and Tag ~= "" then + BRAText = BRAText .. " "..Tag.."." + BRAText = BRAText .. " "..Tag.."." + end + + BRAText = BRAText .. " "..threatsizetext..". "..BRAfromBulls + TextScreen = TextScreen .. " "..threatsizetext.."\n"..BRAfromBulls + + self:I(BRAText) + self:I(TextScreen) + + --[[ + Warnlevel = string.format("%s %s", Warnlevel, threattext) if isGroup then @@ -2140,14 +2390,15 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) TextTTS = string.format("%s. %s. %s. %s.",self.callsigntxt,grpcallsign,threatsizetext,BRAfromBulls) TextScreen = string.format("%s. %s. %s.\n%s\nThreatlevel %s",self.callsigntxt,grpcallsign,threatsizetext,BRAfromBulls,threattext) end + --]] local RadioEntry = {} -- #AWACS.RadioEntry - RadioEntry.TextTTS = TextTTS + RadioEntry.TextTTS = BRAText RadioEntry.TextScreen = TextScreen RadioEntry.IsNew = IsNew RadioEntry.IsGroup = isGroup RadioEntry.GroupID = GID - RadioEntry.Duration = STTS.getSpeechTime(TextTTS,1.2,false)+2 or 16 + RadioEntry.Duration = STTS.getSpeechTime(BRAText,1.2,false)+2 or 16 RadioEntry.ToScreen = true self.RadioQueue:Push(RadioEntry) @@ -2791,17 +3042,17 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo local isPlayer = managedgroup.IsPlayer local isAI = managedgroup.IsAI local Group = managedgroup.Group - local CallSign = managedgroup.CallSign or "unknown 1 1" + local CallSign = managedgroup.CallSign or "Ghost 1 1" local AnchorName = Anchor.AnchorZone:GetName() or "unknown" local AnchorCoordTxt = Anchor.AnchorZoneCoordinateText or "unknown" local Angels = AnchorAngels or 25 local AnchorSpeed = self.CapSpeedBase or 220 local AuftragsNr = managedgroup.CurrentAuftrag - local textTTS = string.format("%s. %s. Anchor at %s at angels %d doing %d knots. Wait for task assignment.",self.callsigntxt,CallSign,AnchorName,Angels,AnchorSpeed) + local textTTS = string.format("%s. %s. Anchor at %s at angels %d doing %d knots. Wait for task assignment.",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed) local ROEROT = self.AwacsROE.." "..self.AwacsROT - local textScreen = string.format("%s. %s.\nAnchor at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s\nWait for task assignment.",self.callsigntxt,CallSign,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) - local TextTasking = string.format("%s. %s.\nAnchor at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s",self.callsigntxt,CallSign,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) + local textScreen = string.format("%s. %s.\nAnchor at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s\nWait for task assignment.",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) + local TextTasking = string.format("%s. %s.\nAnchor at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -2868,8 +3119,8 @@ function AWACS:onafterNewContact(From,Event,To,Contact) local managedcontact = {} -- #AWACS.ManagedContact managedcontact.CID = self.CID managedcontact.Contact = Contact - -- TODO set as per tech age - managedcontact.IFF = "Spades" -- no IFF yet + -- TODO set as per tech age... + managedcontact.IFF = AWACS.IFF.SPADES -- no IFF yet managedcontact.Target = TARGET:New(Contact.group) managedcontact.LinkedGroup = 0 managedcontact.LinkedTask = 0 @@ -2878,10 +3129,14 @@ function AWACS:onafterNewContact(From,Event,To,Contact) if phoneid == 0 then phoneid = 1 end managedcontact.TargetGroupNaming = AWACS.Phonetic[phoneid] + -- let's see if we can inject some info into Contact + Contact.CID = managedcontact.CID + Contact.TargetGroupNaming = managedcontact.TargetGroupNaming + self.Contacts:Push(Contact,self.CID) self.ManagedContacts:Push(Contact,self.CID) - self:_AnnounceContact(Contact,true,nil,false) + self:_AnnounceContact(Contact,true,nil,false,managedcontact.TargetGroupNaming) return self end @@ -2960,7 +3215,7 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) if self:Is("Running") then -- exit if stopped - self:__CheckRadioQueue(nextcall+2) + self:__CheckRadioQueue(nextcall+3) end return self end From 4c7ac68858e34aeafe3a96a61146b719d2ea46ad Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 23 Apr 2022 15:08:43 +0200 Subject: [PATCH 06/16] UTILS - UTILS.GetReportingName(Typename) ENUMS - added ENUMS.ReportingName --- Moose Development/Moose/Utilities/Enums.lua | 47 ++++++++++++++++++++- Moose Development/Moose/Utilities/Utils.lua | 17 ++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 07eae9dc2..e11044f81 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -448,4 +448,49 @@ ENUMS.Phonetic = X = 'Xray', Y = 'Yankee', Z = 'Zulu', -} \ No newline at end of file +} + +--- Reporting Names (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_NATO_reporting_names_for_fighter_aircraft). +-- DCS known aircraft types +-- +-- @type ENUMS.ReportingName +ENUMS.ReportingName = +{ + NATO = { + -- Fighters + Dragon = "JF-17", -- China, correct? + Fagot = "MiG-15", + Farmer = "MiG-19", -- Shenyang J-6 and Mikoyan-Gurevich MiG-19 + Felon = "Su-57", + Fencer = "Su-24", + Fishbed = "MiG-21", + Fitter = "Su-17", -- Sukhoi Su-7 and Su-17/Su-20/Su-22 + Flogger = "MiG-23", --and MiG-27 + Flogger_D = "MiG-27", --and MiG-23 + Flagon = "Su-15", + Foxbat = "MiG-25", + Fulcrum = "MiG-29", + Foxhound = "MiG-31", + Flanker = "Su-27", -- Sukhoi Su-27/Su-30/Su-33/Su-35/Su-37 and Shenyang J-11/J-15/J-16 + Flanker_C = "Su-30", + Flanker_E = "Su-35", + Flanker_F = "Su-37", + Flanker_Dragon = "J-11A", + Sea_Flanker = "Su-33", + Fullback = "Su-32", -- also Su-34 + Frogfoot = "Su-25", + Tomcat = "F-14", -- Iran + Mirage = "Mirage", -- various non-NATO + -- Bomber + H6J = "H6-J", + Bear = "Tu-142", -- also Tu-95 + Blinder = "Tu-22", + Blackjack = "Tu-160", + -- AIC / Transport / Other + Clank = "An-30", + Curl = "An-26", + Candid = "IL-76", + Midas = "IL-78", + Mainstay = "A-50", -- KJ-2000 China + } +} diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 4850c263d..8f7c4f7ec 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1447,6 +1447,23 @@ function UTILS.GetModulationName(Modulation) end +--- Get the NATO reporting name of a unit type name +-- @param #number Typename The type name. +-- @return #string The Reporting name or "Bogey". +function UTILS.GetReportingName(Typename) + + local typename = string.lower(Typename) + + for name, value in pairs(ENUMS.ReportingName.NATO) do + local svalue = string.lower(value) + if string.find(typename,svalue,1,true) then + return name + end + end + + return "Bogey" +end + --- Get the callsign name from its enumerator value -- @param #number Callsign The enumerator callsign. -- @return #string The callsign name or "Ghostrider". From 2f4d5b32b612daea3aa61554a158d98d3781ef08 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 23 Apr 2022 16:41:47 +0200 Subject: [PATCH 07/16] GROUP/UNIT - added :GetNatoReportingName() --- Moose Development/Moose/Utilities/Enums.lua | 3 ++- Moose Development/Moose/Wrapper/Group.lua | 18 ++++++++++++++++++ Moose Development/Moose/Wrapper/Unit.lua | 11 +++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index e11044f81..bdcea66a8 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -483,7 +483,8 @@ ENUMS.ReportingName = Mirage = "Mirage", -- various non-NATO -- Bomber H6J = "H6-J", - Bear = "Tu-142", -- also Tu-95 + Sea_Bear = "Tu-142", -- also Tu-95 + Bear = "Tu-95", -- also Tu-142 Blinder = "Tu-22", Blackjack = "Tu-160", -- AIC / Transport / Other diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index bd5ac2a48..822b98696 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -900,6 +900,24 @@ function GROUP:GetTypeName() return nil end +--- [AIRPLANE] Get the NATO reporting name of a UNIT. Currently airplanes only! +--@param #UNIT self +--@return #string NatoReportingName or "Bogey" if unknown. +function GROUP:GetNatoReportingName() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupTypeName = DCSGroup:getUnit(1):getTypeName() + self:T3( GroupTypeName ) + return UTILS.GetReportingName(GroupTypeName) + end + + return "Bogey" + +end + --- Gets the player name of the group. -- @param #GROUP self -- @return #string The player name of the group. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 680dbb318..d1c2d51a1 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -408,6 +408,17 @@ function UNIT:GetClient() return nil end +--- [AIRPLANE] Get the NATO reporting name of a UNIT. Currently airplanes only! +--@param #UNIT self +--@return #string NatoReportingName or "Bogey" if unknown. +function UNIT:GetNatoReportingName() + + local typename = self:GetTypeName() + return UTILS.GetReportingName(typename) + +end + + --- Returns the unit's number in the group. -- The number is the same number the unit has in ME. -- It may not be changed during the mission. From fd5a190490c54c1e763801060b3724e313ec9c02 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 23 Apr 2022 18:58:27 +0200 Subject: [PATCH 08/16] AWACS Alpha --- Moose Development/Moose/Ops/Awacs.lua | 155 +++++++++++++++++--------- 1 file changed, 103 insertions(+), 52 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 5c6491ad7..63ac06685 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -89,7 +89,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "alpha 0.0.7", -- #string + version = "alpha 0.0.8", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -371,7 +371,7 @@ AWACS.TaskStatus = { --@field #boolean FromAI ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO-List +-- TODO-List 0.0.8 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DEBUG - Escorts via AirWing not staying on -- TODO - System for Players to VID contacts? And put data into contacst fifo @@ -463,8 +463,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ end self.AOCoordinate = self.OpsZone:GetCoordinate() - self.UseBullsAO = false - self.ControlZoneRadius = 100 -- nm + self.UseBullsAO = false -- as per NATOPS + self.ControlZoneRadius = 120 -- nm self.AnchorZone = ZONE:New(AnchorZone) -- Core.Zone#ZONE self.Frequency = Frequency or 271 -- #number self.Modulation = Modulation or radio.modulation.AM @@ -846,6 +846,35 @@ function AWACS:_StartSettings(FlightGroup,Mission) return self end +--- [Internal] Return Bullseye BR for Alpha Check etc, returns e.g. "Bullseye 021, 16" +-- @param #AWACS self +-- @param Core.Point#COORDINATE Coordinate +-- @return #string BullseyeBR +function AWACS:ToStringBULLS( Coordinate ) + local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.coalition ) ) + local DirectionVec3 = BullsCoordinate:GetDirectionVec3( Coordinate ) + local AngleRadians = Coordinate:GetAngleRadians( DirectionVec3 ) + local Distance = Coordinate:Get2DDistance( BullsCoordinate ) + local AngleDegrees = UTILS.Round( UTILS.ToDegree( AngleRadians ), 0 ) + local Bearing = string.format( '%03d', AngleDegrees ) + local Distance = UTILS.Round( UTILS.MetersToNM( Distance ), 0 ) + return string.format("Bullseye %03d, %03d",Bearing,Distance) +end + +--- [Internal] Chnage Bullseye string to be TTS friendly, "Bullseye 021, 16" returns e.g. "Bulls eye 0-2-1, 1-6" +-- @param #AWACS self +-- @param #string Text Input text +-- @return #string BullseyeBRTTS +function AWACS:ToStringBullsTTS(Text) + local text = Text + text=string.gsub(text,"Bullseye","Bulls eye") + text=string.gsub(text,"%d","%1 ") + text=string.gsub(text,"?," ,",") + text=string.gsub(text," $","") + return text +end + + --- [Internal] Check if a group has checked in -- @param #AWACS self -- @param Wrapper.Group#GROUP Group Group to check @@ -908,7 +937,7 @@ function AWACS:_CreatePicture(AO,Callsign,GID) local groupcoord = group:GetCoordinate() local fifo = self.PictureAO -- Utilities.FiFo#FIFO - local maxentries = self.maxspeakentries + local maxentries = self.maxspeakentries or 3 local counter = 0 if not AO then @@ -919,7 +948,7 @@ function AWACS:_CreatePicture(AO,Callsign,GID) if entries < maxentries then maxentries = entries end - local text = "First group." + local text = "Group" local textScreen = text while counter < maxentries do @@ -933,7 +962,7 @@ function AWACS:_CreatePicture(AO,Callsign,GID) text = text .. " "..refBRAA.."." textScreen = textScreen .." "..refBRAA..".\n" if counter < maxentries then - text = text .. " Next group." + text = text .. " Group" textScreen = textScreen .. text .. "\n" end end @@ -983,8 +1012,10 @@ function AWACS:_CreateBogeyDope(Callsign,GID) local cluster = fifo:PullByID(sortedIDs[counter]) -- Ops.Intelligence#INTEL.Contact self:T({cluster}) if cluster and cluster.position then + local tag = cluster.TargetGroupNaming + local reportingname = cluster.group:GetNatoReportingName() -- TODO - add tag - self:_AnnounceContact(cluster,false,group,true) + self:_AnnounceContact(cluster,false,group,true,tag,false,reportingname) end end @@ -1056,8 +1087,8 @@ function AWACS:_Picture(Group) else if clustersAO > 0 then - text = string.format("%s. %s. Picture A O. ",gcallsign, self.callsigntxt) - textScreen = string.format("%s. %s. Picture AO. ",gcallsign, self.callsigntxt) + text = string.format("%s. %s. Picture. ",gcallsign, self.callsigntxt) + textScreen = string.format("%s. %s. Picture. ",gcallsign, self.callsigntxt) if clustersAO == 1 then text = text .. "One group. " textScreen = textScreen .. "One group.\n" @@ -1080,7 +1111,7 @@ function AWACS:_Picture(Group) self.PictureAO:Clear() end - if clustersEWR > 0 then + if clustersAO < 3 and clustersEWR > 0 then text = string.format("%s. %s. Picture Early Warning. ",gcallsign, self.callsigntxt) textScreen = string.format("%s. %s. Picture EWR. ",gcallsign, self.callsigntxt) if clustersEWR == 1 then @@ -1100,7 +1131,7 @@ function AWACS:_Picture(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreatePicture(false,gcallsign,GID) + --self:_CreatePicture(false,gcallsign,GID) self.PictureEWR:Clear() end @@ -1522,9 +1553,16 @@ function AWACS:_CheckIn(Group) GID = managedgroup.GID self.ManagedGrps[self.ManagedGrpID]=managedgroup - text = string.format("%s. %s. Copy. Await tasking.",managedgroup.CallSign,self.callsigntxt) + + local alphacheckbulls = self:ToStringBULLS(Group:GetCoordinate()) + alphacheckbulls = self:ToStringBullsTTS(alphacheckbulls)-- make tts friendly + + self.ManagedGrps[self.ManagedGrpID]=managedgroup + text = string.format("%s. %s. Alpha Check %s",managedgroup.CallSign,self.callsigntxt,alphacheckbulls) + self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) + elseif self.AwacsFG then text = string.format("%s. %s. Negative. You are already checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) end @@ -1584,7 +1622,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,self.CAPVoice,self.Port,nil) - text = string.format("%s. %s. Check in for CAP. Expected playtime %d hours.",managedgroup.CallSign, self.callsigntxt,self.CAPTimeOnStation) + text = string.format("%s. %s. Checking in as fragged. Expected playtime %d hours. Request Alpha Check Bulls Eye.",self.callsigntxt, managedgroup.CallSign, self.CAPTimeOnStation) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text @@ -1594,9 +1632,12 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) RadioEntry.GroupID = managedgroup.GID self.RadioQueue:Push(RadioEntry) + + local alphacheckbulls = self:ToStringBULLS(Group:GetCoordinate()) + alphacheckbulls = self:ToStringBullsTTS(alphacheckbulls)-- make tts friendly self.ManagedGrps[self.ManagedGrpID]=managedgroup - text = string.format("%s. %s. Copy. Await tasking.",managedgroup.CallSign,self.callsigntxt) + text = string.format("%s. %s. Alpha Check %s",managedgroup.CallSign,self.callsigntxt,alphacheckbulls) self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) else @@ -2004,7 +2045,8 @@ function AWACS:_GetBRAfromBullsOrAO(clustercoordinate) BRAText = "AO "..refcoord:ToStringBR(clustercoordinate) else -- get BR from Bulls - BRAText = clustercoordinate:ToStringBULLS(self.coalition) + BRAText = self:ToStringBULLS(clustercoordinate) + -- BRAText = clustercoordinate:ToStringBULLS(self.coalition) end return BRAText end @@ -2294,12 +2336,14 @@ end --- [Internal] Announce a new contact -- @param #AWACS self -- @param Ops.Intelligence#INTEL.Contact Contact --- @param #boolean IsNew +-- @param #boolean IsNew Is a new contact -- @param Wrapper.Group#GROUP Group Announce to Group if not nil -- @param #boolean IsBogeyDope If true, this is a bogey dope announcement --- @param #string Tag Tag name for this contact +-- @param #string Tag Tag name for this contact. Alpha, Brave, Charlie ... +-- @param #boolean IsPopup This is a pop-up group +-- @param #string ReportingName The NATO code reporting name for the contact, e.g. "Foxbat". "Bogey" if unknown. -- @return #AWACS self -function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag) +function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,ReportingName) self:T(self.lid.."_AnnounceContact") -- do we have a group to talk to? local tag = "" @@ -2319,6 +2363,7 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag) self:T("GID="..GID.." CheckedIn = "..tostring(isGroup)) grpcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" end + local contact = Contact -- Ops.Intelligence#INTEL.Contact local intel = self.intel -- Ops.Intelligence#INTEL local size = contact.group:CountAliveUnits() @@ -2329,16 +2374,9 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag) local BRAfromBulls = self:_GetBRAfromBullsOrAO(clustercoordinate) if isGroup then - --BRAfromBulls = clustercoordinate:ToStringBRA(Group:GetCoordinate()) BRAfromBulls = clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),IsNew) end - --local Warnlevel = "Early Warning." - - --if self.OpsZone:IsVec2InZone(clustercoordinate:GetVec2()) and not IsBogeyDope then - --Warnlevel = "Warning." - --end - -- "Uzi 1-1, Magic, BRA, 183 for 10 at 2000, hot" -- ", , /, , , BRA, for at angels , " @@ -2354,11 +2392,14 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag) end if IsNew then - BRAText = BRAText .. " New contact." - TextScreen = TextScreen .. " New contact." + BRAText = BRAText .. " New group." + TextScreen = TextScreen .. " New group." + elseif IsPopup then + BRAText = BRAText .. " Pop-up group." + TextScreen = TextScreen .. " Pop-up group." else - BRAText = BRAText .. " Contact." - TextScreen = TextScreen .. " Contact." + BRAText = BRAText .. " Group." + TextScreen = TextScreen .. " Group." end if Tag and Tag ~= "" then @@ -2369,28 +2410,31 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag) BRAText = BRAText .. " "..threatsizetext..". "..BRAfromBulls TextScreen = TextScreen .. " "..threatsizetext.."\n"..BRAfromBulls + if self.ModernEra then + -- Platform + if ReportingName and ReportingName ~= "Bogey" then + BRAText = BRAText .. " "..ReportingName.."." + TextScreen = TextScreen .. " "..ReportingName.."." + end + -- High - > 40k feet + local height = contact.group:GetAltitude() + local height = UTILS.Round(UTILS.MetersToFeet(height)/1000,0) -- e.g, 25 + if height >= 40 then + BRAText = BRAText .. " High." + TextScreen = TextScreen .. " High." + end + -- Fast (>600kn) or Very fast (>900kn) + local speed = contact.group:GetVelocityKNOTS() + if speed > 900 then + BRAText = BRAText .. " Very Fast." + TextScreen = TextScreen .. " Very Fast." + elseif speed >= 600 and speed <= 900 then + BRAText = BRAText .. " Fast." + TextScreen = TextScreen .. " Fast." + end + end + self:I(BRAText) - self:I(TextScreen) - - --[[ - - Warnlevel = string.format("%s %s", Warnlevel, threattext) - - if isGroup then - Warnlevel = string.format("%s. %s",grpcallsign,Warnlevel) - end - - -- TTS - local TextTTS = string.format("%s. %s %s %s",self.callsigntxt,Warnlevel,threatsizetext,BRAfromBulls) - - -- TextOutput - local TextScreen = string.format("%s. %s. %s\n%s\nThreatlevel %s",self.callsigntxt,Warnlevel,threatsizetext,BRAfromBulls,threattext) - - if IsBogeyDope then - TextTTS = string.format("%s. %s. %s. %s.",self.callsigntxt,grpcallsign,threatsizetext,BRAfromBulls) - TextScreen = string.format("%s. %s. %s.\n%s\nThreatlevel %s",self.callsigntxt,grpcallsign,threatsizetext,BRAfromBulls,threattext) - end - --]] local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.TextTTS = BRAText @@ -3128,6 +3172,13 @@ function AWACS:onafterNewContact(From,Event,To,Contact) local phoneid = math.fmod(self.Countactcounter,27) if phoneid == 0 then phoneid = 1 end managedcontact.TargetGroupNaming = AWACS.Phonetic[phoneid] + managedcontact.ReportingName = Contact.group:GetNatoReportingName() -- e.g. Foxbat. Bogey if unknown + + local IsPopup = false + -- is this a pop-up group? i.e. appeared inside AO + if self.OpsZone:IsVec2InZone(Contact.position:GetVec2()) then + IsPopup = true + end -- let's see if we can inject some info into Contact Contact.CID = managedcontact.CID @@ -3136,7 +3187,7 @@ function AWACS:onafterNewContact(From,Event,To,Contact) self.Contacts:Push(Contact,self.CID) self.ManagedContacts:Push(Contact,self.CID) - self:_AnnounceContact(Contact,true,nil,false,managedcontact.TargetGroupNaming) + self:_AnnounceContact(Contact,true,nil,false,managedcontact.TargetGroupNaming,IsPopup,managedcontact.ReportingName) return self end From d9f409069a9c637a2e2e36533b8aa176bb241339 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 24 Apr 2022 12:46:11 +0200 Subject: [PATCH 09/16] INTEL - add platform type for AIR contacts (Foxbat ... ), defaults to Bogey for AIR and Unknown for everything else SRS- added option to set a label for the SRS radio overlay OpsGroup - added options to use said label, and option to override a frequency for an SRS (TTS) sender --- Moose Development/Moose/Ops/Intelligence.lua | 10 ++++++-- Moose Development/Moose/Ops/OpsGroup.lua | 17 ++++++++---- Moose Development/Moose/Sound/SRS.lua | 27 +++++++++++++++++--- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 60329ee5f..4a475ae92 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -123,6 +123,7 @@ INTEL = { -- @field Ops.Target#TARGET target The Target attached to this contact. -- @field #string recce The name of the recce unit that detected this contact. -- @field #string ctype Contact type. +-- @field #string platform [AIR] Contact platform name, e.g. Foxbat, Flanker_E, defaults to Bogey if unknown --- Cluster info. -- @type INTEL.Cluster @@ -153,7 +154,7 @@ INTEL.Ctype={ --- INTEL class version. -- @field #string version -INTEL.version="0.3.0" +INTEL.version="0.3.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -866,7 +867,12 @@ function INTEL:_CreateContact(Positionable, RecceName) item.isground = group:IsGround() or false item.isship = group:IsShip() or false item.isStatic=false - + if group:IsAir() then + item.platform=group:GetNatoReportingName() + else + -- TODO optionally add ground types? + item.platform="Unknown" + end if item.category==Group.Category.AIRPLANE or item.category==Group.Category.HELICOPTER then item.ctype=INTEL.Ctype.AIRCRAFT elseif item.category==Group.Category.GROUND or item.category==Group.Category.TRAIN then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8907ad8b6..c124ccaba 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1916,14 +1916,16 @@ end -- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. -- @param #number Port SRS port. Default 5002. -- @param #string PathToGoogleKey Full path to the google credentials JSON file, e.g. `"C:\Users\myUsername\Downloads\key.json"`. +-- @param #string Label Label of the SRS comms for the SRS Radio overlay. Defaults to "ROBOT". No spaces allowed! -- @return #OPSGROUP self -function OPSGROUP:SetSRS(PathToSRS, Gender, Culture, Voice, Port, PathToGoogleKey) +function OPSGROUP:SetSRS(PathToSRS, Gender, Culture, Voice, Port, PathToGoogleKey, Label) self.useSRS=true self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) self.msrs:SetGender(Gender) self.msrs:SetCulture(Culture) self.msrs:SetVoice(Voice) self.msrs:SetPort(Port) + self.msrs:SetLabel(Label) if PathToGoogleKey then self.msrs:SetGoogle(PathToGoogleKey) end @@ -1936,8 +1938,9 @@ end -- @param #string Text Text of transmission. -- @param #number Delay Delay in seconds before the transmission is started. -- @param #boolean SayCallsign If `true`, the callsign is prepended to the given text. Default `false`. +-- @param #number Frequency Override sender frequency, helpful when you need multiple radios from the same sender. Default is the frequency set for the OpsGroup. -- @return #OPSGROUP self -function OPSGROUP:RadioTransmission(Text, Delay, SayCallsign) +function OPSGROUP:RadioTransmission(Text, Delay, SayCallsign, Frequency) if Delay and Delay>0 then self:ScheduleOnce(Delay, OPSGROUP.RadioTransmission, self, Text, 0, SayCallsign) @@ -1946,8 +1949,12 @@ function OPSGROUP:RadioTransmission(Text, Delay, SayCallsign) if self.useSRS and self.msrs then local freq, modu, radioon=self:GetRadio() - - self.msrs:SetFrequencies(freq) + + if Frequency then + self.msrs:SetFrequencies(Frequency) + else + self.msrs:SetFrequencies(freq) + end self.msrs:SetModulations(modu) if SayCallsign then @@ -1956,7 +1963,7 @@ function OPSGROUP:RadioTransmission(Text, Delay, SayCallsign) end -- Debug info. - self:T(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) + self:I(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) self.msrs:PlayText(Text) end diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 2c4411ac1..3ec73c6f1 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -45,6 +45,7 @@ -- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send. -- @field #string path Path to the SRS exe. This includes the final slash "/". -- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". +-- @field #string Label Label showing up on the SRS radio overlay. Default is "ROBOT". No spaces allowed. -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -125,11 +126,12 @@ MSRS = { volume = 1, speed = 1, coordinate = nil, + Label = "ROBOT", } --- MSRS class version. -- @field #string version -MSRS.version="0.0.3" +MSRS.version="0.0.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -164,6 +166,7 @@ function MSRS:New(PathToSRS, Frequency, Modulation) self:SetModulations(Modulation) self:SetGender() self:SetCoalition() + self:SetLabel() return self end @@ -206,6 +209,22 @@ function MSRS:GetPath() return self.path end +--- Set label. +-- @param #MSRS self +-- @param #number Label. Default "ROBOT" +-- @return #MSRS self +function MSRS:SetLabel(Label) + self.Label=Label or "ROBOT" + return self +end + +--- Get label. +-- @param #MSRS self +-- @return #number Label. +function MSRS:GetLabel() + return self.Label +end + --- Set port. -- @param #MSRS self -- @param #number Port Port. Default 5002. @@ -640,8 +659,9 @@ end -- @param #number volume Volume. -- @param #number speed Speed. -- @param #number port Port. +-- @param #string label Label, defaults to "ROBOT" (displayed sender name in the radio overlay of SRS) - No spaces allowed! -- @return #string Command. -function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port) +function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port,label) local path=self:GetPath() or STTS.DIRECTORY local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe" @@ -654,6 +674,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp volume=volume or self.volume speed=speed or self.speed port=port or self.port + label=label or self.Label -- Replace modulation modus=modus:gsub("0", "AM") @@ -668,7 +689,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") -- Command. - local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT") + local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, label) -- Set voice or gender/culture. if voice then From 37671cefa3cb3400b3be7ea0ef753b793fa44cdb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 24 Apr 2022 13:50:23 +0200 Subject: [PATCH 10/16] GROUP - overwrite GetHeight() inherited from POSITIONABLE with something that is actually working for groups --- Moose Development/Moose/Wrapper/Group.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 822b98696..ce8bf3dee 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -796,11 +796,19 @@ function GROUP:GetVelocityVec3() return nil end +--- Returns the average group altitude in meters. +-- @param Wrapper.Group#GROUP self +-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. +-- @return #number The altitude of the group or nil if is not existing or alive. +function GROUP:GetAltitude(FromGround) + self:F2( self.GroupName ) + return self:GetHeight(FromGround) +end --- Returns the average group height in meters. -- @param Wrapper.Group#GROUP self --- @param #boolean FromGround Measure from the ground or from sea level. Provide **true** for measuring from the ground. **false** or **nil** if you measure from sea level. --- @return DCS#Vec3 The height of the group or nil if is not existing or alive. +-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. +-- @return #number The height of the group or nil if is not existing or alive. function GROUP:GetHeight( FromGround ) self:F2( self.GroupName ) From 68c75a4e3413239d0f014ad85d9ec61f1ba5eea8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 24 Apr 2022 14:06:24 +0200 Subject: [PATCH 11/16] Menu Refresh cleanup --- Moose Development/Moose/Core/Menu.lua | 201 +++++--------------------- 1 file changed, 35 insertions(+), 166 deletions(-) diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index f0962380d..58b385e60 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -14,7 +14,7 @@ -- * Only create or delete menus when required, and keep existing menus persistent. -- * Update menu structures. -- * Refresh menu structures intelligently, based on a time stamp of updates. --- - Delete obscolete menus. +-- - Delete obsolete menus. -- - Create new one where required. -- - Don't touch the existing ones. -- * Provide a variable amount of parameters to menus. @@ -23,7 +23,7 @@ -- * Provide a great tool to manage menus in your code. -- -- DCS Menus can be managed using the MENU classes. --- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to +-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to -- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing -- menus is not a easy feat if you have complex menu hierarchies defined. -- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. @@ -53,7 +53,6 @@ -- @module Core.Menu -- @image Core_Menu.JPG - MENU_INDEX = {} MENU_INDEX.MenuMission = {} MENU_INDEX.MenuMission.Menus = {} @@ -64,10 +63,7 @@ MENU_INDEX.Coalition[coalition.side.RED] = {} MENU_INDEX.Coalition[coalition.side.RED].Menus = {} MENU_INDEX.Group = {} - - function MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or "" if ParentMenu then if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then @@ -95,20 +91,16 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText ) Path = Path .. "@" .. MenuText return Path - end - function MENU_INDEX:PrepareMission() self.MenuMission.Menus = self.MenuMission.Menus or {} end - function MENU_INDEX:PrepareCoalition( CoalitionSide ) self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} end - --- -- @param Wrapper.Group#GROUP Group function MENU_INDEX:PrepareGroup( Group ) @@ -119,42 +111,26 @@ function MENU_INDEX:PrepareGroup( Group ) end end - - function MENU_INDEX:HasMissionMenu( Path ) - return self.MenuMission.Menus[Path] end - function MENU_INDEX:SetMissionMenu( Path, Menu ) - self.MenuMission.Menus[Path] = Menu end - function MENU_INDEX:ClearMissionMenu( Path ) - self.MenuMission.Menus[Path] = nil end - - function MENU_INDEX:HasCoalitionMenu( Coalition, Path ) - return self.Coalition[Coalition].Menus[Path] end - function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu ) - self.Coalition[Coalition].Menus[Path] = Menu end - function MENU_INDEX:ClearCoalitionMenu( Coalition, Path ) - self.Coalition[Coalition].Menus[Path] = nil end - - function MENU_INDEX:HasGroupMenu( Group, Path ) if Group and Group:IsAlive() then local MenuGroupName = Group:GetName() @@ -162,53 +138,36 @@ function MENU_INDEX:HasGroupMenu( Group, Path ) end return nil end - function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) - local MenuGroupName = Group:GetName() Group:F({MenuGroupName=MenuGroupName,Path=Path}) self.Group[MenuGroupName].Menus[Path] = Menu end - function MENU_INDEX:ClearGroupMenu( Group, Path ) - local MenuGroupName = Group:GetName() self.Group[MenuGroupName].Menus[Path] = nil end - function MENU_INDEX:Refresh( Group ) - for MenuID, Menu in pairs( self.MenuMission.Menus ) do Menu:Refresh() end - for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do Menu:Refresh() end - for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do Menu:Refresh() end - local GroupName = Group:GetName() for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do Menu:Refresh() end - + + return self end - - - - - - - do -- MENU_BASE - --- @type MENU_BASE -- @extends Core.Base#BASE - --- Defines the main MENU class where other MENU classes are derived from. -- This is an abstract class, so don't use it. -- @field #MENU_BASE @@ -216,10 +175,10 @@ do -- MENU_BASE ClassName = "MENU_BASE", MenuPath = nil, MenuText = "", - MenuParentPath = nil + MenuParentPath = nil, } - --- Consructor + --- Constructor -- @param #MENU_BASE -- @return #MENU_BASE function MENU_BASE:New( MenuText, ParentMenu ) @@ -228,27 +187,25 @@ do -- MENU_BASE if ParentMenu ~= nil then MenuParentPath = ParentMenu.MenuPath end - - local self = BASE:Inherit( self, BASE:New() ) + local self = BASE:Inherit( self, BASE:New() ) - self.MenuPath = nil - self.MenuText = MenuText - self.ParentMenu = ParentMenu - self.MenuParentPath = MenuParentPath - self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText + self.MenuPath = nil + self.MenuText = MenuText + self.ParentMenu = ParentMenu + self.MenuParentPath = MenuParentPath + self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText self.Menus = {} self.MenuCount = 0 self.MenuStamp = timer.getTime() self.MenuRemoveParent = false - + if self.ParentMenu then self.ParentMenu.Menus = self.ParentMenu.Menus or {} self.ParentMenu.Menus[MenuText] = self end - - return self + + return self end - function MENU_BASE:SetParentMenu( MenuText, Menu ) if self.ParentMenu then self.ParentMenu.Menus = self.ParentMenu.Menus or {} @@ -256,7 +213,6 @@ do -- MENU_BASE self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1 end end - function MENU_BASE:ClearParentMenu( MenuText ) if self.ParentMenu and self.ParentMenu.Menus[MenuText] then self.ParentMenu.Menus[MenuText] = nil @@ -266,7 +222,6 @@ do -- MENU_BASE end end end - --- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}. -- @param #MENU_BASE self -- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}. @@ -276,7 +231,6 @@ do -- MENU_BASE self.MenuRemoveParent = RemoveParent return self end - --- Gets a @{Menu} from a parent @{Menu} -- @param #MENU_BASE self @@ -285,7 +239,6 @@ do -- MENU_BASE function MENU_BASE:GetMenu( MenuText ) return self.Menus[MenuText] end - --- Sets a menu stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @param MenuStamp @@ -323,9 +276,7 @@ do -- MENU_BASE end end - do -- MENU_COMMAND_BASE - --- @type MENU_COMMAND_BASE -- @field #function MenuCallHandler -- @extends Core.Menu#MENU_BASE @@ -346,8 +297,7 @@ do -- MENU_COMMAND_BASE -- @return #MENU_COMMAND_BASE function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE - + local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE -- When a menu function goes into error, DCS displays an obscure menu message. -- This error handler catches the menu error and displays the full call stack. local ErrorHandler = function( errmsg ) @@ -367,7 +317,7 @@ do -- MENU_COMMAND_BASE local Status, Result = xpcall( MenuFunction, ErrorHandler ) end - return self + return self end --- This sets the new command function of a menu, @@ -380,7 +330,6 @@ do -- MENU_COMMAND_BASE self.CommandMenuFunction = CommandMenuFunction return self end - --- This sets the new command arguments of a menu, -- so that if a menu is regenerated, or if command arguments change, -- that the arguments set for the menu are loosely coupled with the menu itself!!! @@ -391,35 +340,30 @@ do -- MENU_COMMAND_BASE self.CommandMenuArguments = CommandMenuArguments return self end - end - do -- MENU_MISSION - --- @type MENU_MISSION -- @extends Core.Menu#MENU_BASE - --- Manages the main menus for a complete mission. -- -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- @field #MENU_MISSION MENU_MISSION = { - ClassName = "MENU_MISSION" + ClassName = "MENU_MISSION", } --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. -- @param #MENU_MISSION self -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). + -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_MISSION function MENU_MISSION:New( MenuText, ParentMenu ) MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) - if MissionMenu then return MissionMenu else @@ -432,17 +376,15 @@ do -- MENU_MISSION end end - --- Refreshes a radio item for a mission -- @param #MENU_MISSION self -- @return #MENU_MISSION function MENU_MISSION:Refresh() - do missionCommands.removeItem( self.MenuPath ) self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath ) end - + return self end --- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept! @@ -466,7 +408,6 @@ do -- MENU_MISSION MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) - if MissionMenu == self then self:RemoveSubMenus() if not MenuStamp or self.MenuStamp ~= MenuStamp then @@ -487,10 +428,7 @@ do -- MENU_MISSION return self end - - end - do -- MENU_MISSION_COMMAND --- @type MENU_MISSION_COMMAND @@ -503,7 +441,7 @@ do -- MENU_MISSION_COMMAND -- -- @field #MENU_MISSION_COMMAND MENU_MISSION_COMMAND = { - ClassName = "MENU_MISSION_COMMAND" + ClassName = "MENU_MISSION_COMMAND", } --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. @@ -518,7 +456,6 @@ do -- MENU_MISSION_COMMAND MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) - if MissionMenu then MissionMenu:SetCommandMenuFunction( CommandMenuFunction ) MissionMenu:SetCommandMenuArguments( arg ) @@ -532,17 +469,15 @@ do -- MENU_MISSION_COMMAND return self end end - --- Refreshes a radio item for a mission -- @param #MENU_MISSION_COMMAND self -- @return #MENU_MISSION_COMMAND function MENU_MISSION_COMMAND:Refresh() - do missionCommands.removeItem( self.MenuPath ) missionCommands.addCommand( self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + return self end --- Removes a radio command item for a coalition @@ -553,7 +488,6 @@ do -- MENU_MISSION_COMMAND MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) - if MissionMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then @@ -572,13 +506,8 @@ do -- MENU_MISSION_COMMAND return self end - end - - - do -- MENU_COALITION - --- @type MENU_COALITION -- @extends Core.Menu#MENU_BASE @@ -634,18 +563,15 @@ do -- MENU_COALITION -- @param #MENU_COALITION self -- @param DCS#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). + -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_COALITION self function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) - MENU_INDEX:PrepareCoalition( Coalition ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) - if CoalitionMenu then return CoalitionMenu else - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) MENU_INDEX:SetCoalitionMenu( Coalition, Path, self ) @@ -656,17 +582,15 @@ do -- MENU_COALITION return self end end - --- Refreshes a radio item for a coalition -- @param #MENU_COALITION self -- @return #MENU_COALITION function MENU_COALITION:Refresh() - do missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) missionCommands.addSubMenuForCoalition( self.Coalition, self.MenuText, self.MenuParentPath ) end - + return self end --- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept! @@ -689,7 +613,6 @@ do -- MENU_COALITION MENU_INDEX:PrepareCoalition( self.Coalition ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) - if CoalitionMenu == self then self:RemoveSubMenus() if not MenuStamp or self.MenuStamp ~= MenuStamp then @@ -709,11 +632,7 @@ do -- MENU_COALITION return self end - end - - - do -- MENU_COALITION_COMMAND --- @type MENU_COALITION_COMMAND @@ -742,7 +661,6 @@ do -- MENU_COALITION_COMMAND MENU_INDEX:PrepareCoalition( Coalition ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) - if CoalitionMenu then CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction ) CoalitionMenu:SetCommandMenuArguments( arg ) @@ -757,20 +675,17 @@ do -- MENU_COALITION_COMMAND self:SetParentMenu( self.MenuText, self ) return self end - end - - --- Refreshes a radio item for a coalition -- @param #MENU_COALITION_COMMAND self -- @return #MENU_COALITION_COMMAND function MENU_COALITION_COMMAND:Refresh() - do missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + + return self end --- Removes a radio command item for a coalition @@ -781,7 +696,6 @@ do -- MENU_COALITION_COMMAND MENU_INDEX:PrepareCoalition( self.Coalition ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) - if CoalitionMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then @@ -800,20 +714,16 @@ do -- MENU_COALITION_COMMAND return self end - end - --- MENU_GROUP - do -- This local variable is used to cache the menus registered under groups. - -- Menus don't dissapear when groups for players are destroyed and restarted. + -- Menus don't disappear when groups for players are destroyed and restarted. -- So every menu for a client created must be tracked so that program logic accidentally does not create. -- the same menus twice during initialization logic. -- These menu classes are handling this logic with this variable. local _MENUGROUPS = {} - --- @type MENU_GROUP -- @extends Core.Menu#MENU_BASE @@ -889,16 +799,13 @@ do MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) - if GroupMenu then return GroupMenu else self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) MENU_INDEX:SetGroupMenu( Group, Path, self ) - self.Group = Group self.GroupID = Group:GetID() - self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath ) self:SetParentMenu( self.MenuText, self ) @@ -906,12 +813,10 @@ do end end - --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP self -- @return #MENU_GROUP function MENU_GROUP:Refresh() - do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) @@ -920,7 +825,8 @@ do Menu:Refresh() end end - + + return self end --- Removes the sub menus recursively of this MENU_GROUP. @@ -929,7 +835,6 @@ do -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #MENU_GROUP self function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag ) - for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end @@ -938,18 +843,15 @@ do end - --- Removes the main menu and sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP self -- @param MenuStamp -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #nil function MENU_GROUP:Remove( MenuStamp, MenuTag ) - MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) - if GroupMenu == self then self:RemoveSubMenus( MenuStamp, MenuTag ) if not MenuStamp or self.MenuStamp ~= MenuStamp then @@ -993,18 +895,15 @@ do -- @param CommandMenuArgument An argument for the function. -- @return #MENU_GROUP_COMMAND function MENU_GROUP_COMMAND:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... ) - MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) - if GroupMenu then GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) GroupMenu:SetCommandMenuArguments( arg ) return GroupMenu else self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - MENU_INDEX:SetGroupMenu( Group, Path, self ) self.Group = Group @@ -1015,19 +914,17 @@ do self:SetParentMenu( self.MenuText, self ) return self end - end - --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND self -- @return #MENU_GROUP_COMMAND function MENU_GROUP_COMMAND:Refresh() - do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + + return self end --- Removes a menu structure for a group. @@ -1036,11 +933,9 @@ do -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #nil function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag ) - MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) - if GroupMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then @@ -1059,13 +954,9 @@ do return self end - end - --- MENU_GROUP_DELAYED - do - --- @type MENU_GROUP_DELAYED -- @extends Core.Menu#MENU_BASE @@ -1093,16 +984,13 @@ do MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) - if GroupMenu then return GroupMenu else self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) MENU_INDEX:SetGroupMenu( Group, Path, self ) - self.Group = Group self.GroupID = Group:GetID() - if self.MenuParentPath then self.MenuPath = UTILS.DeepCopy( self.MenuParentPath ) else @@ -1116,12 +1004,10 @@ do end - --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP_DELAYED self -- @return #MENU_GROUP_DELAYED function MENU_GROUP_DELAYED:Set() - do if not self.MenuSet then missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) @@ -1132,15 +1018,12 @@ do Menu:Set() end end - end - --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP_DELAYED self -- @return #MENU_GROUP_DELAYED function MENU_GROUP_DELAYED:Refresh() - do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) @@ -1149,7 +1032,8 @@ do Menu:Refresh() end end - + + return self end --- Removes the sub menus recursively of this MENU_GROUP_DELAYED. @@ -1158,7 +1042,6 @@ do -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #MENU_GROUP_DELAYED self function MENU_GROUP_DELAYED:RemoveSubMenus( MenuStamp, MenuTag ) - for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end @@ -1167,18 +1050,15 @@ do end - --- Removes the main menu and sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP_DELAYED self -- @param MenuStamp -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #nil function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag ) - MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) - if GroupMenu == self then self:RemoveSubMenus( MenuStamp, MenuTag ) if not MenuStamp or self.MenuStamp ~= MenuStamp then @@ -1223,18 +1103,15 @@ do -- @param CommandMenuArgument An argument for the function. -- @return #MENU_GROUP_COMMAND_DELAYED function MENU_GROUP_COMMAND_DELAYED:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... ) - MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) - if GroupMenu then GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) GroupMenu:SetCommandMenuArguments( arg ) return GroupMenu else self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - MENU_INDEX:SetGroupMenu( Group, Path, self ) self.Group = Group @@ -1250,33 +1127,29 @@ do self:SetParentMenu( self.MenuText, self ) return self end - end - --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @return #MENU_GROUP_COMMAND_DELAYED function MENU_GROUP_COMMAND_DELAYED:Set() - do if not self.MenuSet then self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) self.MenuSet = true end end - end --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @return #MENU_GROUP_COMMAND_DELAYED function MENU_GROUP_COMMAND_DELAYED:Refresh() - do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + + return self end --- Removes a menu structure for a group. @@ -1285,11 +1158,9 @@ do -- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set. -- @return #nil function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag ) - MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) - if GroupMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then @@ -1308,6 +1179,4 @@ do return self end - end - From 619cc3c04796e02ac3664bafac4d08777ce7b631 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 24 Apr 2022 19:11:08 +0200 Subject: [PATCH 12/16] various --- Moose Development/Moose/Ops/Awacs.lua | 185 ++++++++++++++---- Moose Development/Moose/Ops/OpsZone.lua | 8 +- Moose Development/Moose/Wrapper/Group.lua | 2 + .../Moose/Wrapper/Positionable.lua | 3 +- 4 files changed, 158 insertions(+), 40 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 63ac06685..29cfff212 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -82,6 +82,8 @@ do -- @field #boolean MonitoringOn -- @field Core.Set#SET_GROUP clientset -- @field Utilities.FiFo#FIFO FlightGroups +-- @field #number PictureInterval Interval in seconds for general picture +-- @field #number PictureTimeStamp Interval timestamp -- @extends Core.Fsm#FSM --- @@ -145,6 +147,8 @@ AWACS = { AwacsReady = false, CatchAllMissions = {}, CatchAllFGs = {}, + PictureInterval = 300, + PictureTimeStamp = 0, } --- @@ -274,7 +278,12 @@ AWACS.THREATLEVEL = { -- @field Core.Menu#MENU_GROUP_COMMAND picture -- @field Core.Menu#MENU_GROUP_COMMAND bogeydope -- @field Core.Menu#MENU_GROUP_COMMAND declare +-- @field Core.Menu#MENU_GROUP tasking -- @field Core.Menu#MENU_GROUP_COMMAND showtask +-- @field Core.Menu#MENU_GROUP_COMMAND judy +-- @field Core.Menu#MENU_GROUP_COMMAND unable +-- @field Core.Menu#MENU_GROUP_COMMAND abort +-- @field Core.Menu#MENU_GROUP_COMMAND commit --- -- @type AWACS.ManagedGroup @@ -481,6 +490,9 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.FlightGroups = FIFO:New() -- Utilities.FiFo#FIFO self.Countactcounter = 0 + self.PictureInterval = 300 -- picture every 5 mins + self.PictureTimeStamp = 0 -- timestamp + local speed = 250 self.SpeedBase = speed self.Speed = UTILS.KnotsToAltKIAS(speed,self.AwacsAngels*1000) @@ -567,7 +579,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ MonitoringData.EscortsShiftChange = false MonitoringData.EscortsStateFG= "unknown" MonitoringData.EscortsStateMission = "unknown" - self.MonitoringOn = true -- #boolean + self.MonitoringOn = false -- #boolean self.MonitoringData = MonitoringData self.CatchAllMissions = {} @@ -717,7 +729,7 @@ function AWACS:_StartEscorts(Shiftchange) local AwacsFG = self.AwacsFG -- Ops.FlightGroup#FLIGHTGROUP local group = AwacsFG:GetGroup() - local mission = AUFTRAG:NewESCORT(group,{x=-100, y=0, z=200},30,{"Air"}) + local mission = AUFTRAG:NewESCORT(group,{x=-100, y=0, z=200},45,{"Air"}) self.CatchAllMissions[#self.CatchAllMissions+1] = mission mission:SetRequiredAssets(self.EscortNumber) @@ -770,7 +782,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) -- Non AWACS does not seem take AWACS CS in DCS Group --group:CommandSetCallsign(CALLSIGN.Aircraft.Pig,self.CallSignNo,2) - AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil) + AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil,"AWACS") --self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) --local text = string.format("%s starting for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") @@ -790,6 +802,8 @@ function AWACS:_StartSettings(FlightGroup,Mission) self.AwacsTimeStamp = timer.getTime() self.EscortsTimeStamp = timer.getTime() + + self.PictureTimeStamp = timer.getTime() self.AwacsReady = true -- set FSM to started @@ -819,7 +833,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) -- Non AWACS does not seem take AWACS CS in DCS Group -- group:CommandSetCallsign(CALLSIGN.Aircraft.Pig,self.CallSignNo,2) - AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil) + AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil,"AWACS") --self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) @@ -861,7 +875,7 @@ function AWACS:ToStringBULLS( Coordinate ) return string.format("Bullseye %03d, %03d",Bearing,Distance) end ---- [Internal] Chnage Bullseye string to be TTS friendly, "Bullseye 021, 16" returns e.g. "Bulls eye 0-2-1, 1-6" +--- [Internal] Chnage Bullseye string to be TTS friendly, "Bullseye 021, 16" returns e.g. "Bulls eye 0 2 1. 1 6" -- @param #AWACS self -- @param #string Text Input text -- @return #string BullseyeBRTTS @@ -869,7 +883,7 @@ function AWACS:ToStringBullsTTS(Text) local text = Text text=string.gsub(text,"Bullseye","Bulls eye") text=string.gsub(text,"%d","%1 ") - text=string.gsub(text,"?," ,",") + text=string.gsub(text,"?," ,"\.") text=string.gsub(text," $","") return text end @@ -882,6 +896,10 @@ end -- @return #boolean CheckedIn -- @return #string CallSign function AWACS:_GetManagedGrpID(Group) + if not Group or not Group:IsAlive() then + self:E(self.lid.."_GetManagedGrpID - Requested Group is not alive!") + return 0,false,"" + end self:T(self.lid.."_GetManagedGrpID for "..Group:GetName()) local GID = 0 local Outcome = false @@ -928,16 +946,32 @@ end -- @param #boolean AO If true this is for AO, else EWR -- @param #string Callsign Callsign to address -- @param #number GID GroupID for comms +-- @param #number MaxEntries Max entries to show +-- @param #boolean IsGeneral Is a general picture, address all stations -- @return #AWACS self -function AWACS:_CreatePicture(AO,Callsign,GID) +function AWACS:_CreatePicture(AO,Callsign,GID,MaxEntries,IsGeneral) self:T(self.lid.."_CreatePicture AO="..tostring(AO).." for "..Callsign.." GID "..GID) - local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup - local group = managedgroup.Group -- Wrapper.Group#GROUP - local groupcoord = group:GetCoordinate() + local managedgroup = nil + local group = nil + local groupcoord = nil + + if IsGeneral then + -- local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + -- local group = managedgroup.Group -- Wrapper.Group#GROUP + -- local groupcoord = group:GetCoordinate() + else + managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + group = managedgroup.Group -- Wrapper.Group#GROUP + groupcoord = group:GetCoordinate() + end + local fifo = self.PictureAO -- Utilities.FiFo#FIFO local maxentries = self.maxspeakentries or 3 + if MaxEntries and MaxEntries>0 and MaxEntries <= 3 then + maxentries = MaxEntries + end local counter = 0 if not AO then @@ -958,9 +992,19 @@ function AWACS:_CreatePicture(AO,Callsign,GID) if cluster and cluster.coordinate then local clustercoord = cluster.coordinate -- Core.Point#COORDINATE --local refBRAA = clustercoord:ToStringBRA(groupcoord) - local refBRAA = clustercoord:ToStringBRAANATO(groupcoord,true) - text = text .. " "..refBRAA.."." - textScreen = textScreen .." "..refBRAA..".\n" + local refBRAA = "" + if IsGeneral then + -- bulls eye reference + refBRAA = self:ToStringBULLS(clustercoord) + refBRAA = self:ToStringBullsTTS(refBRAA) + text = text .. " "..refBRAA.."." + textScreen = textScreen .." "..refBRAA..".\n" + else + -- pilot reference + refBRAA = clustercoord:ToStringBRAANATO(groupcoord,true) + text = text .. " "..refBRAA + textScreen = textScreen .." "..refBRAA.."\n" + end if counter < maxentries then text = text .. " Group" textScreen = textScreen .. text .. "\n" @@ -976,8 +1020,13 @@ function AWACS:_CreatePicture(AO,Callsign,GID) RadioEntry.TextTTS = text RadioEntry.TextScreen = textScreen RadioEntry.GroupID = GID - RadioEntry.IsGroup = managedgroup.IsPlayer - RadioEntry.ToScreen = managedgroup.IsPlayer + if IsGeneral then + RadioEntry.IsGroup = false + RadioEntry.ToScreen = self.debug + else + RadioEntry.IsGroup = managedgroup.IsPlayer + RadioEntry.ToScreen = managedgroup.IsPlayer + end RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1028,13 +1077,20 @@ end --- [Internal] AWACS Menu for Picture -- @param #AWACS self -- @param Wrapper.Group#GROUP Group Group to use +-- @param #boolean IsGeneral General picture if true, address no-one specific -- @return #AWACS self -function AWACS:_Picture(Group) +function AWACS:_Picture(Group,IsGeneral) self:T(self.lid.."_Picture") local text = "" local textScreen = text local GID, Outcome = self:_GetManagedGrpID(Group) - local gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" + local gcallsign = "" + + if IsGeneral then + gcallsign = "All Stations" + else + gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" + end if not self.intel then -- no intel yet! @@ -1051,7 +1107,7 @@ function AWACS:_Picture(Group) return self end - if Outcome then + if Outcome or IsGeneral then -- Pilot is checked in -- get clusters from Intel local clustertable = self.intel:GetClusterTable() @@ -1106,12 +1162,12 @@ function AWACS:_Picture(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreatePicture(true,gcallsign,GID) + self:_CreatePicture(true,gcallsign,GID,3,IsGeneral) self.PictureAO:Clear() end - if clustersAO < 3 and clustersEWR > 0 then + if clustersAO == 0 and clustersEWR > 0 then text = string.format("%s. %s. Picture Early Warning. ",gcallsign, self.callsigntxt) textScreen = string.format("%s. %s. Picture EWR. ",gcallsign, self.callsigntxt) if clustersEWR == 1 then @@ -1131,7 +1187,7 @@ function AWACS:_Picture(Group) RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - --self:_CreatePicture(false,gcallsign,GID) + self:_CreatePicture(false,gcallsign,GID,3,IsGeneral) self.PictureEWR:Clear() end @@ -1369,6 +1425,38 @@ function AWACS:_Declare(Group) return self end +--- [Internal] AWACS Menu for Commit +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_Commit(Group) + self:T(self.lid.."_Commit") + + local GID, Outcome = self:_GetManagedGrpID(Group) + local text = "Commit Not yet implemented" + if Outcome then + --[[ yes, known + + --]] + elseif self.AwacsFG then + -- no, unknown + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = GID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + return self +end + --- [Internal] AWACS Menu for Judy -- @param #AWACS self -- @param Wrapper.Group#GROUP Group Group to use @@ -1558,7 +1646,7 @@ function AWACS:_CheckIn(Group) alphacheckbulls = self:ToStringBullsTTS(alphacheckbulls)-- make tts friendly self.ManagedGrps[self.ManagedGrpID]=managedgroup - text = string.format("%s. %s. Alpha Check %s",managedgroup.CallSign,self.callsigntxt,alphacheckbulls) + text = string.format("%s. %s. Alpha Check. %s",managedgroup.CallSign,self.callsigntxt,alphacheckbulls) self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) @@ -1637,7 +1725,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) alphacheckbulls = self:ToStringBullsTTS(alphacheckbulls)-- make tts friendly self.ManagedGrps[self.ManagedGrpID]=managedgroup - text = string.format("%s. %s. Alpha Check %s",managedgroup.CallSign,self.callsigntxt,alphacheckbulls) + text = string.format("%s. %s. Alpha Check. %s",managedgroup.CallSign,self.callsigntxt,alphacheckbulls) self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) else @@ -1723,11 +1811,14 @@ function AWACS:_SetClientMenus() local picture = MENU_GROUP_COMMAND:New(grp,"Picture",basemenu,self._Picture,self,grp) local bogeydope = MENU_GROUP_COMMAND:New(grp,"Bogey Dope",basemenu,self._BogeyDope,self,grp) local declare = MENU_GROUP_COMMAND:New(grp,"Declare",basemenu,self._Declare,self,grp) + local tasking = MENU_GROUP:New(grp,"Tasking",basemenu) local showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",tasking,self._Showtask,self,grp) - local judy = MENU_GROUP_COMMAND:New(grp,"Judy",tasking,self._Judy,self,grp) + local commit = MENU_GROUP_COMMAND:New(grp,"Commit",tasking,self._Commit,self,grp) local unable = MENU_GROUP_COMMAND:New(grp,"Unable",tasking,self._Unable,self,grp) local abort = MENU_GROUP_COMMAND:New(grp,"Abort",tasking,self._TaskAbort,self,grp) + local judy = MENU_GROUP_COMMAND:New(grp,"Judy",tasking,self._Judy,self,grp):Refresh() + local checkout = MENU_GROUP_COMMAND:New(grp,"Check Out",basemenu,self._CheckOut,self,grp):Refresh() clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure groupname = grp:GetName(), @@ -1737,10 +1828,12 @@ function AWACS:_SetClientMenus() picture = picture, bogeydope = bogeydope, declare = declare, + tasking = tasking, showtask = showtask, judy = judy, unable = unable, abort = abort, + commit=commit, } elseif not clientmenus[grp:GetName()] then -- check in only @@ -1762,11 +1855,14 @@ function AWACS:_SetClientMenus() local picture = MENU_COALITION_COMMAND:New(self.coalition,"Picture",basemenu,self._Picture,self,grp) local bogeydope = MENU_COALITION_COMMAND:New(self.coalition,"Bogey Dope",basemenu,self._BogeyDope,self,grp) local declare = MENU_COALITION_COMMAND:New(self.coalition,"Declare",basemenu,self._Declare,self,grp) + local tasking = MENU_GROUP:New(grp,"Tasking",basemenu) local showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",tasking,self._Showtask,self,grp) - local judy = MENU_GROUP_COMMAND:New(grp,"Judy",tasking,self._Judy,self,grp) + local commit = MENU_GROUP_COMMAND:New(grp,"Commit",tasking,self._Commit,self,grp) local unable = MENU_GROUP_COMMAND:New(grp,"Unable",tasking,self._Unable,self,grp) local abort = MENU_GROUP_COMMAND:New(grp,"Abort",tasking,self._TaskAbort,self,grp) + local judy = MENU_GROUP_COMMAND:New(grp,"Judy",tasking,self._Judy,self,grp):Refresh() + local checkin = MENU_COALITION_COMMAND:New(self.coalition,"Check In",basemenu,self._CheckIn,self,grp) local checkout = MENU_COALITION_COMMAND:New(self.coalition,"Check Out",basemenu,self._CheckOut,self,grp):Refresh() clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure @@ -1779,9 +1875,11 @@ function AWACS:_SetClientMenus() bogeydope = bogeydope, declare = declare, showtask = showtask, + tasking = tasking, judy = judy, unable = unable, abort = abort, + commit = commit, } end end @@ -1949,7 +2047,8 @@ function AWACS:_StartIntel(awacs) local intel = INTEL:New(self.DetectionSet,self.coalition,self.callsigntxt) --intel:SetVerbosity(2) - intel:SetClusterAnalysis(true,self.debug) + intel:SetClusterAnalysis(true,false) + if self.NoHelos then intel:SetFilterCategory({Unit.Category.AIRPLANE}) else @@ -2261,9 +2360,9 @@ function AWACS:_LogStatistics() local text = string.gsub(text,"{","\n") local text = string.gsub(text,"}","") local text = string.gsub(text,"="," = ") - self:T(text) + self:I(text) if self.MonitoringOn then - MESSAGE:New(text,20,"AWACS",true):ToAll() + MESSAGE:New(text,20,"AWACS",false):ToAll() end return self end @@ -2404,7 +2503,7 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo if Tag and Tag ~= "" then BRAText = BRAText .. " "..Tag.."." - BRAText = BRAText .. " "..Tag.."." + TextScreen = TextScreen .. " "..Tag.."." end BRAText = BRAText .. " "..threatsizetext..". "..BRAfromBulls @@ -2413,11 +2512,12 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo if self.ModernEra then -- Platform if ReportingName and ReportingName ~= "Bogey" then - BRAText = BRAText .. " "..ReportingName.."." - TextScreen = TextScreen .. " "..ReportingName.."." + ReportingName = string.gsub(ReportingName,"_"," ") + BRAText = BRAText .. ". "..ReportingName.."." + TextScreen = TextScreen .. ". "..ReportingName.."." end -- High - > 40k feet - local height = contact.group:GetAltitude() + local height = contact.group:GetHeight() local height = UTILS.Round(UTILS.MetersToFeet(height)/1000,0) -- e.g, 25 if height >= 40 then BRAText = BRAText .. " High." @@ -2970,7 +3070,7 @@ function AWACS:onafterStatus(From, Event, To) end if self.debug then - self:T(report:Text()) + self:I(report:Text()) end -- Check on AUFTRAG status for CAP AI @@ -2979,7 +3079,8 @@ function AWACS:onafterStatus(From, Event, To) end else - -- do other stuff + -- do other stuff, AWACS dead? + -- TODO AWACS dead end @@ -2989,15 +3090,15 @@ function AWACS:onafterStatus(From, Event, To) end monitoringdata.AwacsShiftChange = self.ShiftChangeAwacsFlag + if self.AwacsFG then monitoringdata.AwacsStateFG = self.AwacsFG:GetState() end + monitoringdata.AwacsStateMission = self.AwacsMission:GetState() - --monitoringdata.EscortsStateMission = self.Escor monitoringdata.EscortsShiftChange = self.ShiftChangeEscortsFlag monitoringdata.AICAPCurrent = self.AICAPMissions:Count() monitoringdata.AICAPMax = self.MaxAIonCAP - --monitoringdata.Players = self.clientset:CountAlive() monitoringdata.Airwings = self.CAPAirwings:Count() self.MonitoringData = monitoringdata @@ -3006,6 +3107,14 @@ function AWACS:onafterStatus(From, Event, To) self:_LogStatistics() end + local picturetime = timer.getTime() - self.PictureTimeStamp + + if self.AwacsInZone and picturetime > self.PictureInterval then + -- reset timer + self.PictureTimeStamp = timer.getTime() + self:_Picture(nil,true) + end + self:__Status(30) return self @@ -3266,7 +3375,7 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) if self:Is("Running") then -- exit if stopped - self:__CheckRadioQueue(nextcall+3) + self:__CheckRadioQueue(nextcall+2) end return self end @@ -3417,7 +3526,7 @@ AwacsAW2:NewPayload("CAP 2-1",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG. -- Get AWACS started local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("NW Zone"),"Anchor One",255,radio.modulation.AM ) testawacs:SetEscort(2) -testawacs:SetAwacsDetails(CALLSIGN.AWACS.Darkstar,1,300,300,60,20) +testawacs:SetAwacsDetails(CALLSIGN.AWACS.Wizard,1,30,300,243,30) testawacs:SetSRS("E:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010,nil) testawacs:AddCAPAirWing(AwacsAW2) testawacs:__Start(5) diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index c738d9608..84f27dfd7 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -92,9 +92,15 @@ OPSZONE.version="0.3.0" --- Create a new OPSZONE class object. -- @param #OPSZONE self --- @param Core.Zone#ZONE Zone The zone. +-- @param Core.Zone#ZONE Zone The zone. Needs to be a ZONE\_RADIUS (round) zone. Can be passed as ZONE\_AIRBASE or simply as the name of the airbase. -- @param #number CoalitionOwner Initial owner of the coaliton. Default `coalition.side.NEUTRAL`. -- @return #OPSZONE self +-- @usage +-- myopszone = OPSZONE:New(ZONE:FindByName("OpsZoneOne"),coalition.side.RED) -- base zone from the mission editor +-- myopszone = OPSZONE:New(ZONE_RADIUS:New("OpsZoneTwo",mycoordinate:GetVec2(),5000),coalition.side.BLUE) -- radius zone of 5km at a coordinate +-- myopszone = OPSZONE:New(ZONE_RADIUS:New("Batumi")) -- airbase zone from Batumi Airbase, ca 2500m radius +-- myopszone = OPSZONE:New(ZONE_AIRBASE:New("Batumi",6000),coalition.side.BLUE) -- airbase zone from Batumi Airbase, but with a specific radius of 6km +-- function OPSZONE:New(Zone, CoalitionOwner) -- Inherit everything from LEGION class. diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index ce8bf3dee..8098a0526 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1023,6 +1023,8 @@ function GROUP:GetCoordinate() if FirstUnit then local FirstUnitCoordinate = FirstUnit:GetCoordinate() + local Heading = self:GetHeading() + FirstUnitCoordinate.Heading = Heading return FirstUnitCoordinate end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index cd979322b..b0055720f 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -376,7 +376,8 @@ function POSITIONABLE:GetCoordinate() local PositionableVec3 = self:GetVec3() local coord=COORDINATE:NewFromVec3(PositionableVec3) - + local heading = self:GetHeading() + coord.Heading = heading -- Return a new coordiante object. return coord From 71fd72e4627fd065626bc2a7f51bff2e25ff4078 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 25 Apr 2022 09:59:38 +0200 Subject: [PATCH 13/16] adding ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) (might be called from POSITIONABLE), closes gap for GROUP/UNIT:IsInZone(Zone_Polygon) GROUP - better docu --- Moose Development/Moose/Core/Zone.lua | 12 ++++++++++++ Moose Development/Moose/Wrapper/Group.lua | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index e7359808f..de5b91b97 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1989,6 +1989,18 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) return InPolygon end +--- Returns if a point is within the zone. +-- @param #ZONE_POLYGON_BASE self +-- @param DCS#Vec3 Vec3 The point to test. +-- @return #boolean true if the point is within the zone. +function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) + self:F2( Vec3 ) + + local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) + + return InZone +end + --- Define a random @{DCS#Vec2} within the zone. -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The Vec2 coordinate. diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 8098a0526..8127ffba9 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -908,8 +908,8 @@ function GROUP:GetTypeName() return nil end ---- [AIRPLANE] Get the NATO reporting name of a UNIT. Currently airplanes only! ---@param #UNIT self +--- [AIRPLANE] Get the NATO reporting name (platform, e.g. "Flanker") of a GROUP (note - first unit the group). "Bogey" if not found. Currently airplanes only! +--@param #GROUP self --@return #string NatoReportingName or "Bogey" if unknown. function GROUP:GetNatoReportingName() self:F2( self.GroupName ) @@ -1087,7 +1087,7 @@ end -- amount of fuel in the group. -- @param #GROUP self -- @return #number The fuel state of the unit with the least amount of fuel. --- @return #Unit reference to #Unit object for further processing. +-- @return Wrapper.Unit#UNIT reference to #Unit object for further processing. function GROUP:GetFuelMin() self:F3(self.ControllableName) From de0bf7d814bcbef4715e44d17060f7af6bfdb2e0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 25 Apr 2022 10:36:10 +0200 Subject: [PATCH 14/16] typo --- Moose Development/Moose/Utilities/Enums.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index bdcea66a8..423bd85e9 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -11,7 +11,7 @@ -- -- DCS itself provides a lot of enumerators for various things. See [Enumerators](https://wiki.hoggitworld.com/view/Category:Enumerators) on Hoggit. -- --- Other Moose classe also have enumerators. For example, the AIRBASE class has enumerators for airbase names. +-- Other Moose classes also have enumerators. For example, the AIRBASE class has enumerators for airbase names. -- -- @module ENUMS -- @image MOOSE.JPG From a4f1c60da280045219ae90f3afc259fe5a40b7a0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 26 Apr 2022 10:09:05 +0200 Subject: [PATCH 15/16] Enums - added a couple of names --- Moose Development/Moose/Utilities/Enums.lua | 77 +++++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 423bd85e9..21d01762a 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -458,7 +458,7 @@ ENUMS.ReportingName = { NATO = { -- Fighters - Dragon = "JF-17", -- China, correct? + Dragon = "JF-17", -- China, correctly Fierce Dragon, Thunder for PAC Fagot = "MiG-15", Farmer = "MiG-19", -- Shenyang J-6 and Mikoyan-Gurevich MiG-19 Felon = "Su-57", @@ -475,15 +475,54 @@ ENUMS.ReportingName = Flanker_C = "Su-30", Flanker_E = "Su-35", Flanker_F = "Su-37", - Flanker_Dragon = "J-11A", + Flanker_L = "J-11A", + Firebird = "J-10", Sea_Flanker = "Su-33", - Fullback = "Su-32", -- also Su-34 + Fullback = "Su-34", -- also Su-32 Frogfoot = "Su-25", Tomcat = "F-14", -- Iran Mirage = "Mirage", -- various non-NATO - -- Bomber - H6J = "H6-J", - Sea_Bear = "Tu-142", -- also Tu-95 + Codling = "Yak-40", + Maya = "L-39", + -- Fighters US/NATO + Warthog = "A-10", + --Mosquito = "A-20", + Skyhawk = "A-4E", + Viggen = "AJS37", + Harrier = "AV-8B", + Spirit = "B-2", + Aviojet = "C-101", + Nighthawk = "F-117A", + Eagle = "F-15C", + Mudhen = "F-15E", + Viper = "F-16", + Phantom = "F-4E", + Tiger = "F-5", -- was thinkg to name this MiG-25 ;) + Sabre = "F-86", + Hornet = "A-18", -- avoiding the slash + Hawk = "Hawk", + Albatros = "L-39", + Goshawk = "T-45", + Starfighter = "F-104", + Tornado = "Tornado", + -- Transport / Bomber / Others + Atlas = "A400", + Lancer = "B1-B", + Stratofortress = "B-52H", + Hercules = "C-130", -- modded version has type name "Hercules", unfortunately + Globemaster = "C-17", + Greyhound = "C-2A", + Galaxy = "C-5", + Hawkexe = "E-2D", + Sentry = "E-3A", + Stratotanker = "KC-135", + Extender = "KC-10", + Orion = "P-3C", + Viking = "S-3B", + Osprey = "V-22", + -- Bomber Rus + Badger = "H6-J", + Bear_J = "Tu-142", -- also Tu-95 Bear = "Tu-95", -- also Tu-142 Blinder = "Tu-22", Blackjack = "Tu-160", @@ -492,6 +531,30 @@ ENUMS.ReportingName = Curl = "An-26", Candid = "IL-76", Midas = "IL-78", - Mainstay = "A-50", -- KJ-2000 China + Mainstay = "A-50", + Mainring = "KJ-2000", -- A-50 China + Yak = "Yak-52", + -- Helos + Helix = "Ka-27", + Shark = "Ka-50", + Hind = "Mi-24", + Halo = "Mi-26", + Hip = "Mi-8", + Havoc = "Mi-28", + Gazelle = "SA342", + -- Helos US + Huey = "UH-1H", + Cobra = "AH-1", + Apache = "AH-64", + Chinook = "CH-47", + Sea_Stallion = "CH-53", + Kiowa = "OH-58", + Seahawk = "SH-60", + Blackhawk = "UH-60", + Sea_King = "S-61", + -- Drones + UCAV = "WingLoong", + Reaper = "MQ-9", + Predator = "MQ-1A", } } From 3a513da9424acf66d392bde04c2eddb39775a991 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 27 Apr 2022 14:37:38 +0200 Subject: [PATCH 16/16] Awacs next Alpha --- Moose Development/Moose/Core/Point.lua | 2 +- Moose Development/Moose/Ops/Auftrag.lua | 4 +- Moose Development/Moose/Ops/Awacs.lua | 1323 ++++++++++++++++++----- 3 files changed, 1084 insertions(+), 245 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 36f467284..053c86560 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2752,7 +2752,7 @@ do -- COORDINATE return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings ) end - --- Return a BRAA string from a COORDINATE to the COORDINATE. + --- Return a BRA string from a COORDINATE to the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. -- @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. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 87c481c84..f39ca2010 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -3150,9 +3150,9 @@ function AUFTRAG:IsSuccess() return self.status==AUFTRAG.Status.SUCCESS end ---- Check if mission is over. This could be state DONE or CANCELLED. +--- Check if mission is over. This could be state DONE, CANCELLED, SUCCESS, FAILED. -- @param #AUFTRAG self --- @return #boolean If true, mission is currently executing. +-- @return #boolean If true, mission is over. function AUFTRAG:IsOver() local over = self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 29cfff212..e6a5bb728 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -31,6 +31,7 @@ do -- @field #string coalitiontxt = "blue" -- @field Core.Zone#ZONE OpsZone, -- @field Core.Zone#ZONE AnchorZone, +-- @field Core.Zone#ZONE BorderZone, -- @field #number Frequency -- @field #number Modulation -- @field Wrapper.Airbase#AIRBASE Airbase @@ -91,7 +92,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "alpha 0.0.8", -- #string + version = "alpha 0.0.10", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -131,7 +132,7 @@ AWACS = { AwacsROE = "", AwacsROT = "", MenuStrict = true, - MaxAIonCAP = 4, + MaxAIonCAP = 3, AIonCAP = 0, AICAPMissions = {}, -- Utilities.FiFo#FIFO ShiftChangeAwacsFlag = false, @@ -149,6 +150,7 @@ AWACS = { CatchAllFGs = {}, PictureInterval = 300, PictureTimeStamp = 0, + BorderZone = nil, } --- @@ -272,18 +274,18 @@ AWACS.THREATLEVEL = { -- @type AWACS.MenuStructure -- @field #boolean menuset -- @field #string groupname --- @field Core.Menu#MENU_GROUP basemenu --- @field Core.Menu#MENU_GROUP_COMMAND checkin --- @field Core.Menu#MENU_GROUP_COMMAND checkout --- @field Core.Menu#MENU_GROUP_COMMAND picture --- @field Core.Menu#MENU_GROUP_COMMAND bogeydope --- @field Core.Menu#MENU_GROUP_COMMAND declare --- @field Core.Menu#MENU_GROUP tasking --- @field Core.Menu#MENU_GROUP_COMMAND showtask --- @field Core.Menu#MENU_GROUP_COMMAND judy --- @field Core.Menu#MENU_GROUP_COMMAND unable --- @field Core.Menu#MENU_GROUP_COMMAND abort --- @field Core.Menu#MENU_GROUP_COMMAND commit +-- @field Core.Menu#MENU_GROUP_DELAYED basemenu +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED checkin +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED checkout +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED picture +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED bogeydope +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED declare +-- @field Core.Menu#MENU_GROUP_DELAYED tasking +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED showtask +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED judy +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED unable +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED abort +-- @field Core.Menu#MENU_GROUP_COMMAND_DELAYED commit --- -- @type AWACS.ManagedGroup @@ -293,8 +295,8 @@ AWACS.THREATLEVEL = { -- @field #boolean IsPlayer -- @field #boolean IsAI -- @field #string CallSign --- @field #number CurrentAuftrag --- @field #number CurrentTask +-- @field #number CurrentAuftrag -- Auftragsnummer for AI +-- @field #number CurrentTask -- ManagedTask ID -- @field #boolean HasAssignedTask -- @field #number GID -- @field #number AnchorStackNo @@ -313,6 +315,7 @@ AWACS.THREATLEVEL = { -- @field #number LinkedGroup --> GID -- @field #string Status - AWACS.TaskStatus.... -- @field #string TargetGroupNaming +-- @field #string EngagementTag --- -- @type AWACS.TaskDescription @@ -362,9 +365,9 @@ AWACS.TaskStatus = { --- -- @type AWACS.AnchorData -- @field #number AnchorBaseAngels --- @field #boolean AnchorZone +-- @field Core.Zone#ZONE_RADIUS AnchorZone -- @field Core.Point#COORDINATE AnchorZoneCoordinate --- @field #boolean AnchorZoneCoordinateText +-- @field #string AnchorZoneCoordinateText -- @field Utilities.FiFo#FIFO AnchorAssignedID FiFo of #AWACS.AnchorAssignedEntry -- @field Utilities.FiFo#FIFO Anchors FiFo of available stacks @@ -380,21 +383,26 @@ AWACS.TaskStatus = { --@field #boolean FromAI ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO-List 0.0.8 +-- TODO-List 0.0.9 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- DEBUG - Escorts via AirWing not staying on +-- -- TODO - System for Players to VID contacts? And put data into contacst fifo -- TODO - TripWire --- DEBUG - Multiple AIRWING connection? Can't really get recruit to work, switched to random round robin --- TODO - LotATC / IFF --- TODO - Player & AI tasking --- TODO - (WIP) Reporting +-- +-- TODO - (LOW) LotATC / IFF +-- TODO - Player tasking +-- TODO - AI Tasking -- TODO - Missile launch callout -- TODO - Localization --- DEBUG - Shift Change, Change on asset RTB or dead or mission done --- TODO - Borders for INTEL. Necessary? +-- -- TODO - Event detection, Player joining, eject, crash, dead, leaving; AI shot -> DEFEND --- +-- TODO - Optimizer +-- +-- DEBUG - Multiple AIRWING connection? Can't really get recruit to work, switched to random round robin +-- DEBUG - Shift Change, Change on asset RTB or dead or mission done (done for AWACS and Escorts) +-- +-- DONE - Escorts via AirWing not staying on +-- DONE - Borders for INTEL. Optional, i.e. land based defense within borders -- DONE - Use AO as Anchor of Bulls, AO as default -- DONE - SRS TTS output -- DONE - Check-In/Out Humans @@ -407,6 +415,7 @@ AWACS.TaskStatus = { -- DONE - ROE -- DONE - Anchor Stack Management -- DONE - Shift Length AWACS/AI +-- DONE - (WIP) Reporting ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -473,13 +482,14 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.AOCoordinate = self.OpsZone:GetCoordinate() self.UseBullsAO = false -- as per NATOPS - self.ControlZoneRadius = 120 -- nm + self.ControlZoneRadius = 100 -- nm self.AnchorZone = ZONE:New(AnchorZone) -- Core.Zone#ZONE self.Frequency = Frequency or 271 -- #number self.Modulation = Modulation or radio.modulation.AM self.Airbase = AIRBASE:FindByName(AirbaseName) self.AwacsAngels = 25 -- orbit at 25'000 ft self.OrbitZone = ZONE:New(AwacsOrbit) -- Core.Zone#ZONE + self.BorderZone = nil self.CallSign = CALLSIGN.AWACS.Magic -- #number self.CallSignNo = 1 -- #number self.NoHelos = true @@ -490,17 +500,19 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.FlightGroups = FIFO:New() -- Utilities.FiFo#FIFO self.Countactcounter = 0 - self.PictureInterval = 300 -- picture every 5 mins + self.PictureInterval = 300 -- picture every 5s mins self.PictureTimeStamp = 0 -- timestamp + self.intelstarted = false + local speed = 250 self.SpeedBase = speed self.Speed = UTILS.KnotsToAltKIAS(speed,self.AwacsAngels*1000) self.CapSpeedBase = 220 self.Heading = 0 -- north self.Leg = 50 -- nm - self.invisible = true - self.immortal = true + self.invisible = false + self.immortal = false self.callsigntxt = "AWACS" self.AwacsTimeOnStation = 2 @@ -589,7 +601,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.PictureAO = FIFO:New() -- Utilities.FiFo#FIFO self.PictureEWR = FIFO:New() -- Utilities.FiFo#FIFO self.Contacts = FIFO:New() -- Utilities.FiFo#FIFO - self.ManagedContacts = FIFO:New() + --self.ManagedContacts = FIFO:New() self.CID = 0 self.ContactsAO = FIFO:New() -- Utilities.FiFo#FIFO @@ -611,6 +623,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self:AddTransition("*", "CheckedOut", "*") self:AddTransition("*", "AssignAnchor", "*") self:AddTransition("*", "AssignedAnchor", "*") + self:AddTransition("*", "ReAnchor", "*") self:AddTransition("*", "NewCluster", "*") self:AddTransition("*", "NewContact", "*") self:AddTransition("*", "LostCluster", "*") @@ -619,6 +632,9 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self:AddTransition("*", "EscortShiftChange", "*") self:AddTransition("*", "AwacsShiftChange", "*") self:AddTransition("*", "FlightOnMission", "*") + self:AddTransition("*", "Intercept", "*") + self:AddTransition("*", "InterceptSuccess", "*") + self:AddTransition("*", "InterceptFailure", "*") -- self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -653,6 +669,20 @@ function AWACS:GetName() return self.Name or "not set" end +--- [User] Set Border Zone +-- @param #AWACS self +-- @param Core.Zone#ZONE Zone +-- @return #AWACS self +function AWACS:SetBorderZone(Zone) + self:T(self.lid.."SetBorderZone") + 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(),"Border Zone"):ToAll() + end + return self +end + --- [User] Set AWACS flight details -- @param #AWACS self -- @param #number CallSign Defaults to CALLSIGN.AWACS.Magic @@ -720,12 +750,55 @@ function AWACS:SetEscort(EscortNumber) return self end +--- [Internal] Message a vector BR to a position +-- @param #AWACS self +-- @param #number GID Group GID +-- @param #string Tag (optional) Text to add after Vector, e.g. " to Anchor" - NOTE the leading space +-- @param Core.Point#COORDINATE Coordinate The Coordinate to use +-- @param #number Angels (Optional) Add Angels +-- @return #AWACS self +function AWACS:_MessageVector(GID,Tag,Coordinate,Angels) + self:I(self.lid.."_MessageVector") + + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + local Tag = Tag or "" + + if managedgroup and Coordinate then + + local tocallsign = managedgroup.CallSign or "Ghost 1 1" + local group = managedgroup.Group + local groupposition = group:GetCoordinate() + + local BRtext = Coordinate:ToStringBR(groupposition) + + local text = string.format("%s, %s. Vector%s. %s",tocallsign, self.callsigntxt,Tag,BRtext) + local textScreen = string.format("%s, %s, Vector%s %s",tocallsign, self.callsigntxt,Tag,BRtext) + + if Angels then + text = text .. ". Angels "..tostring(Angels).."." + textScreen = textScreen .. ". Angels "..tostring(Angels).."." + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = 0 + RadioEntry.ToScreen = self.debug + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 + self.RadioQueue:Push(RadioEntry) + + end + + return self +end + --- [Internal] Start AWACS Escorts FlightGroup -- @param #AWACS self -- @param #boolean Shiftchange This is a shift change call -- @return #AWACS self function AWACS:_StartEscorts(Shiftchange) - self:I(self.lid.."_StartEscorts") + self:T(self.lid.."_StartEscorts") local AwacsFG = self.AwacsFG -- Ops.FlightGroup#FLIGHTGROUP local group = AwacsFG:GetGroup() @@ -761,7 +834,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) -- Is this our Awacs mission? if self.AwacsMission:GetName() == Mission:GetName() then - self:I("Setting up Awacs") + self:T("Setting up Awacs") AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) AwacsFG:SwitchRadio(self.Frequency,self.Modulation) AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) @@ -803,7 +876,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) self.AwacsTimeStamp = timer.getTime() self.EscortsTimeStamp = timer.getTime() - self.PictureTimeStamp = timer.getTime() + self.PictureTimeStamp = timer.getTime() + 10*60 self.AwacsReady = true -- set FSM to started @@ -865,7 +938,8 @@ end -- @param Core.Point#COORDINATE Coordinate -- @return #string BullseyeBR function AWACS:ToStringBULLS( Coordinate ) - local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.coalition ) ) + -- local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.coalition ) ) + local BullsCoordinate = self.OpsZone:GetCoordinate() local DirectionVec3 = BullsCoordinate:GetDirectionVec3( Coordinate ) local AngleRadians = Coordinate:GetAngleRadians( DirectionVec3 ) local Distance = Coordinate:Get2DDistance( BullsCoordinate ) @@ -883,7 +957,7 @@ function AWACS:ToStringBullsTTS(Text) local text = Text text=string.gsub(text,"Bullseye","Bulls eye") text=string.gsub(text,"%d","%1 ") - text=string.gsub(text,"?," ,"\.") + text=string.gsub(text,".," ,"\.") text=string.gsub(text," $","") return text end @@ -923,7 +997,7 @@ end -- @param #number GID GID to use -- @return #string Callsign function AWACS:_GetCallSign(Group,GID) - self:I(self.lid.."_GetCallSign - GID "..tostring(GID)) + self:T(self.lid.."_GetCallSign - GID "..tostring(GID)) if GID and type(GID) == "number" and GID > 0 then local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -937,10 +1011,246 @@ function AWACS:_GetCallSign(Group,GID) local callnumbermajor = string.char(string.byte(callnumber,1)) local callnumberminor = string.char(string.byte(callnumber,2)) callsign = string.gsub(shortcallsign,callnumber,"").." "..callnumbermajor.." "..callnumberminor - self:I("Generated Callsign for TTS = " .. callsign) + self:T("Generated Callsign for TTS = " .. callsign) return callsign end +function AWACS:_CleanUpContacts() + self:I(self.lid.."_CleanUpContacts") + + if self.Contacts:Count() > 0 then + local deadcontacts = FIFO:New() + self.Contacts:ForEach( + function (Contact) + local contact = Contact -- #AWACS.ManagedContact + if not contact.Contact.group:IsAlive() then + deadcontacts:Push(contact,contact.CID) + end + end + ) + -- announce VANISHED + if deadcontacts:Count() > 0 then + -- announce lost + deadcontacts:ForEach( + function (Contact) + local contact = Contact -- #AWACS.ManagedContact + local text = string.format("%s, %s Group. Vanished.",self.callsigntxt, contact.TargetGroupNaming) + local textScreen = string.format("%s, %s group vanished.", self.callsigntxt, contact.TargetGroupNaming) + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = 0 + RadioEntry.ToScreen = self.debug + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 + self.RadioQueue:Push(RadioEntry) + + -- pull from Contacts + self.Contacts:PullByID(contact.CID) + end + ) + end + end + return self +end + +--- [Internal] Select pilots available for tasking, return AI and Human +-- @param #AWACS self +-- @return #table AIPilots Table of #AWACS.ManagedGroup +-- @return #table HumanPilots Table of #AWACS.ManagedGroup +function AWACS:_GetIdlePilots() + self:T(self.lid.."_GetIdlePilots") + local AIPilots = {} + local HumanPilots = {} + + for _name,_entry in pairs (self.ManagedGrps) do + local entry = _entry -- #AWACS.ManagedGroup + self:T("Looking at entry "..entry.GID.." Name "..entry.GroupName) + local managedtask = self:_ReadAssignedTaskFromGID(entry.GID) -- #AWACS.ManagedTask + local overridetask = false + if managedtask then + self:T("Current task = "..managedtask.ToDo or "Unknown") + if managedtask.ToDo == AWACS.TaskDescription.ANCHOR then + overridetask = true + end + end + if entry.IsAI then + if entry.FlightGroup:IsAirborne() and (not entry.HasAssignedTask or overridetask) then -- must be idle, or? + self:T("Adding AI with Callsign: "..entry.CallSign) + AIPilots[#AIPilots+1] = _entry + end + elseif entry.IsPlayer then + if not entry.HasAssignedTask or overridetask then -- must be idle, or? + self:T("Adding Human with Callsign: "..entry.CallSign) + HumanPilots[#HumanPilots+1] = _entry + end + end + end + + return AIPilots, HumanPilots + +end + +--- [Internal] Select max 3 targets for picture, bogey dope etc +-- @param #AWACS self +-- @param #boolean Untargeted Return not yet targeted contacts only +-- @return #boolean HaveTargets True if targets could be found, else false +-- @return Utilities.FiFo#FIFO Targetselection +function AWACS:_TargetSelectionProcess(Untargeted) + self:I(self.lid.."_TargetSelectionProcess") + + local maxtargets = 3 -- handleable number of callouts + local contactstable = self.Contacts:GetDataTable() + local targettable = FIFO:New() + local sortedtargets = FIFO:New() + local HaveTargets = false + + -- Bucket sort + + if Untargeted then + -- pre-filter + local prefiltered = FIFO:New() + self.Contacts:ForEach( + function (Contact) + local contact = Contact -- #AWACS.ManagedContact + if contact.Contact.group:IsAlive() and (contact.Status == AWACS.TaskStatus.IDLE or contact.Status == AWACS.TaskStatus.UNASSIGNED) then + prefiltered:Push(contact,contact.CID) + end + end + ) + contactstable = prefiltered:GetDataTable() + --self:T(prefiltered:Flush()) + prefiltered:Clear() + end + + -- Bucket 1 - close to AIC (HVT) ca ~45nm + local HVTCoordinate = self.OrbitZone:GetCoordinate() + for _,_contact in pairs(contactstable) do + local contact = _contact -- #AWACS.ManagedContact + -- get distance + local contactcoord = contact.Contact.group:GetCoordinate() + local distance = UTILS.NMToMeters(200) + if contactcoord then + distance = HVTCoordinate:Get2DDistance(contactcoord) + end + if UTILS.MetersToNM(distance) <= 45 then + targettable:Push(contact,distance) + sortedtargets:Push(contact,contact.CID) + end -- end distance + end -- end for + + -- Bucket 2 - in AO + if targettable:Count() < 3 then + for _,_contact in pairs(contactstable) do + local contact = _contact -- #AWACS.ManagedContact + -- get distance + local contactcoord = contact.Contact.group:GetCoordinate() + local distance = UTILS.NMToMeters(200) + if contactcoord then + distance = self.OpsZone:Get2DDistance(contactcoord) + end + if contactcoord and self.OpsZone:IsVec2InZone(contactcoord:GetVec2()) then + -- TODO prefer heavy groups + local groupsize = contact.Contact.group:CountAliveUnits() + local threatlevel = contact.Contact.group:GetThreatLevel() + if groupsize >= 3 then + local threat = groupsize*threatlevel + distance = math.abs(math.floor(100-threat)) + end -- end grpsize + if not sortedtargets:HasUniqueID(contact.CID) then + targettable:Push(contact,distance) + end -- end unique + end -- end in zone + end -- end for + end -- end count + + if targettable:Count() < 3 then + -- Bucket 3 - in Radar(Control)Zone, < 100nm to AO, Aspect HOT on AO + for _,_contact in pairs(contactstable) do + local contact = _contact -- #AWACS.ManagedContact + -- get distance + local contactcoord = contact.Contact.group:GetCoordinate() + local distance = UTILS.NMToMeters(200) + if contactcoord then + distance = self.ControlZone:Get2DDistance(contactcoord) + end + if contactcoord and self.ControlZone:IsVec2InZone(contactcoord:GetVec2()) then + if not contactcoord.Heading then + contactcoord.Heading = contact.Contact.group:GetHeading() + end -- end heading + if contactcoord:ToStringAspect(self.ControlZone:GetCoordinate()) == "Hot" then + -- TODO prefer heavy groups + local groupsize = contact.Contact.group:CountAliveUnits() + local threatlevel = contact.Contact.group:GetThreatLevel() + if groupsize >= 3 then + local threat = groupsize*threatlevel + distance = math.abs(math.floor(100-threat)) + end -- end sizing + if not sortedtargets:HasUniqueID(contact.CID) then + targettable:Push(contact,distance) + end -- end unique + end -- end aspect + end -- end in zone + end -- end for + end -- end count + + -- Bucket 4 (if set) within the border polyzone to be defended + if targettable:Count() < 3 and self.BorderZone then + for _,_contact in pairs(contactstable) do + local contact = _contact -- #AWACS.ManagedContact + -- get distance + local contactcoord = contact.Contact.group:GetCoordinate() + local distance = UTILS.NMToMeters(200) + if contactcoord then + distance = self.BorderZone:Get2DDistance(contactcoord) + end + --local distance = self.BorderZone:Get2DDistance(contactcoord) + if contactcoord and self.BorderZone:IsVec2InZone(contactcoord:GetVec2()) then + -- TODO prefer heavy groups + local groupsize = contact.Contact.group:CountAliveUnits() + local threatlevel = contact.Contact.group:GetThreatLevel() + if groupsize >= 3 then + local threat = groupsize*threatlevel + distance = math.abs(math.floor(100-threat)) + end -- end size + if not sortedtargets:HasUniqueID(contact.CID) then + targettable:Push(contact,distance) + end -- end unique + end -- end in zone + end -- end for + end -- end count + + -- control + if self.debug then + self:I(string.format("%s**** TargetSelectionProcess outcome %d targets.",self.lid,targettable:Count())) + --targettable:Flush() + if not self.TargetMarkers then + self.TargetMarkers = {} + end + for _,_marker in pairs(self.TargetMarkers) do + local marker = _marker -- Wrapper.Marker#MARKER + marker:Remove(1) + self.TargetMarkers = nil + self.TargetMarkers = {} + end + for _,_contact in pairs(targettable:GetDataTable()) do + local contact = _contact -- #AWACS.ManagedContact + local markerinfo = string.format("CID=%d\nName=%s\nPlatform=%s\nSize=%d",contact.CID,contact.TargetGroupNaming,contact.ReportingName,contact.Contact.group:CountAliveUnits()) + local markercoord = contact.Contact.group:GetCoordinate() + self.TargetMarkers[#self.TargetMarkers+1] = MARKER:New(markercoord,markerinfo):ToAll(1) + end + end + + sortedtargets:Clear() + --TODO - sort table - but max 3 anyway? + + if targettable:Count() > 0 then + HaveTargets = true + end + + return HaveTargets, targettable +end + --- [Internal] AWACS Speak Picture AO/EWR entries -- @param #AWACS self -- @param #boolean AO If true this is for AO, else EWR @@ -950,87 +1260,121 @@ end -- @param #boolean IsGeneral Is a general picture, address all stations -- @return #AWACS self function AWACS:_CreatePicture(AO,Callsign,GID,MaxEntries,IsGeneral) - self:T(self.lid.."_CreatePicture AO="..tostring(AO).." for "..Callsign.." GID "..GID) + self:I(self.lid.."_CreatePicture AO="..tostring(AO).." for "..Callsign.." GID "..GID) local managedgroup = nil local group = nil local groupcoord = nil - if IsGeneral then - -- local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup - -- local group = managedgroup.Group -- Wrapper.Group#GROUP - -- local groupcoord = group:GetCoordinate() - else + if not IsGeneral then managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup group = managedgroup.Group -- Wrapper.Group#GROUP groupcoord = group:GetCoordinate() end - local fifo = self.PictureAO -- Utilities.FiFo#FIFO + local maxentries = self.maxspeakentries or 3 + if MaxEntries and MaxEntries>0 and MaxEntries <= 3 then maxentries = MaxEntries end + local counter = 0 if not AO then - fifo = self.PictureEWR + -- fifo = self.PictureEWR end local entries = fifo:GetSize() if entries < maxentries then maxentries = entries end - local text = "Group" - local textScreen = text + local text = "" + local textScreen = "" + + -- " group, , BRA, for at angels , " while counter < maxentries do counter = counter + 1 - local cluster = fifo:Pull() -- Ops.Intelligence#INTEL.Cluster - self:T({cluster}) - if cluster and cluster.coordinate then - local clustercoord = cluster.coordinate -- Core.Point#COORDINATE - --local refBRAA = clustercoord:ToStringBRA(groupcoord) + local contact = fifo:Pull() -- #AWACS.ManagedContact + self:T({contact}) + if contact and contact.Contact.group and contact.Contact.group:IsAlive() then + local coordinate = contact.Contact.group:GetCoordinate() + if not coordinate.Heading then + coordinate.Heading = contact.Contact.group:GetHeading() + end local refBRAA = "" + text = contact.TargetGroupNaming.." group." -- Alpha Group. + textScreen = contact.TargetGroupNaming.." group," + --self:I("Step 1") + --self:I(text) + --self:I(textScreen) + -- sizing + local size = contact.Contact.group:CountAliveUnits() + local threatsize, threatsizetext = self:_GetBlurredSize(size) + --local threatlevel = contact.threatlevel + --local threattext = self:_GetThreatLevelText(threatlevel) + text = text.." "..threatsizetext.."." -- Alpha Group. Heavy. + textScreen = textScreen.." "..threatsizetext.."," + --self:I("Step 2") + --self:I(text) + --self:I(textScreen) if IsGeneral then - -- bulls eye reference - refBRAA = self:ToStringBULLS(clustercoord) - refBRAA = self:ToStringBullsTTS(refBRAA) - text = text .. " "..refBRAA.."." - textScreen = textScreen .." "..refBRAA..".\n" + -- AO/BE Reference + refBRAA=self:ToStringBULLS(coordinate) + text = text .. " "..self:ToStringBullsTTS(refBRAA).."." -- Alpha Group. Heavy. Bulls eye 0 2 1, 1 6. + textScreen = textScreen .. " "..refBRAA.."," -- Alpha Group, Heavy, Bullseye 021, 16, + --self:I("Step 2 - General") + --self:I(text) + --self:I(textScreen) else -- pilot reference - refBRAA = clustercoord:ToStringBRAANATO(groupcoord,true) + refBRAA = coordinate:ToStringBRAANATO(groupcoord,true) -- Charlie group, Singleton, BRAA, 045, 105 miles, Angels 41, Flanking, Track North-East, Spades. text = text .. " "..refBRAA - textScreen = textScreen .." "..refBRAA.."\n" + textScreen = textScreen .." "..refBRAA + --self:I("Step 2 - Pilot") + --self:I(text) + --self:I(textScreen) end - if counter < maxentries then - text = text .. " Group" - textScreen = textScreen .. text .. "\n" + -- Aspect + local aspect = "" + if IsGeneral then + aspect = coordinate:ToStringAspect(self.OpsZone:GetCoordinate()) + text = text .. " "..aspect.."." -- Alpha Group. Heavy. Bulls eye 0 2 1, 1 6. Flanking. + textScreen = textScreen .. " "..aspect.."." -- Alpha Group, Heavy, Bullseye 021, 16, Flanking. + --self:I("Step 3 - General") + --self:I(text) + --self:I(textScreen) end + -- engagement tag? + if contact.EngagementTag then + text = text .. " "..contact.EngagementTag -- Alpha Group. Heavy. Bulls eye 0 2 1, 1 6. Flanking. Targeted by Jazz 1 1. + textScreen = textScreen .. " "..contact.EngagementTag -- Alpha Group, Heavy, Bullseye 021, 16, Flanking. Targeted by Jazz 1 1. + end + + -- Transmit Radio + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = GID + if IsGeneral then + RadioEntry.IsGroup = false + RadioEntry.ToScreen = self.debug + else + RadioEntry.IsGroup = managedgroup.IsPlayer + RadioEntry.ToScreen = managedgroup.IsPlayer + end + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 + + self.RadioQueue:Push(RadioEntry) end end -- empty queue from leftovers fifo:Clear() - - local RadioEntry = {} -- #AWACS.RadioEntry - RadioEntry.IsNew = true - RadioEntry.TextTTS = text - RadioEntry.TextScreen = textScreen - RadioEntry.GroupID = GID - if IsGeneral then - RadioEntry.IsGroup = false - RadioEntry.ToScreen = self.debug - else - RadioEntry.IsGroup = managedgroup.IsPlayer - RadioEntry.ToScreen = managedgroup.IsPlayer - end - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 - - self.RadioQueue:Push(RadioEntry) - + return self end @@ -1063,7 +1407,7 @@ function AWACS:_CreateBogeyDope(Callsign,GID) if cluster and cluster.position then local tag = cluster.TargetGroupNaming local reportingname = cluster.group:GetNatoReportingName() - -- TODO - add tag + -- DONE - add tag self:_AnnounceContact(cluster,false,group,true,tag,false,reportingname) end end @@ -1083,13 +1427,17 @@ function AWACS:_Picture(Group,IsGeneral) self:T(self.lid.."_Picture") local text = "" local textScreen = text - local GID, Outcome = self:_GetManagedGrpID(Group) - local gcallsign = "" - + local GID, Outcome, gcallsign = self:_GetManagedGrpID(Group) + --local gcallsign = "" + + if Outcome then + IsGeneral = false + end + if IsGeneral then gcallsign = "All Stations" - else - gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" + --else + --gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" end if not self.intel then @@ -1101,7 +1449,7 @@ function AWACS:_Picture(Group,IsGeneral) RadioEntry.TextTTS = text RadioEntry.TextScreen = text RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) return self @@ -1110,22 +1458,53 @@ function AWACS:_Picture(Group,IsGeneral) if Outcome or IsGeneral then -- Pilot is checked in -- get clusters from Intel - local clustertable = self.intel:GetClusterTable() + + -- DONE Use contacts table! + local contactstable = self.Contacts:GetDataTable() + + --local clustertable = self.intel:GetClusterTable() or {} -- sort into buckets - for _,_cluster in pairs(clustertable) do - local cluster = _cluster -- Ops.Intelligence#INTEL.Cluster - local coordVec2 = cluster.coordinate:GetVec2() + for _,_contact in pairs(contactstable) do + + local contact = _contact -- #AWACS.ManagedContact + + self:T(UTILS.OneLineSerialize(contact)) + + local coordVec2 = contact.Contact.position:GetVec2() + + --local coordVec2 = cluster.coordinate:GetVec2() if self.OpsZone:IsVec2InZone(coordVec2) then - self.PictureAO:Push(cluster) + self.PictureAO:Push(contact) + elseif self.OrbitZone:IsVec2InZone(coordVec2) then + self.PictureAO:Push(contact) elseif self.ControlZone:IsVec2InZone(coordVec2) then - self.PictureEWR:Push(cluster) + local distance = math.floor((contact.Contact.position:Get2DDistance(self.ControlZone:GetCoordinate()) / 1000) + 1) -- km + self.PictureEWR:Push(contact,distance) end + end local clustersAO = self.PictureAO:GetSize() local clustersEWR = self.PictureEWR:GetSize() + if clustersAO < 3 and clustersEWR > 0 then + -- make sure we have 3, can only add 1, 2 or 3 + local IDstack = self.PictureEWR:GetSortedDataTable() + -- how many do we need? + local weneed = 3-clustersAO + -- do we have enough? + self:T(string.format("Picture - adding %d/%d contacts from EWR",weneed,clustersEWR)) + if weneed > clustersEWR then + weneed = clustersEWR + end + for i=1,weneed do + self.PictureAO:Push(IDstack[i]) + end + end + + clustersAO = self.PictureAO:GetSize() + if clustersAO == 0 and clustersEWR == 0 then -- clear text = string.format("%s. %s. Clear.",gcallsign, self.callsigntxt) @@ -1137,7 +1516,7 @@ function AWACS:_Picture(Group,IsGeneral) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) else @@ -1159,14 +1538,15 @@ function AWACS:_Picture(Group,IsGeneral) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) self:_CreatePicture(true,gcallsign,GID,3,IsGeneral) self.PictureAO:Clear() + self.PictureEWR:Clear() end - + --[[ if clustersAO == 0 and clustersEWR > 0 then text = string.format("%s. %s. Picture Early Warning. ",gcallsign, self.callsigntxt) textScreen = string.format("%s. %s. Picture EWR. ",gcallsign, self.callsigntxt) @@ -1175,7 +1555,7 @@ function AWACS:_Picture(Group,IsGeneral) textScreen = textScreen .. "One group.\n" else text = text .. clustersEWR .. " groups. " - textScreen = textScreen .. clustersAO .. " groups.\n" + textScreen = textScreen .. clustersEWR .. " groups.\n" end local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1184,13 +1564,14 @@ function AWACS:_Picture(Group,IsGeneral) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) self:_CreatePicture(false,gcallsign,GID,3,IsGeneral) self.PictureEWR:Clear() end + --]] end elseif self.AwacsFG then @@ -1203,7 +1584,7 @@ function AWACS:_Picture(Group,IsGeneral) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) end @@ -1216,8 +1597,8 @@ end -- @return #AWACS self function AWACS:_BogeyDope(Group) self:T(self.lid.."_BogeyDope") - local text = "BogeyDope WIP" - local textScreen = "BogeyDope WIP" + local text = "" + local textScreen = "" local GID, Outcome = self:_GetManagedGrpID(Group) local gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" @@ -1232,7 +1613,7 @@ function AWACS:_BogeyDope(Group) RadioEntry.GroupID = 0 RadioEntry.IsGroup = false RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) return self @@ -1255,9 +1636,16 @@ function AWACS:_BogeyDope(Group) -- Get distance for sorting local dist = pilotcoord:Get2DDistance(cluster.position) - + if self.OpsZone:IsVec2InZone(coordVec2) then self.ContactsAO:Push(cluster,dist) + elseif self.BorderZone and self.BorderZone:IsVec2InZone(coordVec2) then + self.ContactsAO:Push(cluster,dist) + else + local distance = cluster.position:Get2DDistance(self.OrbitZone:GetCoordinate()) + if (distance <= UTILS.NMToMeters(45)) then + self.ContactsAO:Push(cluster,distance) + end end end @@ -1274,7 +1662,7 @@ function AWACS:_BogeyDope(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = Outcome - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) else @@ -1295,7 +1683,7 @@ function AWACS:_BogeyDope(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) self:_CreateBogeyDope(self:_GetCallSign(Group,GID) or "Ghost 1 1",GID) @@ -1312,7 +1700,7 @@ function AWACS:_BogeyDope(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) end @@ -1402,7 +1790,7 @@ function AWACS:_Declare(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1418,7 +1806,7 @@ function AWACS:_Declare(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) end @@ -1450,7 +1838,7 @@ function AWACS:_Commit(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1482,7 +1870,7 @@ function AWACS:_Judy(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1514,7 +1902,7 @@ function AWACS:_Unable(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1546,7 +1934,7 @@ function AWACS:_TaskAbort(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1610,7 +1998,7 @@ function AWACS:_Showtask(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) end @@ -1622,7 +2010,7 @@ end -- @param Wrapper.Group#GROUP Group Group to use -- @return #AWACS self function AWACS:_CheckIn(Group) - self:I(self.lid.."_CheckIn "..Group:GetName()) + self:T(self.lid.."_CheckIn "..Group:GetName()) -- check if already known local GID, Outcome = self:_GetManagedGrpID(Group) local text = "" @@ -1662,7 +2050,7 @@ function AWACS:_CheckIn(Group) RadioEntry.GroupID = GID RadioEntry.IsGroup = true RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1676,7 +2064,7 @@ end -- @param #number AuftragsNr Ops.Auftrag#AUFTRAG.auftragsnummer -- @return #AWACS self function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) - self:I(self.lid.."_CheckInAI "..Group:GetName() .. " to Auftrag Nr "..AuftragsNr) + self:T(self.lid.."_CheckInAI "..Group:GetName() .. " to Auftrag Nr "..AuftragsNr) -- check if already known local GID, Outcome = self:_GetManagedGrpID(Group) local text = "" @@ -1694,7 +2082,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) local callsignstring = UTILS.GetCallsignName(self.AICAPCAllName) local callsignmajor = math.fmod(self.AICAPCAllNumber,9) local callsign = string.format("%s %d 1",callsignstring,callsignmajor) - self:I("Assigned Callsign: ".. callsign) + self:T("Assigned Callsign: ".. callsign) managedgroup.CallSign = callsign managedgroup.CurrentAuftrag = AuftragsNr managedgroup.HasAssignedTask = false @@ -1708,14 +2096,14 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) FlightGroup:SwitchRadio(self.Frequency,self.Modulation) --FlightGroup:SetDefaultCallsign(self.AICAPCAllName,callsignmajor) - FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,self.CAPVoice,self.Port,nil) + FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,self.CAPVoice,self.Port,nil,"FLIGHT") text = string.format("%s. %s. Checking in as fragged. Expected playtime %d hours. Request Alpha Check Bulls Eye.",self.callsigntxt, managedgroup.CallSign, self.CAPTimeOnStation) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.ToScreen = false - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 RadioEntry.FromAI = true RadioEntry.GroupID = managedgroup.GID @@ -1736,7 +2124,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.ToScreen = false - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1749,7 +2137,7 @@ end -- @param #number GID GroupID -- @return #AWACS self function AWACS:_CheckOut(Group,GID) - self:I(self.lid.."_CheckOut") + self:T(self.lid.."_CheckOut") -- check if already known local GID, Outcome = self:_GetManagedGrpID(Group) @@ -1757,11 +2145,17 @@ function AWACS:_CheckOut(Group,GID) if Outcome then -- yes, known text = string.format("%s. %s. Copy. Have a safe flight home.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) - self:I(text) + self:T(text) -- grab some data before we nil the entry local AnchorAssigned = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local Stack = AnchorAssigned.AnchorStackNo local Angels = AnchorAssigned.AnchorStackAngels + -- remove menus + if AnchorAssigned.IsPlayer then + local menus = self.clientmenus[AnchorAssigned.GroupName] --#AWACS.MenuStructure + menus.basemenu:Remove() + self.clientmenus[AnchorAssigned.GroupName] = nil + end self.ManagedGrps[GID] = nil self:__CheckedOut(1,GID,Stack,Angels) else @@ -1773,7 +2167,7 @@ function AWACS:_CheckOut(Group,GID) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) @@ -1802,24 +2196,28 @@ function AWACS:_SetClientMenus() if checkedin then -- full menu minus checkin clientcheckedin = clientcheckedin + 1 + local hasclientmenu = self.clientmenus[grp:GetName()] -- #AWACS.MenuStructure - --local checkinmenu = hasclientmenu.checkin -- Core.Menu#MENU_GROUP_COMMAND - --checkinmenu:Remove(nil,grp:GetName()) - local basemenu = hasclientmenu.basemenu -- Core.Menu#MENU_GROUP - --local basemenu = MENU_GROUP:New(grp.Name,nil) + + local basemenu = hasclientmenu.basemenu -- Core.Menu#MENU_GROUP_DELAYED) + basemenu:RemoveSubMenus() - local picture = MENU_GROUP_COMMAND:New(grp,"Picture",basemenu,self._Picture,self,grp) - local bogeydope = MENU_GROUP_COMMAND:New(grp,"Bogey Dope",basemenu,self._BogeyDope,self,grp) - local declare = MENU_GROUP_COMMAND:New(grp,"Declare",basemenu,self._Declare,self,grp) - local tasking = MENU_GROUP:New(grp,"Tasking",basemenu) - local showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",tasking,self._Showtask,self,grp) - local commit = MENU_GROUP_COMMAND:New(grp,"Commit",tasking,self._Commit,self,grp) - local unable = MENU_GROUP_COMMAND:New(grp,"Unable",tasking,self._Unable,self,grp) - local abort = MENU_GROUP_COMMAND:New(grp,"Abort",tasking,self._TaskAbort,self,grp) - local judy = MENU_GROUP_COMMAND:New(grp,"Judy",tasking,self._Judy,self,grp):Refresh() + local picture = MENU_GROUP_COMMAND_DELAYED:New(grp,"Picture",basemenu,self._Picture,self,grp) + local bogeydope = MENU_GROUP_COMMAND_DELAYED:New(grp,"Bogey Dope",basemenu,self._BogeyDope,self,grp) + local declare = MENU_GROUP_COMMAND_DELAYED:New(grp,"Declare",basemenu,self._Declare,self,grp) + + local tasking = MENU_GROUP_DELAYED:New(grp,"Tasking",basemenu) + local showtask = MENU_GROUP_COMMAND_DELAYED:New(grp,"Showtask",tasking,self._Showtask,self,grp) + local commit = MENU_GROUP_COMMAND_DELAYED:New(grp,"Commit",tasking,self._Commit,self,grp) + local unable = MENU_GROUP_COMMAND_DELAYED:New(grp,"Unable",tasking,self._Unable,self,grp) + local abort = MENU_GROUP_COMMAND_DELAYED:New(grp,"Abort",tasking,self._TaskAbort,self,grp) + local judy = MENU_GROUP_COMMAND_DELAYED:New(grp,"Judy",tasking,self._Judy,self,grp) + + local checkout = MENU_GROUP_COMMAND_DELAYED:New(grp,"Check Out",basemenu,self._CheckOut,self,grp) + + basemenu:Set() - local checkout = MENU_GROUP_COMMAND:New(grp,"Check Out",basemenu,self._CheckOut,self,grp):Refresh() clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure groupname = grp:GetName(), menuset = true, @@ -1837,34 +2235,42 @@ function AWACS:_SetClientMenus() } elseif not clientmenus[grp:GetName()] then -- check in only - local basemenu = MENU_GROUP:New(grp,self.Name,nil) - local checkin = MENU_GROUP_COMMAND:New(grp,"Check In",basemenu,self._CheckIn,self,grp) + local basemenu = MENU_GROUP_DELAYED:New(grp,self.Name,nil) + basemenu:RemoveSubMenus() + local checkin = MENU_GROUP_COMMAND_DELAYED:New(grp,"Check In",basemenu,self._CheckIn,self,grp) checkin:SetTag(grp:GetName()) - checkin:Refresh() + --checkin:Refresh() + basemenu:Set() + clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure groupname = grp:GetName(), menuset = true, basemenu = basemenu, checkin = checkin, } + end end else if grp and grp:IsAlive() and grp:GetUnit(1):IsPlayer() and not clientmenus[grp:GetName()] then - local basemenu = MENU_COALITION:New(self.coalition,self.Name,nil) - local picture = MENU_COALITION_COMMAND:New(self.coalition,"Picture",basemenu,self._Picture,self,grp) - local bogeydope = MENU_COALITION_COMMAND:New(self.coalition,"Bogey Dope",basemenu,self._BogeyDope,self,grp) - local declare = MENU_COALITION_COMMAND:New(self.coalition,"Declare",basemenu,self._Declare,self,grp) + local basemenu = MENU_GROUP_DELAYED:New(grp,self.Name,nil) + basemenu:RemoveSubMenus() + local picture = MENU_GROUP_COMMAND_DELAYED:New(grp,"Picture",basemenu,self._Picture,self,grp) + local bogeydope = MENU_GROUP_COMMAND_DELAYED:New(grp,"Bogey Dope",basemenu,self._BogeyDope,self,grp) + local declare = MENU_GROUP_COMMAND_DELAYED:New(grp,"Declare",basemenu,self._Declare,self,grp) - local tasking = MENU_GROUP:New(grp,"Tasking",basemenu) - local showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",tasking,self._Showtask,self,grp) - local commit = MENU_GROUP_COMMAND:New(grp,"Commit",tasking,self._Commit,self,grp) - local unable = MENU_GROUP_COMMAND:New(grp,"Unable",tasking,self._Unable,self,grp) - local abort = MENU_GROUP_COMMAND:New(grp,"Abort",tasking,self._TaskAbort,self,grp) - local judy = MENU_GROUP_COMMAND:New(grp,"Judy",tasking,self._Judy,self,grp):Refresh() + local tasking = MENU_GROUP_DELAYED:New(grp,"Tasking",basemenu) + local showtask = MENU_GROUP_COMMAND_DELAYED:New(grp,"Showtask",tasking,self._Showtask,self,grp) + local commit = MENU_GROUP_COMMAND_DELAYED:New(grp,"Commit",tasking,self._Commit,self,grp) + local unable = MENU_GROUP_COMMAND_DELAYED:New(grp,"Unable",tasking,self._Unable,self,grp) + local abort = MENU_GROUP_COMMAND_DELAYED:New(grp,"Abort",tasking,self._TaskAbort,self,grp) + local judy = MENU_GROUP_COMMAND_DELAYED:New(grp,"Judy",tasking,self._Judy,self,grp) + + local checkin = MENU_GROUP_COMMAND_DELAYED:New(grp,"Check In",basemenu,self._CheckIn,self,grp) + local checkout = MENU_GROUP_COMMAND_DELAYED:New(grp,"Check Out",basemenu,self._CheckOut,self,grp) + + basemenu:Set() - local checkin = MENU_COALITION_COMMAND:New(self.coalition,"Check In",basemenu,self._CheckIn,self,grp) - local checkout = MENU_COALITION_COMMAND:New(self.coalition,"Check Out",basemenu,self._CheckOut,self,grp):Refresh() clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure groupname = grp:GetName(), menuset = true, @@ -2003,7 +2409,7 @@ function AWACS:_AssignAnchorToID(GID) self:T({Anchor,freeangels}) self:__AssignedAnchor(5,GID,Anchor,AnchorStackNo,freeangels) else - self:E(self.lid .. "Cannot assing free anchor stack to GID ".. GID) + self:E(self.lid .. "Cannot assign free anchor stack to GID ".. GID .. " Trying again in 10secs.") -- try again ... self:__AssignAnchor(10,GID) end @@ -2021,7 +2427,7 @@ function AWACS:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels) local stack = AnchorStackNo or 0 local angels = Angels or 0 local debugstring = string.format("%s_RemoveIDFromAnchor for GID=%d Stack=%d Angels=%d",self.lid,gid,stack,angels) - self:I(debugstring) + self:T(debugstring) -- pull correct anchor if stack > 0 and angels > 0 then local AnchorStackNo = AnchorStackNo or 1 @@ -2043,12 +2449,28 @@ end function AWACS:_StartIntel(awacs) self:T(self.lid.."_StartIntel") + if self.intelstarted then return self end + self.DetectionSet:AddGroup(awacs) local intel = INTEL:New(self.DetectionSet,self.coalition,self.callsigntxt) --intel:SetVerbosity(2) intel:SetClusterAnalysis(true,false) + local acceptzoneset = SET_ZONE:New() + acceptzoneset:AddZone(self.ControlZone) + --acceptzoneset:AddZone(self.OpsZone) + + self.OrbitZone:SetRadius(UTILS.NMToMeters(45)) + acceptzoneset:AddZone(self.OrbitZone) + + if self.BorderZone then + acceptzoneset:AddZone(self.BorderZone) + end + + --self.AwacsInZone + --intel:SetAcceptZones(acceptzoneset) + if self.NoHelos then intel:SetFilterCategory({Unit.Category.AIRPLANE}) else @@ -2084,6 +2506,8 @@ function AWACS:_StartIntel(awacs) LostCluster(Cluster,Mission) end + self.intelstarted = true + intel:__Start(2) self.intel = intel -- Ops.Intelligence#INTEL @@ -2156,8 +2580,10 @@ end -- @param #AWACS.TaskDescription Description Short Description Task Type -- @param #string ScreenText Long task description for screen output -- @param #table Object Object for Ops.Target#TARGET assignment --- @return #AWACS self -function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object) +-- @param #AWACS.TaskStatus TaskStatus Status of this task +-- @param Ops.Auftrag#AUFTRAG Auftrag The Auftrag for this task if any +-- @return #number TID Task ID created +function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object,TaskStatus,Auftrag) self:I(self.lid.."_CreateTaskForGroup "..GroupID .." Description: "..Description) local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup @@ -2165,9 +2591,14 @@ function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object) self.ManagedTaskID = self.ManagedTaskID + 1 task.TID = self.ManagedTaskID task.AssignedGroupID = GroupID - task.Status = AWACS.TaskStatus.ASSIGNED + task.Status = TaskStatus or AWACS.TaskStatus.ASSIGNED task.ToDo = Description - task.Target = TARGET:New(Object) + task.Auftrag = Auftrag + if Object and Object:IsInstanceOf("TARGET") then + task.Target = Object + else + task.Target = TARGET:New(Object) + end task.ScreenText = ScreenText if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then task.Target.Type = TARGET.ObjectType.ZONE @@ -2181,7 +2612,7 @@ function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object) self.ManagedGrps[GroupID] = managedgroup - return self + return task.TID end --- [Internal] Read registered Task for Group by its ID @@ -2272,7 +2703,7 @@ end -- @param #number GID Group ID -- @return #AWACS self function AWACS:_MessageAIReadyForTasking(GID) - self:I(self.lid.."_MessageAIReadyForTasking") + self:T(self.lid.."_MessageAIReadyForTasking") -- obtain group details if GID >0 and self.ManagedGrps[GID] then local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -2284,7 +2715,7 @@ function AWACS:_MessageAIReadyForTasking(GID) RadioEntry.IsNew = true RadioEntry.IsGroup = false RadioEntry.GroupID = GID - RadioEntry.Duration = STTS.getSpeechTime(TextTTS,1.2,false)+2 or 16 + RadioEntry.Duration = STTS.getSpeechTime(TextTTS,0.95,false)+2 or 16 RadioEntry.ToScreen = false RadioEntry.FromAI = true @@ -2293,19 +2724,36 @@ function AWACS:_MessageAIReadyForTasking(GID) return self end +--- [Internal] Update Contact Tag +-- @param #AWACS self +-- @param #number CID Contact ID +-- @param #string Text Text to be used +-- @return #AWACS self +function AWACS:_UpdateContactEngagementTag(CID,Text) + self:I(self.lid.."_UpdateContactEngagementTag") + local text = Text or "" + -- get contact + local contact = self.Contacts:PullByID(CID) -- #AWACS.ManagedContact + contact.EngagementTag = text + self.Contacts:Push(contact,CID) + return self +end + --- [Internal] Check available tasks and status -- @param #AWACS self -- @return #AWACS self function AWACS:_CheckTaskQueue() - self:T(self.lid.."_CheckTaskQueue") + self:I(self.lid.."_CheckTaskQueue") local opentasks = 0 local assignedtasks = 0 - --- INTERNAL TASKS + ---------------------------------------- + -- ANCHOR + ---------------------------------------- if self.ManagedTasks:IsNotEmpty() then opentasks = self.ManagedTasks:GetSize() - self:T("Assigned Tasks: " .. opentasks) + self:I("Assigned Tasks: " .. opentasks) local taskstack = self.ManagedTasks:GetPointerStack() for _id,_entry in pairs(taskstack) do local data = _entry -- Utilities.FiFo#FIFO.IDEntry @@ -2314,7 +2762,7 @@ function AWACS:_CheckTaskQueue() local description = entry.ToDo self:I("ToDo = "..description) if description == AWACS.TaskDescription.ANCHOR or description == AWACS.TaskDescription.REANCHOR then - self:T("Open Tasks ANCHOR/REANCHOR") + self:I("Open Task ANCHOR/REANCHOR") -- see if we have reached the anchor zone local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup if managedgroup then @@ -2323,11 +2771,9 @@ function AWACS:_CheckTaskQueue() local zone = target:GetObject() -- Core.Zone#ZONE self:T({zone}) if group:IsInZone(zone) then - self:I("Open Tasks ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) + self:I("Open Task ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) -- made it target:Stop() - -- pull task from OpenTasks - self.ManagedTasks:PullByPointer(_id) -- add group to idle stack if managedgroup.IsAI then -- message AI on station @@ -2336,15 +2782,110 @@ function AWACS:_CheckTaskQueue() --self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID) --self.CAPIdleHuman:Push(entry.AssignedGroupID) end + managedgroup.HasAssignedTask = false + self.ManagedGrps[entry.AssignedGroupID] = managedgroup + -- pull task from OpenTasks + self.ManagedTasks:PullByPointer(_id) else -- not there yet + self:I("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) end end + + ---------------------------------------- + -- INTERCEPT + ---------------------------------------- + elseif description == AWACS.TaskDescription.INTERCEPT then -- TODO + self:I("Open Tasks INTERCEPT") + local taskstatus = entry.Status + local targetstatus = entry.Target:GetState() + local auftrag = entry.Auftrag -- Ops.Auftrag#AUFTRAG + local auftragstatus = "Not Known" + if auftrag then + auftragstatus = auftrag:GetState() + end + local text = string.format("ID=%d | Status=%s | TargetState=%s | AuftragState=%s",entry.TID,taskstatus,targetstatus,auftragstatus) + self:I(text) + if auftrag then + if auftrag:IsExecuting() then + entry.Status = AWACS.TaskStatus.EXECUTING + elseif auftrag:IsSuccess() then + entry.Status = AWACS.TaskStatus.SUCCESS + elseif auftrag:GetState() == AUFTRAG.Status.FAILED then + entry.Status = AWACS.TaskStatus.FAILED + end + if targetstatus == "Dead" then + entry.Status = AWACS.TaskStatus.SUCCESS + elseif targetstatus == "Alive" and auftrag:IsOver() then + entry.Status = AWACS.TaskStatus.FAILED + end + else + -- Player task + -- TODO + end + + local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup + + if entry.Status == AWACS.TaskStatus.SUCCESS then + self:I("Open Tasks INTERCEPT success for GroupID "..entry.AssignedGroupID) + if managedgroup then + + self:_UpdateContactEngagementTag(managedgroup.ContactCID,"") + + managedgroup.HasAssignedTask = false + managedgroup.ContactCID = 0 + + if managedgroup.IsAI then + managedgroup.CurrentAuftrag = 0 + else + managedgroup.CurrentTask = 0 + end + + self.ManagedGrps[entry.AssignedGroupID] = managedgroup + self.ManagedTasks:PullByPointer(_id) + + self:__InterceptSuccess(1) + self:__ReAnchor(5,managedgroup.GID) + end + + elseif entry.Status == AWACS.TaskStatus.FAILED then + self:I("Open Tasks INTERCEPT failed for GroupID "..entry.AssignedGroupID) + if managedgroup then + managedgroup.HasAssignedTask = false + self:_UpdateContactEngagementTag(managedgroup.ContactCID,"") + managedgroup.ContactCID = 0 + if managedgroup.IsAI then + managedgroup.CurrentAuftrag = 0 + else + managedgroup.CurrentTask = 0 + end + if managedgroup.IsPlayer then + entry.IsPlayerTask = false + end + self.ManagedGrps[entry.AssignedGroupID] = managedgroup + end + -- re-assign, if possible. FG state? Issue re-anchor + entry.IsUnassigned = true + entry.CurrentAuftrag = 0 + entry.Auftrag = nil + entry.Status = AWACS.TaskStatus.UNASSIGNED + entry.AssignedGroupID = 0 + self.ManagedTasks:PullByPointer(_id) + --self.ManagedTasks:Push(entry,entry.TID) + self:__InterceptFailure(1) + self:__ReAnchor(5,managedgroup.GID) + end + + ---------------------------------------- + -- OTHER + ---------------------------------------- + elseif description == AWACS.TaskDescription.RTB then -- TODO end + end end @@ -2360,7 +2901,7 @@ function AWACS:_LogStatistics() local text = string.gsub(text,"{","\n") local text = string.gsub(text,"}","") local text = string.gsub(text,"="," = ") - self:I(text) + self:T(text) if self.MonitoringOn then MESSAGE:New(text,20,"AWACS",false):ToAll() end @@ -2372,7 +2913,7 @@ end -- @param Ops.AirWing#AIRWING AirWing The AirWing to (also) obtain CAP flights from -- @return #AWACS self function AWACS:AddCAPAirWing(AirWing) - self:I(self.lid.."AddCAPAirWing") + self:T(self.lid.."AddCAPAirWing") if AirWing then -- TODO - Test Install callback -- DONE - add distance to AO as UniqueID @@ -2469,9 +3010,10 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo local threatsize, threatsizetext = self:_GetBlurredSize(size) local threatlevel = contact.threatlevel local threattext = self:_GetThreatLevelText(threatlevel) - local clustercoordinate = contact.position - - local BRAfromBulls = self:_GetBRAfromBullsOrAO(clustercoordinate) + local clustercoordinate = contact.group:GetCoordinate() + clustercoordinate:SetHeading(contact.group:GetHeading()) + + local BRAfromBulls = self:_GetBRAfromBullsOrAO(clustercoordinate).."." if isGroup then BRAfromBulls = clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),IsNew) end @@ -2496,25 +3038,30 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo elseif IsPopup then BRAText = BRAText .. " Pop-up group." TextScreen = TextScreen .. " Pop-up group." + elseif IsBogeyDope and Tag and Tag ~= "" then + BRAText = BRAText .. " "..Tag.." group." + TextScreen = TextScreen .. " "..Tag.." group." else BRAText = BRAText .. " Group." TextScreen = TextScreen .. " Group." end - if Tag and Tag ~= "" then - BRAText = BRAText .. " "..Tag.."." - TextScreen = TextScreen .. " "..Tag.."." + if not IsBogeyDope then + if Tag and Tag ~= "" then + BRAText = BRAText .. " "..Tag.."." + TextScreen = TextScreen .. " "..Tag.."." + end end BRAText = BRAText .. " "..threatsizetext..". "..BRAfromBulls - TextScreen = TextScreen .. " "..threatsizetext.."\n"..BRAfromBulls + TextScreen = TextScreen .. " "..threatsizetext..". "..BRAfromBulls if self.ModernEra then -- Platform if ReportingName and ReportingName ~= "Bogey" then ReportingName = string.gsub(ReportingName,"_"," ") - BRAText = BRAText .. ". "..ReportingName.."." - TextScreen = TextScreen .. ". "..ReportingName.."." + BRAText = BRAText .. " "..ReportingName.."." + TextScreen = TextScreen .. " "..ReportingName.."." end -- High - > 40k feet local height = contact.group:GetHeight() @@ -2534,7 +3081,7 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo end end - self:I(BRAText) + --self:I(BRAText) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.TextTTS = BRAText @@ -2542,7 +3089,7 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo RadioEntry.IsNew = IsNew RadioEntry.IsGroup = isGroup RadioEntry.GroupID = GID - RadioEntry.Duration = STTS.getSpeechTime(BRAText,1.2,false)+2 or 16 + RadioEntry.Duration = STTS.getSpeechTime(BRAText,0.95,false)+2 or 16 RadioEntry.ToScreen = true self.RadioQueue:Push(RadioEntry) @@ -2572,15 +3119,17 @@ end -- @param #AWACS self -- @return #number CAPMissions -- @return #number Alert5Missions +-- @return #number InterceptMissions function AWACS:_CleanUpAIMissionStack() - self:I(self.lid.."_CleanUpAIMissionStack") + self:T(self.lid.."_CleanUpAIMissionStack") local CAPMissions = 0 local Alert5Missions = 0 + local InterceptMissions = 0 local MissionStack = FIFO:New() - self:I("Checking MissionStack") + self:T("Checking MissionStack") for _,_mission in pairs(self.CatchAllMissions) do -- looking for missions of type CAP and ALERT5 local mission = _mission -- Ops.Auftrag#AUFTRAG @@ -2591,20 +3140,23 @@ function AWACS:_CleanUpAIMissionStack() elseif type == AUFTRAG.Type.CAP then MissionStack:Push(mission,mission.auftragsnummer) CAPMissions = CAPMissions + 1 + elseif type == AUFTRAG.Type.INTERCEPT then + MissionStack:Push(mission,mission.auftragsnummer) + InterceptMissions = InterceptMissions + 1 end end self.AICAPMissions = nil self.AICAPMissions = MissionStack - return CAPMissions, Alert5Missions + return CAPMissions, Alert5Missions, InterceptMissions end function AWACS:_ConsistencyCheck() - self:I(self.lid.."_ConsistencyCheck") + self:T(self.lid.."_ConsistencyCheck") if self.debug then - self:I("CatchAllMissions") + self:T("CatchAllMissions") local catchallm = {} local report1 = REPORT:New("CatchAll") report1:Add("====================") @@ -2632,7 +3184,7 @@ function AWACS:_ConsistencyCheck() local catchallfg = {} - self:I("CatchAllFGs") + self:T("CatchAllFGs") report1:Add("====================") report1:Add("CatchAllFGs") report1:Add("====================") @@ -2654,7 +3206,7 @@ function AWACS:_ConsistencyCheck() end end report1:Add("====================") - self:I(report1:Text()) + self:T(report1:Text()) self.CatchAllFGs = nil self.CatchAllFGs = catchallfg @@ -2667,15 +3219,16 @@ end -- @param #AWACS self -- @return #AWACS self function AWACS:_CheckAICAPOnStation() - self:I(self.lid.."_CheckAICAPOnStation") + self:T(self.lid.."_CheckAICAPOnStation") self:_ConsistencyCheck() - local capmissions, alert5missions = self:_CleanUpAIMissionStack() - self:I({capmissions, alert5missions}) + local capmissions, alert5missions, interceptmissions = self:_CleanUpAIMissionStack() + self:T({capmissions, alert5missions, interceptmissions}) if self.MaxAIonCAP > 0 then - local onstation = self.AICAPMissions:Count() + --local onstation = self.AICAPMissions:Count() + local onstation = capmissions + alert5missions -- control number of AI CAP Flights if self.AIRequested < self.MaxAIonCAP then -- not enough @@ -2691,13 +3244,13 @@ function AWACS:_CheckAICAPOnStation() local selectedAW = AWS[math.random(1,availableAWS)] selectedAW:AddMission(mission) self.AIRequested = self.AIRequested + 1 - self:I("CAP="..capmissions.." ALERT5="..alert5missions.." Requested="..self.AIRequested) + self:T("CAP="..capmissions.." ALERT5="..alert5missions.." Requested="..self.AIRequested) end end if self.AIRequested > self.MaxAIonCAP then -- too many, send one home - self:I(string.format("*** Onstation %d > MaxAIOnCAP %d",onstation,self.MaxAIonCAP)) + self:T(string.format("*** Onstation %d > MaxAIOnCAP %d",onstation,self.MaxAIonCAP)) local mission = self.AICAPMissions:Pull() -- Ops.Auftrag#AUFTRAG local Groups = mission:GetOpsGroups() local OpsGroup = self:_GetAliveOpsGroupFromTable(Groups) @@ -2716,7 +3269,7 @@ function AWACS:_CheckAICAPOnStation() for _,_Mission in pairs(missions) do --local mission = self.AICAPMissions:ReadByID(_MissionID) -- Ops.Auftrag#AUFTRAG local mission = _Mission -- Ops.Auftrag#AUFTRAG - self:I("Looking at AuftragsNr " .. mission.auftragsnummer) + self:T("Looking at AuftragsNr " .. mission.auftragsnummer) local type = mission:GetType() local state = mission:GetState() --if type == AUFTRAG.Type.CAP or type == AUFTRAG.Type.ALERT5 or type == AUFTRAG.Type.ORBIT then @@ -2727,7 +3280,7 @@ function AWACS:_CheckAICAPOnStation() local FGstate = mission:GetGroupStatus(OpsGroup) if OpsGroup then FGstate = OpsGroup:GetState() - self:I("FG Object in state: " .. FGstate) + self:T("FG Object in state: " .. FGstate) end -- FG ready? -- if OpsGroup and (state == AUFTRAG.Status.STARTED or FGstate == AUFTRAG.Status.EXECUTING or FGstate == AUFTRAG.Status.SCHEDULED) then @@ -2777,7 +3330,7 @@ function AWACS:_CheckAICAPOnStation() report:Add("===============") end if self.debug then - self:T(report:Text()) + self:I(report:Text()) end end end @@ -2817,6 +3370,133 @@ function AWACS:_SetAIROE(FlightGroup,Group) return self end +--- [Internal] Assign a Pilot to a target +-- @param #AWACS self +-- @param #table Pilots Table of #AWACS.ManagedGroup Pilot +-- @param #AWACS.ManagedContact Target +-- @return #AWACS self +function AWACS:_AssignPilotToTarget(Pilots,Target) + self:I(self.lid.."_AssignPilotToTarget") + + local inreach = false + local Pilot = nil + + -- Check Distance + local targetgroupcoord = Target.Contact.position + local closest = UTILS.NMToMeters(101) + + -- get closest pilot from target + for _,_Pilot in pairs(Pilots) do + local pilotcoord = _Pilot.Group:GetCoordinate() + local targetdist = targetgroupcoord:Get2DDistance(pilotcoord) + if UTILS.MetersToNM(targetdist) < 100 and targetdist < closest then + self:I(string.format("%sTarget distance %d! Assignment %s!",self.lid,UTILS.Round(UTILS.MetersToNM(targetdist),0),_Pilot.CallSign)) + inreach = true + closest = targetdist + Pilot = _Pilot + else + self:I(self.lid .. "Target distance > 100NM! No Assignment!") + end + end + + -- TODO Currently doing AI only + if inreach and Pilot and Pilot.IsAI then + -- Target information + local callsign = Pilot.CallSign + local FGStatus = Pilot.FlightGroup:GetState() + self:I("Pilot AI Callsign: " .. callsign) + self:I("Pilot FG State: " .. FGStatus) + local targetstatus = Target.Target:GetState() + self:I("Target State: " .. targetstatus) + + -- + local currmission = Pilot.FlightGroup:GetMissionCurrent() + if currmission then + self:I("Current Mission: " .. currmission:GetType()) + end + -- create one intercept Auftrag and one to return to CAP post this one + local intercept = AUFTRAG:NewINTERCEPT(Target.Target) + intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL) + intercept:SetWeaponType(ENUMS.WeaponFlag.Auto) + + Pilot.FlightGroup:AddMission(intercept) + + local Angels = Pilot.AnchorStackAngels + Angels = Angels * 1000 + local AnchorSpeed = self.CapSpeedBase or 220 + AnchorSpeed = UTILS.KnotsToAltKIAS(AnchorSpeed,Angels) + local Anchor = self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -- #AWACS.AnchorData + local capauftrag = AUFTRAG:NewCAP(Anchor.AnchorZone,Angels,AnchorSpeed,Anchor.AnchorZoneCoordinate,0,15,{}) + capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) + Pilot.FlightGroup:AddMission(capauftrag) + + -- cancel current mission + if currmission then + currmission:__Cancel(3) + end + + -- update known mission list + self.CatchAllMissions[#self.CatchAllMissions+1] = intercept + self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag + + -- update pilot TaskSheet + self.ManagedTasks:PullByID(Pilot.CurrentTask) + + Pilot.HasAssignedTask = true + Pilot.CurrentTask = self:_CreateTaskForGroup(Pilot.GID,AWACS.TaskDescription.INTERCEPT,"Intercept Task",Target.Target,AWACS.TaskStatus.ASSIGNED,intercept) + Pilot.CurrentAuftrag = intercept.auftragsnummer + Pilot.ContactCID = Target.CID + + -- update managed group + self.ManagedGrps[Pilot.GID] = Pilot + + -- Update Contact Status + Target.LinkedTask = Pilot.CurrentTask + Target.LinkedGroup = Pilot.GID + Target.Status = AWACS.TaskStatus.ASSIGNED + Target.EngagementTag = string.format("Targeted by %s.",Pilot.CallSign) + + self.Contacts:PullByID(Target.CID) + self.Contacts:Push(Target,Target.CID) + + -- message commit and return commit from AI + --[[ + Controller: “TANGO, TEN GROUPS, GROUP ROCK 250/45, THIRTY-FIVE + THOUSAND, TRACK EAST, HOSTILE, RECOMMEND RAPTOR COMMIT.” + Fighter: “RAPTOR COMMIT. + --]] + + local bratext = Target.Contact.position:ToStringBRA(Pilot.Group:GetCoordinate()) + local text = string.format("%s. %s group. %s. %s commit.", self.callsigntxt,Target.TargetGroupNaming,bratext,Pilot.CallSign) + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.ToScreen = self.debug + RadioEntry.Duration = (STTS.getSpeechTime(text,0.95,false) or 8) + 5 + + self.RadioQueue:Push(RadioEntry) + + local text = string.format("%s. Commit.",Pilot.CallSign) + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.ToScreen = self.debug + RadioEntry.FromAI = true + RadioEntry.GroupID = Pilot.GID + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + self:__Intercept(2) + + end + + return self +end + -- TODO FSMs ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Functions @@ -2829,7 +3509,7 @@ end -- @param #string To -- @return #AWACS self function AWACS:onafterStart(From, Event, To) - self:I({From, Event, To}) + self:T({From, Event, To}) -- Set up control zone self.ControlZone = ZONE_RADIUS:New(self.OpsZone:GetName(),self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius)) @@ -2855,16 +3535,8 @@ function AWACS:onafterStart(From, Event, To) return self end ---- [Internal] onafterStatus --- @param #AWACS self --- @param #string From --- @param #string Event --- @param #string To --- @return #AWACS self -function AWACS:onafterStatus(From, Event, To) - self:I({From, Event, To}) - - self:_SetClientMenus() +function AWACS:_CheckAwacsStatus() + self:I(self.lid.."_CheckAwacsStatus") local awacs = nil if self.AwacsFG then @@ -2881,16 +3553,16 @@ function AWACS:onafterStatus(From, Event, To) self.AwacsInZone = true self:I(self.lid.."Arrived in Orbit Zone: " .. orbitzone:GetName()) local text = string.format("%s on station for A O %s control.",self.callsigntxt,self.OpsZone:GetName() or "A O") - + local textScreen = string.format("%s on station for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text - RadioEntry.TextScreen = text + RadioEntry.TextScreen = textScreen RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_StartIntel(awacs) + --self:_StartIntel(awacs) end end @@ -2900,7 +3572,11 @@ function AWACS:onafterStatus(From, Event, To) -------------------------------- if (awacs and awacs:IsAlive()) then - + + if not self.intelstarted then + self:_StartIntel(awacs) + end + -- Check on Awacs Mission Status local AWmission = self.AwacsMission -- Ops.Auftrag#AUFTRAG local awstatus = AWmission:GetState() @@ -3070,23 +3746,68 @@ function AWACS:onafterStatus(From, Event, To) end if self.debug then - self:I(report:Text()) - end - - -- Check on AUFTRAG status for CAP AI - if self:Is("Running") then - self:_CheckAICAPOnStation() + self:T(report:Text()) end else -- do other stuff, AWACS dead? - -- TODO AWACS dead - + -- TODO AWACS dead, or per EVENT? + -- Check on Awacs Mission Status + local AWmission = self.AwacsMission -- Ops.Auftrag#AUFTRAG + local awstatus = AWmission:GetState() + if AWmission:IsOver() then + -- yup we're dead + self:I(self.lid.."*****AWACS is dead!*****") + self.ShiftChangeAwacsFlag = true + self:__AwacsShiftChange(2) + end end - -- Check task queue (both) - if self:Is("Running") then + return monitoringdata +end + +--- [Internal] onafterStatus +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #AWACS self +function AWACS:onafterStatus(From, Event, To) + self:I({From, Event, To}) + + self:_SetClientMenus() + + local monitoringdata = self:_CheckAwacsStatus() + + local awacsalive = false + if self.AwacsFG then + local awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP + if awacs and awacs:IsAlive() then + awacsalive= true + end + end + + -- Check on AUFTRAG status for CAP AI + if self:Is("Running") and (awacsalive or self.AwacsInZone) then + + self:_CheckAICAPOnStation() + + self:_CleanUpContacts() + + if self.debug then + --local outcome, targets = self:_TargetSelectionProcess() -- TODO for debug ATM + end + local outcome, targets = self:_TargetSelectionProcess(true) + self:_CheckTaskQueue() + + local AI, Humans = self:_GetIdlePilots() + -- assign Pilot if there are targets and available Pilots, prefer Humans to AI + -- TODO - Implemented AI First, Humans laters ;) + if outcome and #AI > 0 then + -- add a task for AI + self:_AssignPilotToTarget(AI,targets:Pull()) + end end monitoringdata.AwacsShiftChange = self.ShiftChangeAwacsFlag @@ -3182,7 +3903,7 @@ end -- @param #number AnchorStackNo -- @return #AWACS self function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo, AnchorAngels) - self:I({From, Event, To, "GID=" .. GID, "Stack=" .. AnchorStackNo}) + self:T({From, Event, To, "GID=" .. GID, "Stack=" .. AnchorStackNo}) -- TODO local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup if not managedgroup then @@ -3218,7 +3939,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo self.RadioQueue:Push(RadioEntry) - self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.AnchorZone) + managedgroup.CurrentTask = self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.AnchorZone) -- if isAI and AuftragsNr and AuftragsNr > 0 and self.AICAPMissions:HasUniqueID(AuftragsNr) then @@ -3293,10 +4014,22 @@ function AWACS:onafterNewContact(From,Event,To,Contact) Contact.CID = managedcontact.CID Contact.TargetGroupNaming = managedcontact.TargetGroupNaming - self.Contacts:Push(Contact,self.CID) - self.ManagedContacts:Push(Contact,self.CID) + self.Contacts:Push(managedcontact,self.CID) + --self.ManagedContacts:Push(managedcontact,self.CID) - self:_AnnounceContact(Contact,true,nil,false,managedcontact.TargetGroupNaming,IsPopup,managedcontact.ReportingName) + -- only announce if in right distance to HVT/AIC or in ControlZone or in BorderZone + local ContactCoordinate = Contact.position:GetVec2() + local incontrolzone = self.ControlZone:IsVec2InZone(ContactCoordinate) + -- distance check to HVT + local distance = Contact.position:Get2DDistance(self.OrbitZone:GetCoordinate()) + local inborderzone = false + if self.BorderZone then + inborderzone = self.BorderZone:IsVec2InZone(ContactCoordinate) + end + + if incontrolzone or inborderzone or (distance <= UTILS.NMToMeters(45)) then + self:_AnnounceContact(Contact,true,nil,false,managedcontact.TargetGroupNaming,IsPopup,managedcontact.ReportingName) + end return self end @@ -3310,7 +4043,7 @@ end -- @return #AWACS self function AWACS:onafterLostContact(From,Event,To,Contact) self:T({From, Event, To, Contact}) - -- TODO Check Idle, Assigned Tasks for status + self:_CleanUpContacts() return self end @@ -3324,7 +4057,7 @@ end -- @return #AWACS self function AWACS:onafterLostCluster(From,Event,To,Cluster,Mission) self:T({From, Event, To}) - -- TODO Remove Cluster from Picture + self:_CleanUpContacts() return self end @@ -3347,14 +4080,14 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) if not RadioEntry.FromAI then -- AI AWACS Speaking self.AwacsFG:RadioTransmission(RadioEntry.TextTTS,1,false) - self:I(RadioEntry.TextTTS) + self:T(RadioEntry.TextTTS) else -- CAP AI speaking if RadioEntry.GroupID and RadioEntry.GroupID ~= 0 then local managedgroup = self.ManagedGrps[RadioEntry.GroupID] -- #AWACS.ManagedGroup if managedgroup and managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive() then managedgroup.FlightGroup:RadioTransmission(RadioEntry.TextTTS,1,false) - self:I(RadioEntry.TextTTS) + self:T(RadioEntry.TextTTS) end end end @@ -3365,7 +4098,7 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) local managedgroup = self.ManagedGrps[RadioEntry.GroupID] -- #AWACS.ManagedGroup if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group) - self:I(RadioEntry.TextScreen) + self:T(RadioEntry.TextScreen) end else MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition) @@ -3412,7 +4145,7 @@ end -- @return #AWACS self function AWACS:onafterAwacsShiftChange(From,Event,To) self:I({From, Event, To}) - -- request new Escorts, check if AWACS-FG still alive first! + -- request new AWACS if self.AwacsFG and self.ShiftChangeAwacsFlag and not self.ShiftChangeAwacsRequested then -- ok we're good to re-request @@ -3445,14 +4178,14 @@ end function AWACS:onafterFlightOnMission(From, Event, To, FlightGroup, Mission) self:I({From, Event, To}) -- coming back from AW, set up the flight - self:I("FlightGroup " .. FlightGroup:GetName() .. " Mission " .. Mission:GetName() .. " Type "..Mission:GetType()) + self:T("FlightGroup " .. FlightGroup:GetName() .. " Mission " .. Mission:GetName() .. " Type "..Mission:GetType()) self.CatchAllFGs[#self.CatchAllFGs+1] = FlightGroup if not self:Is("Stopped") then if not self.AwacsReady or self.ShiftChangeAwacsFlag or self.ShiftChangeEscortsFlag then self:_StartSettings(FlightGroup,Mission) elseif Mission and (Mission:GetType() == AUFTRAG.Type.CAP or Mission:GetType() == AUFTRAG.Type.ALERT5 or Mission:GetType() == AUFTRAG.Type.ORBIT) then if not self.FlightGroups:HasUniqueID(FlightGroup:GetName()) then - self:I("Pushing FG " .. FlightGroup:GetName() .. " to stack!") + self:T("Pushing FG " .. FlightGroup:GetName() .. " to stack!") self.FlightGroups:Push(FlightGroup,FlightGroup:GetName()) end end @@ -3460,6 +4193,80 @@ function AWACS:onafterFlightOnMission(From, Event, To, FlightGroup, Mission) return self end +--- On after "ReAnchor". +-- @param #AWACS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number GID Group ID to check and re-anchor if possible +-- @return #AWACS self +function AWACS:onafterReAnchor(From, Event, To, GID) + self:I({From, Event, To, GID}) + -- get managedgroup + -- check AI FG state + -- check weapon state + -- check fuel state + -- vector back to anchor or RTB + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + if managedgroup then + if managedgroup.IsAI then + -- AI will now have a new CAP AUFTRAG and head back to the stack anyway + local AIFG = managedgroup.FlightGroup -- Ops.FlightGroup#FLIGHTGROUP + if AIFG and AIFG:IsAlive() then + -- check state + if AIFG:IsFuelLow() or AIFG:IsOutOfMissiles() or AIFG:IsOutOfAmmo() then + local destbase = AIFG.homebase + if not destbase then destbase = self.Airbase end + -- RTB call needs an AIRBASE + AIFG:RTB(destbase) + -- Check out + self:_CheckOut(AIFG:GetGroup(),GID) + self.AIRequested = self.AIRequested - 1 + else + -- re-establish anchor task + -- get anchor zone data + local Anchor = self.AnchorStacks:ReadByPointer(managedgroup.AnchorStackNo) -- #AWACS.AnchorData + local AnchorZone = Anchor.AnchorZone -- Core.Zone#ZONE + managedgroup.CurrentTask = self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,"Re-Anchor AI",AnchorZone) + managedgroup.HasAssignedTask = true + local mission = AIFG:GetMissionCurrent() -- Ops.Auftrag#AUFTRAG + if mission then + managedgroup.CurrentAuftrag = mission.auftragsnummer or 0 + else + managedgroup.CurrentAuftrag = 0 + end + managedgroup.ContactCID = 0 + self.ManagedGrps[GID] = managedgroup + self:_MessageVector(GID," to Anchor",Anchor.AnchorZoneCoordinate,managedgroup.AnchorStackAngels) + end + else + -- lost group, remove from known groups, declare vanished + -- AI - remove from known FGs! -- done in status loop + -- ALL remove from managedgrps + + -- message loss + local savedcallsign = managedgroup.CallSign + + local text = string.format("%s. Lost Flight %s.",self.callsigntxt, savedcallsign) + local textScreen = string.format("%s. Lost Flight %s.", self.callsigntxt, savedcallsign) + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = 0 + RadioEntry.ToScreen = self.debug + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 + self.RadioQueue:Push(RadioEntry) + + self.ManagedGrps[GID] = nil + end + elseif managedgroup.IsPlayer then + -- TODO + end + end + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- END AWACS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3476,13 +4283,14 @@ AwacsAW:SetReportOn() AwacsAW:SetMarker(true) AwacsAW:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Kutaisi)) AwacsAW:SetRespawnAfterDestroyed(900) +AwacsAW:SetTakeoffHot() AwacsAW:__Start(2) -- And a couple of Squads -- AWACS itself local Squad_One = SQUADRON:New("Awacs One",2,"Awacs North") Squad_One:AddMissionCapability({AUFTRAG.Type.ORBIT},100) -Squad_One:SetFuelLowRefuel(false) +Squad_One:SetFuelLowRefuel(true) Squad_One:SetFuelLowThreshold(0.2) Squad_One:SetTurnoverTime(10,20) AwacsAW:AddSquadron(Squad_One) @@ -3494,15 +4302,19 @@ Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT}) Squad_Two:SetFuelLowRefuel(true) Squad_Two:SetFuelLowThreshold(0.3) Squad_Two:SetTurnoverTime(10,20) +Squad_Two:SetTakeoffHot() +Squad_Two:SetRadio(255,radio.modulation.AM) AwacsAW:AddSquadron(Squad_Two) AwacsAW:NewPayload("Escorts",-1,{AUFTRAG.Type.ESCORT},100) -- CAP -local Squad_Three = SQUADRON:New("CAP",2,"CAP North") +local Squad_Three = SQUADRON:New("CAP",10,"CAP North") Squad_Three:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},80) Squad_Three:SetFuelLowRefuel(true) Squad_Three:SetFuelLowThreshold(0.3) Squad_Three:SetTurnoverTime(10,20) +Squad_Three:SetTakeoffHot() +Squad_Two:SetRadio(255,radio.modulation.AM) AwacsAW:AddSquadron(Squad_Three) AwacsAW:NewPayload("Aerial-1-2",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},100) @@ -3510,23 +4322,50 @@ AwacsAW:NewPayload("Aerial-1-2",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRA local AwacsAW2 = AIRWING:New("AirForce WH-2","AirForce Two") AwacsAW2:SetReportOn() AwacsAW2:SetMarker(true) -AwacsAW2:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Batumi)) +AwacsAW2:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Senaki_Kolkhi)) AwacsAW2:SetRespawnAfterDestroyed(900) +AwacsAW2:SetTakeoffHot() AwacsAW2:__Start(2) -- CAP2 -local Squad_ThreeOne = SQUADRON:New("CAP2",4,"CAP West") +local Squad_ThreeOne = SQUADRON:New("CAP2",10,"CAP West") Squad_ThreeOne:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},80) Squad_ThreeOne:SetFuelLowRefuel(true) Squad_ThreeOne:SetFuelLowThreshold(0.3) Squad_ThreeOne:SetTurnoverTime(10,20) +Squad_ThreeOne:SetTakeoffHot() +Squad_Two:SetRadio(255,radio.modulation.AM) AwacsAW2:AddSquadron(Squad_ThreeOne) AwacsAW2:NewPayload("CAP 2-1",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},100) -- Get AWACS started local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("NW Zone"),"Anchor One",255,radio.modulation.AM ) testawacs:SetEscort(2) -testawacs:SetAwacsDetails(CALLSIGN.AWACS.Wizard,1,30,300,243,30) +testawacs:SetAwacsDetails(CALLSIGN.AWACS.Magic,1,30,300,243,20) testawacs:SetSRS("E:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010,nil) +testawacs:SetBorderZone(ZONE:FindByName("Blue Border")) testawacs:AddCAPAirWing(AwacsAW2) testawacs:__Start(5) + +-- Red CAP +function GetRedCAP() + local caps = SPAWN:New("Red CAP") + :InitLimit(0,1) + :InitCleanUp(60) + :SpawnScheduled(300,0.1) + local TU22 = SPAWN:New("TU-22") + :InitLimit(0,1) + :InitCleanUp(60) + :SpawnScheduled(360,0.1) + local Fulcrum = SPAWN:New("Aerial-1") + :InitLimit(0,1) + :InitCleanUp(60) + :SpawnScheduled(420,0.1) + local Bears = SPAWN:New("Aerial-2") + :InitLimit(0,1) + :InitCleanUp(60) + :SpawnScheduled(480,0.1) +end + +local captimer = TIMER:New(GetRedCAP) +captimer:Start(600) \ No newline at end of file