From fc9e237dbb5bfaf44c4f3b0d501af96a610d7297 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 08:12:02 +0200 Subject: [PATCH 01/22] UTILS - LiFo/FiFo Stacks --- Moose Development/Moose/Utilities/Utils.lua | 286 +++++++++++++++++++- 1 file changed, 276 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f78dda58f..213e4d1ae 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2356,7 +2356,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) return datatable end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FIFO ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2377,13 +2377,10 @@ do --- FIFO class. -- @type FIFO -- @field #string ClassName Name of the class. --- @field #boolean debug -- @field #string lid Class id string for output to DCS log file. -- @field #string version Version of FiFo -- @field #number counter -- @field #number pointer --- @field #number nextin --- @field #number nextout -- @field #table stackbypointer -- @field #table stackbyid -- @extends Core.Base#BASE @@ -2399,13 +2396,10 @@ do -- @field #FIFO FIFO = { ClassName = "FIFO", - debug = true, lid = "", version = "0.0.1", counter = 0, pointer = 0, - nextin = 0, - nextout = 0, stackbypointer = {}, stackbyid = {} } @@ -2451,10 +2445,12 @@ end function FIFO:Pull() self:T(self.lid.."Pull") if self.counter == 0 then return nil end - local object = self.stackbypointer[self.pointer].data - self.stackbypointer[self.pointer] = nil + --local object = self.stackbypointer[self.pointer].data + --self.stackbypointer[self.pointer] = nil + local object = self.stackbypointer[1].data + self.stackbypointer[1] = nil self.counter = self.counter - 1 - self.pointer = self.pointer - 1 + --self.pointer = self.pointer - 1 self:Flatten() return object end @@ -2519,6 +2515,14 @@ function FIFO:IsEmpty() return self.counter == 0 and true or false end +--- FIFO Get stack size +-- @param #FIFO self +-- @return #number size +function FIFO:GetSize() + self:T(self.lid.."GetSize") + return self.counter +end + --- FIFO Check Stack is NOT empty -- @param #FIFO self -- @return #boolean notempty @@ -2543,6 +2547,29 @@ function FIFO:GetIDStack() return self.stackbyid end +--- FIFO Get table of UniqueIDs sorthed smallest to largest +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetIDStackSorted() + self:T(self.lid.."GetIDStackSorted") + + local stack = self:GetIDStack() + local idstack = {} + for _id,_entry in pairs(stack) do + idstack[#idstack+1] = _id + + self:I({"pre",_id}) + end + + local function sortID(a, b) + return a < b + end + + table.sort(idstack) + + return idstack +end + --- FIFO Print stacks to dcs.log -- @param #FIFO self -- @return #FIFO self @@ -2566,4 +2593,243 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- End FIFO ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- LIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +do +--- **UTILS** - FiFo Stack. +-- +-- **Main Features:** +-- +-- * Build a simple multi-purpose LiFo (Last-In, First-Out) stack for generic data. +-- +-- === +-- +-- ### Author: **applevangelist** +-- @module Utils.LiFo +-- @image MOOSE.JPG + + +--- LIFO class. +-- @type LIFO +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string version Version of LiFo +-- @field #number counter +-- @field #number pointer +-- @field #table stackbypointer +-- @field #table stackbyid +-- @extends Core.Base#BASE +-- + +--- +-- @type LIFO.IDEntry +-- @field #number pointer +-- @field #table data +-- @field #table uniqueID + +--- +-- @field #LIFO +LIFO = { + ClassName = "LIFO", + lid = "", + version = "0.0.1", + counter = 0, + pointer = 0, + stackbypointer = {}, + stackbyid = {} +} + +--- Instantiate a new LIFO Stack +-- @param #LIFO self +-- @return #LIFO self +function LIFO:New() + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #INTEL + self.pointer = 0 + self.counter = 0 + self.stackbypointer = {} + self.stackbyid = {} + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", "LiFo", self.version) + self:I(self.lid .."Created.") + return self +end + +--- LIFO Push Object to Stack +-- @param #LIFO self +-- @param #table Object +-- @param #string UniqueID (optional) - will default to current pointer + 1 +-- @return #LIFO self +function LIFO:Push(Object,UniqueID) + self:T(self.lid.."Push") + self:T({Object,UniqueID}) + self.pointer = self.pointer + 1 + self.counter = self.counter + 1 + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + if UniqueID then + self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + else + self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + end + return self +end + +--- LIFO Pull Object from Stack +-- @param #LIFO self +-- @return #table Object or nil if stack is empty +function LIFO:Pull() + self:T(self.lid.."Pull") + if self.counter == 0 then return nil end + local object = self.stackbypointer[self.pointer].data + self.stackbypointer[self.pointer] = nil + --local object = self.stackbypointer[1].data + --self.stackbypointer[1] = nil + self.counter = self.counter - 1 + self.pointer = self.pointer - 1 + self:Flatten() + return object +end + +--- LIFO Pull Object from Stack by Pointer +-- @param #LIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty +function LIFO:PullByPointer(Pointer) + self:T(self.lid.."PullByPointer " .. tostring(Pointer)) + if self.counter == 0 then return nil end + local object = self.stackbypointer[Pointer] -- #LIFO.IDEntry + self.stackbypointer[Pointer] = nil + self.stackbyid[object.uniqueID] = nil + self.counter = self.counter - 1 + self:Flatten() + return object.data +end + +--- LIFO Pull Object from Stack by UniqueID +-- @param #LIFO self +-- @param #tableUniqueID +-- @return #table Object or nil if stack is empty +function LIFO:PullByID(UniqueID) + self:T(self.lid.."PullByID " .. tostring(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) +end + +--- LIFO Housekeeping +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Flatten() + self:T(self.lid.."Flatten") + -- rebuild stacks + local pointerstack = {} + local idstack = {} + local counter = 0 + for _ID,_entry in pairs(self.stackbypointer) do + counter = counter + 1 + pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} + end + for _ID,_entry in pairs(pointerstack) do + idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} + end + self.stackbypointer = nil + self.stackbypointer = pointerstack + self.stackbyid = nil + self.stackbyid = idstack + self.counter = counter + self.pointer = counter + return self +end + +--- LIFO Check Stack is empty +-- @param #LIFO self +-- @return #boolean empty +function LIFO:IsEmpty() + self:T(self.lid.."IsEmpty") + return self.counter == 0 and true or false +end + +--- LIFO Get stack size +-- @param #LIFO self +-- @return #number size +function LIFO:GetSize() + self:T(self.lid.."GetSize") + return self.counter +end + +--- LIFO Check Stack is NOT empty +-- @param #LIFO self +-- @return #boolean notempty +function LIFO:IsNotEmpty() + self:T(self.lid.."IsNotEmpty") + return not self:IsEmpty() +end + +--- LIFO Get the data stack by pointer +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetPointerStack() + self:T(self.lid.."GetPointerStack") + return self.stackbypointer +end + +--- LIFO Get the data stack by UniqueID +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetIDStack() + self:T(self.lid.."GetIDStack") + return self.stackbyid +end + +--- LIFO Get table of UniqueIDs sorthed smallest to largest +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetIDStackSorted() + self:T(self.lid.."GetIDStackSorted") + + local stack = self:GetIDStack() + local idstack = {} + for _id,_entry in pairs(stack) do + idstack[#idstack+1] = _id + + self:I({"pre",_id}) + end + + local function sortID(a, b) + return a < b + end + + table.sort(idstack) + + return idstack +end + +--- LIFO Print stacks to dcs.log +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Flush() + self:T(self.lid.."FiFo Flush") + self:I("LIFO Flushing Stack by Pointer") + for _id,_data in pairs (self.stackbypointer) do + local data = _data -- #LIFO.IDEntry + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("LIFO Flushing Stack by ID") + for _id,_data in pairs (self.stackbyid) do + local data = _data -- #LIFO.IDEntry + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("Counter = " .. self.counter) + self:I("Pointer = ".. self.pointer) + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End LIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- end \ No newline at end of file From dd957237e2d424612737818c6c9c94caa6aa72e4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 08:54:42 +0200 Subject: [PATCH 02/22] Nicefy docs --- Moose Development/Moose/AI/AI_A2A_Dispatcher.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index a4ad52423..13b92ec50 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -2361,7 +2361,7 @@ do -- AI_A2A_DISPATCHER end --- Set flashing player messages on or off - -- @param #AI_A2G_DISPATCHER self + -- @param #AI_A2A_DISPATCHER self -- @param #boolean onoff Set messages on (true) or off (false) function AI_A2A_DISPATCHER:SetSendMessages( onoff ) self.SetSendPlayerMessages = onoff diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 213e4d1ae..5991c2327 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2370,9 +2370,6 @@ do -- === -- -- ### Author: **applevangelist** --- @module Utils.FiFo --- @image MOOSE.JPG - --- FIFO class. -- @type FIFO @@ -2384,7 +2381,6 @@ do -- @field #table stackbypointer -- @field #table stackbyid -- @extends Core.Base#BASE --- --- -- @type FIFO.IDEntry @@ -2409,7 +2405,7 @@ FIFO = { -- @return #FIFO self function FIFO:New() -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) -- #INTEL + local self=BASE:Inherit(self, BASE:New()) self.pointer = 0 self.counter = 0 self.stackbypointer = {} @@ -2600,7 +2596,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- do ---- **UTILS** - FiFo Stack. +--- **UTILS** - LiFo Stack. -- -- **Main Features:** -- @@ -2609,9 +2605,6 @@ do -- === -- -- ### Author: **applevangelist** --- @module Utils.LiFo --- @image MOOSE.JPG - --- LIFO class. -- @type LIFO @@ -2623,7 +2616,6 @@ do -- @field #table stackbypointer -- @field #table stackbyid -- @extends Core.Base#BASE --- --- -- @type LIFO.IDEntry @@ -2648,7 +2640,7 @@ LIFO = { -- @return #LIFO self function LIFO:New() -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) -- #INTEL + local self=BASE:Inherit(self, BASE:New()) self.pointer = 0 self.counter = 0 self.stackbypointer = {} From 626b48c3d1f48c9ddb85afb00ec7ec721aa46b81 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 11:11:18 +0200 Subject: [PATCH 03/22] Added LIFO/FIFO:HasUniqueID(UniqueID) --- Moose Development/Moose/Utilities/Utils.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 5991c2327..998df18ad 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2535,6 +2535,14 @@ function FIFO:GetPointerStack() return self.stackbypointer end +--- FIFO Check if a certain UniqeID exists +-- @param #FIFO self +-- @return #boolean exists +function FIFO:HasUniqueID(UniqueID) + self:T(self.lid.."HasUniqueID") + return self.stackbyid[UniqueID] and true or false +end + --- FIFO Get the data stack by UniqueID -- @param #FIFO self -- @return #table Table of #FIFO.IDEntry entries @@ -2801,6 +2809,14 @@ function LIFO:GetIDStackSorted() return idstack end +--- LIFO Check if a certain UniqeID exists +-- @param #LIFO self +-- @return #boolean exists +function LIFO:HasUniqueID(UniqueID) + self:T(self.lid.."HasUniqueID") + return self.stackbyid[UniqueID] and true or false +end + --- LIFO Print stacks to dcs.log -- @param #LIFO self -- @return #LIFO self From 6e218ed908cbfbe7e308abb9b2b20a4bd0fdd06f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 15:05:49 +0200 Subject: [PATCH 04/22] LIFO/FIFO - enforce unique ID --- Moose Development/Moose/Utilities/Utils.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 998df18ad..f00509a60 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2410,6 +2410,7 @@ function FIFO:New() self.counter = 0 self.stackbypointer = {} self.stackbyid = {} + self.uniquecounter = 0 -- Set some string id for output to DCS.log file. self.lid=string.format("%s (%s) | ", "FiFo", self.version) self:I(self.lid .."Created.") @@ -2430,7 +2431,8 @@ function FIFO:Push(Object,UniqueID) if UniqueID then self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } else - self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + self.uniquecounter = self.uniquecounter + 1 + self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = self.uniquecounter } end return self end @@ -2540,7 +2542,7 @@ end -- @return #boolean exists function FIFO:HasUniqueID(UniqueID) self:T(self.lid.."HasUniqueID") - return self.stackbyid[UniqueID] and true or false + return self.stackbyid[UniqueID] and true or false end --- FIFO Get the data stack by UniqueID @@ -2582,12 +2584,12 @@ function FIFO:Flush() self:I("FIFO Flushing Stack by Pointer") for _id,_data in pairs (self.stackbypointer) do local data = _data -- #FIFO.IDEntry - self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) end self:I("FIFO Flushing Stack by ID") for _id,_data in pairs (self.stackbyid) do local data = _data -- #FIFO.IDEntry - self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) end self:I("Counter = " .. self.counter) self:I("Pointer = ".. self.pointer) @@ -2651,6 +2653,7 @@ function LIFO:New() local self=BASE:Inherit(self, BASE:New()) self.pointer = 0 self.counter = 0 + self.uniquecounter = 0 self.stackbypointer = {} self.stackbyid = {} -- Set some string id for output to DCS.log file. @@ -2673,7 +2676,8 @@ function LIFO:Push(Object,UniqueID) if UniqueID then self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } else - self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } + self.uniquecounter = self.uniquecounter + 1 + self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = self.uniquecounter } end return self end From 061469840b25a60039b1f45c1301afe0b0d06870 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Apr 2022 15:55:51 +0200 Subject: [PATCH 05/22] FIFO --- Moose Development/Moose/Utilities/Utils.lua | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f00509a60..b782c9a93 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2427,13 +2427,13 @@ function FIFO:Push(Object,UniqueID) self:T({Object,UniqueID}) self.pointer = self.pointer + 1 self.counter = self.counter + 1 - self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } - if UniqueID then - self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } - else - self.uniquecounter = self.uniquecounter + 1 - self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = self.uniquecounter } + local uniID = UniqueID + if not UniqueID then + self.uniquecounter = self.uniquecounter + 1 + uniID = self.uniquecounter end + self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } return self end @@ -2564,7 +2564,7 @@ function FIFO:GetIDStackSorted() for _id,_entry in pairs(stack) do idstack[#idstack+1] = _id - self:I({"pre",_id}) + self:T({"pre",_id}) end local function sortID(a, b) @@ -2672,13 +2672,13 @@ function LIFO:Push(Object,UniqueID) self:T({Object,UniqueID}) self.pointer = self.pointer + 1 self.counter = self.counter + 1 - self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } - if UniqueID then - self.stackbyid[UniqueID] = { pointer = self.pointer, data = Object, uniqueID = UniqueID } - else - self.uniquecounter = self.uniquecounter + 1 - self.stackbyid[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = self.uniquecounter } + local uniID = UniqueID + if not UniqueID then + self.uniquecounter = self.uniquecounter + 1 + uniID = self.uniquecounter end + self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } return self end @@ -2801,7 +2801,7 @@ function LIFO:GetIDStackSorted() for _id,_entry in pairs(stack) do idstack[#idstack+1] = _id - self:I({"pre",_id}) + self:T({"pre",_id}) end local function sortID(a, b) @@ -2829,12 +2829,12 @@ function LIFO:Flush() self:I("LIFO Flushing Stack by Pointer") for _id,_data in pairs (self.stackbypointer) do local data = _data -- #LIFO.IDEntry - self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) end self:I("LIFO Flushing Stack by ID") for _id,_data in pairs (self.stackbyid) do local data = _data -- #LIFO.IDEntry - self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) end self:I("Counter = " .. self.counter) self:I("Pointer = ".. self.pointer) From 0522036b8024fd3427ae10b7b60d025e8043f8e8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 16 Apr 2022 18:58:43 +0200 Subject: [PATCH 06/22] Docu --- Moose Development/Moose/Utilities/STTS.lua | 3 ++ Moose Development/Moose/Utilities/Utils.lua | 34 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua index 97836a1ee..dccbdd67b 100644 --- a/Moose Development/Moose/Utilities/STTS.lua +++ b/Moose Development/Moose/Utilities/STTS.lua @@ -125,6 +125,9 @@ end -- -- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min -- +-- @param #number length can also be passed as #string +-- @param #number speed Defaults to 1.0 +-- @param #boolean isGoogle We're using Google TTS function STTS.getSpeechTime(length,speed,isGoogle) local maxRateRatio = 3 diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index b782c9a93..b176fce85 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2413,7 +2413,22 @@ function FIFO:New() self.uniquecounter = 0 -- Set some string id for output to DCS.log file. self.lid=string.format("%s (%s) | ", "FiFo", self.version) - self:I(self.lid .."Created.") + self:T(self.lid .."Created.") + return self +end + +--- Empty FIFO Stack +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Clear() + self:T(self.lid.."Clear") + self.pointer = 0 + self.counter = 0 + self.stackbypointer = nil + self.stackbyid = nil + self.stackbypointer = {} + self.stackbyid = {} + self.uniquecounter = 0 return self end @@ -2658,7 +2673,22 @@ function LIFO:New() self.stackbyid = {} -- Set some string id for output to DCS.log file. self.lid=string.format("%s (%s) | ", "LiFo", self.version) - self:I(self.lid .."Created.") + self:T(self.lid .."Created.") + return self +end + +--- Empty LIFO Stack +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Clear() + self:T(self.lid.."Clear") + self.pointer = 0 + self.counter = 0 + self.stackbypointer = nil + self.stackbyid = nil + self.stackbypointer = {} + self.stackbyid = {} + self.uniquecounter = 0 return self end From b1a417295f3475dbde3b953da7fd72603d6f28d9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 17 Apr 2022 11:24:36 +0200 Subject: [PATCH 07/22] TextAndSound - Docu --- Moose Development/Moose/Core/TextAndSound.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/TextAndSound.lua b/Moose Development/Moose/Core/TextAndSound.lua index 8a9ea69b5..f56015fe2 100644 --- a/Moose Development/Moose/Core/TextAndSound.lua +++ b/Moose Development/Moose/Core/TextAndSound.lua @@ -1,5 +1,7 @@ --- **Core** - TEXTANDSOUND (MOOSE gettext) system -- +-- === +-- -- ## Main Features: -- -- * A GetText for Moose @@ -18,7 +20,8 @@ -- ## Date: April 2022 -- -- === --- @module Core.TEXTANDSOUND +-- +-- @module Core.TextAndSound -- @image MOOSE.JPG --- Text and Sound class. From 75fddd9349c54f1324123281b86f8cd5da7506ff Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 17 Apr 2022 11:25:48 +0200 Subject: [PATCH 08/22] FIFO/LIFO - Read functions. Count as alternative to GetSize() --- Moose Development/Moose/Utilities/Utils.lua | 69 +++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index b176fce85..cae2cd1a5 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2393,7 +2393,7 @@ do FIFO = { ClassName = "FIFO", lid = "", - version = "0.0.1", + version = "0.0.2", counter = 0, pointer = 0, stackbypointer = {}, @@ -2483,6 +2483,29 @@ function FIFO:PullByPointer(Pointer) return object.data end + +--- FIFO Read, not Pull, Object from Stack by Pointer +-- @param #FIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty or pointer does not exist +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 +end + +--- FIFO Read, not Pull, Object from Stack by UniqueID +-- @param #FIFO self +-- @param #number UniqueID +-- @return #table Object or nil if stack is empty or ID does not exist +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 +end + --- FIFO Pull Object from Stack by UniqueID -- @param #FIFO self -- @param #tableUniqueID @@ -2536,6 +2559,14 @@ function FIFO:GetSize() return self.counter end +--- FIFO Get stack size +-- @param #FIFO self +-- @return #number size +function FIFO:Count() + self:T(self.lid.."Count") + return self.counter +end + --- FIFO Check Stack is NOT empty -- @param #FIFO self -- @return #boolean notempty @@ -2568,7 +2599,7 @@ function FIFO:GetIDStack() return self.stackbyid end ---- FIFO Get table of UniqueIDs sorthed smallest to largest +--- FIFO Get table of UniqueIDs sorted smallest to largest -- @param #FIFO self -- @return #table Table of #FIFO.IDEntry entries function FIFO:GetIDStackSorted() @@ -2653,7 +2684,7 @@ do LIFO = { ClassName = "LIFO", lid = "", - version = "0.0.1", + version = "0.0.2", counter = 0, pointer = 0, stackbypointer = {}, @@ -2743,6 +2774,28 @@ function LIFO:PullByPointer(Pointer) return object.data end +--- LIFO Read, not Pull, Object from Stack by Pointer +-- @param #LIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty or pointer does not exist +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 +end + +--- LIFO Read, not Pull, Object from Stack by UniqueID +-- @param #LIFO self +-- @param #number UniqueID +-- @return #table Object or nil if stack is empty or ID does not exist +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 +end + --- LIFO Pull Object from Stack by UniqueID -- @param #LIFO self -- @param #tableUniqueID @@ -2796,6 +2849,14 @@ function LIFO:GetSize() return self.counter end +--- LIFO Get stack size +-- @param #LIFO self +-- @return #number size +function LIFO:Count() + self:T(self.lid.."Count") + return self.counter +end + --- LIFO Check Stack is NOT empty -- @param #LIFO self -- @return #boolean notempty @@ -2820,7 +2881,7 @@ function LIFO:GetIDStack() return self.stackbyid end ---- LIFO Get table of UniqueIDs sorthed smallest to largest +--- LIFO Get table of UniqueIDs sorted smallest to largest -- @param #LIFO self -- @return #table Table of #LIFO.IDEntry entries function LIFO:GetIDStackSorted() From bc141698c8ec14540c33873e0516f0f2795a5351 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 18 Apr 2022 12:25:58 +0200 Subject: [PATCH 09/22] FIFO - make own file in Utils AUFTRAG - Add base missions params for NewNOTHING Marker - typo Modules - FiFo added UTILS - removed FiFo code --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/Auftrag.lua | 26 +- Moose Development/Moose/Utilities/FiFo.lua | 591 ++++++++++++++++++++ Moose Development/Moose/Utilities/Utils.lua | 581 ------------------- Moose Development/Moose/Wrapper/Marker.lua | 2 +- 5 files changed, 615 insertions(+), 586 deletions(-) create mode 100644 Moose Development/Moose/Utilities/FiFo.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 3b5e08a74..e81fde9af 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -4,6 +4,7 @@ __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' ) +__Moose.Include( 'Scripts/Moose/Utilities/FiFo.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/Beacon.lua' ) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index e22a204a6..905a68bb0 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2031,15 +2031,16 @@ function AUFTRAG:NewNOTHING() --mission:_TargetFromObject(Coordinate) - mission.optionROE=ENUMS.ROE.ReturnFire - mission.optionAlarm=ENUMS.AlarmState.Auto + mission.optionROE=ENUMS.ROE.WeaponHold + mission.optionAlarm=ENUMS.AlarmState.Green mission.missionFraction=1.0 mission.categories={AUFTRAG.Category.ALL} mission.DCStask=mission:GetDCSMissionTask() - + mission.DCStask.params.adinfinitum=true + return mission end @@ -5403,8 +5404,25 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) DCStask.params=param table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.NOTHING then - elseif self.type==AUFTRAG.Type.HOVER then + --------------------- + -- NOTHING Mission -- + --------------------- + + local DCStask={} + + DCStask.id=AUFTRAG.SpecialTask.NOTHING + + -- We create a "fake" DCS task and pass the parameters to the OPSGROUP. + local param={} + + DCStask.params=param + + table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.HOVER then --------------------- -- HOVER Mission -- diff --git a/Moose Development/Moose/Utilities/FiFo.lua b/Moose Development/Moose/Utilities/FiFo.lua new file mode 100644 index 000000000..c2e738468 --- /dev/null +++ b/Moose Development/Moose/Utilities/FiFo.lua @@ -0,0 +1,591 @@ +--- **UTILS** - ClassicFiFo Stack. +-- +-- === +-- +-- ## Main Features: +-- +-- * Build a simple multi-purpose FiFo (First-In, First-Out) stack for generic data. +-- * [Wikipedia](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics) +-- +-- === +-- +-- ### Author: **applevangelist** +-- @module Utils.FiFo +-- @image MOOSE.JPG + +-- Date: April 2022 + +do +--- FIFO class. +-- @type FIFO +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string version Version of FiFo +-- @field #number counter +-- @field #number pointer +-- @field #table stackbypointer +-- @field #table stackbyid +-- @extends Core.Base#BASE + +--- +-- @type FIFO.IDEntry +-- @field #number pointer +-- @field #table data +-- @field #table uniqueID + +--- +-- @field #FIFO +FIFO = { + ClassName = "FIFO", + lid = "", + version = "0.0.2", + counter = 0, + pointer = 0, + stackbypointer = {}, + stackbyid = {} +} + +--- Instantiate a new FIFO Stack +-- @param #FIFO self +-- @return #FIFO self +function FIFO:New() + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) + self.pointer = 0 + self.counter = 0 + self.stackbypointer = {} + self.stackbyid = {} + self.uniquecounter = 0 + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", "FiFo", self.version) + self:T(self.lid .."Created.") + return self +end + +--- Empty FIFO Stack +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Clear() + self:T(self.lid.."Clear") + self.pointer = 0 + self.counter = 0 + self.stackbypointer = nil + self.stackbyid = nil + self.stackbypointer = {} + self.stackbyid = {} + self.uniquecounter = 0 + return self +end + +--- FIFO Push Object to Stack +-- @param #FIFO self +-- @param #table Object +-- @param #string UniqueID (optional) - will default to current pointer + 1 +-- @return #FIFO self +function FIFO:Push(Object,UniqueID) + self:T(self.lid.."Push") + self:T({Object,UniqueID}) + self.pointer = self.pointer + 1 + self.counter = self.counter + 1 + local uniID = UniqueID + if not UniqueID then + self.uniquecounter = self.uniquecounter + 1 + uniID = self.uniquecounter + end + self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } + return self +end + +--- FIFO Pull Object from Stack +-- @param #FIFO self +-- @return #table Object or nil if stack is empty +function FIFO:Pull() + self:T(self.lid.."Pull") + if self.counter == 0 then return nil end + --local object = self.stackbypointer[self.pointer].data + --self.stackbypointer[self.pointer] = nil + local object = self.stackbypointer[1].data + self.stackbypointer[1] = nil + self.counter = self.counter - 1 + --self.pointer = self.pointer - 1 + self:Flatten() + return object +end + +--- FIFO Pull Object from Stack by Pointer +-- @param #FIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty +function FIFO:PullByPointer(Pointer) + self:T(self.lid.."PullByPointer " .. tostring(Pointer)) + if self.counter == 0 then return nil end + local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry + self.stackbypointer[Pointer] = nil + if object then self.stackbyid[object.uniqueID] = nil end + self.counter = self.counter - 1 + self:Flatten() + if object then + return object.data + else + return nil + end +end + + +--- FIFO Read, not Pull, Object from Stack by Pointer +-- @param #FIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty or pointer does not exist +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 +end + +--- FIFO Read, not Pull, Object from Stack by UniqueID +-- @param #FIFO self +-- @param #number UniqueID +-- @return #table Object or nil if stack is empty or ID does not exist +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 +end + +--- FIFO Pull Object from Stack by UniqueID +-- @param #FIFO self +-- @param #tableUniqueID +-- @return #table Object or nil if stack is empty +function FIFO:PullByID(UniqueID) + self:T(self.lid.."PullByID " .. tostring(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) +end + +--- FIFO Housekeeping +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Flatten() + self:T(self.lid.."Flatten") + -- rebuild stacks + local pointerstack = {} + local idstack = {} + local counter = 0 + for _ID,_entry in pairs(self.stackbypointer) do + counter = counter + 1 + pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} + end + for _ID,_entry in pairs(pointerstack) do + idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} + end + self.stackbypointer = nil + self.stackbypointer = pointerstack + self.stackbyid = nil + self.stackbyid = idstack + self.counter = counter + self.pointer = counter + return self +end + +--- FIFO Check Stack is empty +-- @param #FIFO self +-- @return #boolean empty +function FIFO:IsEmpty() + self:T(self.lid.."IsEmpty") + return self.counter == 0 and true or false +end + +--- FIFO Get stack size +-- @param #FIFO self +-- @return #number size +function FIFO:GetSize() + self:T(self.lid.."GetSize") + return self.counter +end + +--- FIFO Get stack size +-- @param #FIFO self +-- @return #number size +function FIFO:Count() + self:T(self.lid.."Count") + return self.counter +end + +--- FIFO Check Stack is NOT empty +-- @param #FIFO self +-- @return #boolean notempty +function FIFO:IsNotEmpty() + self:T(self.lid.."IsNotEmpty") + return not self:IsEmpty() +end + +--- FIFO Get the data stack by pointer +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetPointerStack() + self:T(self.lid.."GetPointerStack") + return self.stackbypointer +end + +--- FIFO Check if a certain UniqeID exists +-- @param #FIFO self +-- @return #boolean exists +function FIFO:HasUniqueID(UniqueID) + self:T(self.lid.."HasUniqueID") + return self.stackbyid[UniqueID] and true or false +end + +--- FIFO Get the data stack by UniqueID +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetIDStack() + self:T(self.lid.."GetIDStack") + return self.stackbyid +end + +--- FIFO Get table of UniqueIDs sorted smallest to largest +-- @param #FIFO self +-- @return #table Table of #FIFO.IDEntry entries +function FIFO:GetIDStackSorted() + self:T(self.lid.."GetIDStackSorted") + + local stack = self:GetIDStack() + local idstack = {} + for _id,_entry in pairs(stack) do + idstack[#idstack+1] = _id + + self:T({"pre",_id}) + end + + local function sortID(a, b) + return a < b + end + + table.sort(idstack) + + return idstack +end + +--- FIFO Print stacks to dcs.log +-- @param #FIFO self +-- @return #FIFO self +function FIFO:Flush() + self:T(self.lid.."FiFo Flush") + self:I("FIFO Flushing Stack by Pointer") + for _id,_data in pairs (self.stackbypointer) do + local data = _data -- #FIFO.IDEntry + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("FIFO Flushing Stack by ID") + for _id,_data in pairs (self.stackbyid) do + local data = _data -- #FIFO.IDEntry + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("Counter = " .. self.counter) + self:I("Pointer = ".. self.pointer) + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End FIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- LIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +do +--- **UTILS** - LiFo Stack. +-- +-- **Main Features:** +-- +-- * Build a simple multi-purpose LiFo (Last-In, First-Out) stack for generic data. +-- +-- === +-- +-- ### Author: **applevangelist** + +--- LIFO class. +-- @type LIFO +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string version Version of LiFo +-- @field #number counter +-- @field #number pointer +-- @field #table stackbypointer +-- @field #table stackbyid +-- @extends Core.Base#BASE + +--- +-- @type LIFO.IDEntry +-- @field #number pointer +-- @field #table data +-- @field #table uniqueID + +--- +-- @field #LIFO +LIFO = { + ClassName = "LIFO", + lid = "", + version = "0.0.2", + counter = 0, + pointer = 0, + stackbypointer = {}, + stackbyid = {} +} + +--- Instantiate a new LIFO Stack +-- @param #LIFO self +-- @return #LIFO self +function LIFO:New() + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) + self.pointer = 0 + self.counter = 0 + self.uniquecounter = 0 + self.stackbypointer = {} + self.stackbyid = {} + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", "LiFo", self.version) + self:T(self.lid .."Created.") + return self +end + +--- Empty LIFO Stack +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Clear() + self:T(self.lid.."Clear") + self.pointer = 0 + self.counter = 0 + self.stackbypointer = nil + self.stackbyid = nil + self.stackbypointer = {} + self.stackbyid = {} + self.uniquecounter = 0 + return self +end + +--- LIFO Push Object to Stack +-- @param #LIFO self +-- @param #table Object +-- @param #string UniqueID (optional) - will default to current pointer + 1 +-- @return #LIFO self +function LIFO:Push(Object,UniqueID) + self:T(self.lid.."Push") + self:T({Object,UniqueID}) + self.pointer = self.pointer + 1 + self.counter = self.counter + 1 + local uniID = UniqueID + if not UniqueID then + self.uniquecounter = self.uniquecounter + 1 + uniID = self.uniquecounter + end + self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } + self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } + return self +end + +--- LIFO Pull Object from Stack +-- @param #LIFO self +-- @return #table Object or nil if stack is empty +function LIFO:Pull() + self:T(self.lid.."Pull") + if self.counter == 0 then return nil end + local object = self.stackbypointer[self.pointer].data + self.stackbypointer[self.pointer] = nil + --local object = self.stackbypointer[1].data + --self.stackbypointer[1] = nil + self.counter = self.counter - 1 + self.pointer = self.pointer - 1 + self:Flatten() + return object +end + +--- LIFO Pull Object from Stack by Pointer +-- @param #LIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty +function LIFO:PullByPointer(Pointer) + self:T(self.lid.."PullByPointer " .. tostring(Pointer)) + if self.counter == 0 then return nil end + local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry + self.stackbypointer[Pointer] = nil + if object then self.stackbyid[object.uniqueID] = nil end + self.counter = self.counter - 1 + self:Flatten() + if object then + return object.data + else + return nil + end +end + +--- LIFO Read, not Pull, Object from Stack by Pointer +-- @param #LIFO self +-- @param #number Pointer +-- @return #table Object or nil if stack is empty or pointer does not exist +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 +end + +--- LIFO Read, not Pull, Object from Stack by UniqueID +-- @param #LIFO self +-- @param #number UniqueID +-- @return #table Object or nil if stack is empty or ID does not exist +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 +end + +--- LIFO Pull Object from Stack by UniqueID +-- @param #LIFO self +-- @param #tableUniqueID +-- @return #table Object or nil if stack is empty +function LIFO:PullByID(UniqueID) + self:T(self.lid.."PullByID " .. tostring(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) +end + +--- LIFO Housekeeping +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Flatten() + self:T(self.lid.."Flatten") + -- rebuild stacks + local pointerstack = {} + local idstack = {} + local counter = 0 + for _ID,_entry in pairs(self.stackbypointer) do + counter = counter + 1 + pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} + end + for _ID,_entry in pairs(pointerstack) do + idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} + end + self.stackbypointer = nil + self.stackbypointer = pointerstack + self.stackbyid = nil + self.stackbyid = idstack + self.counter = counter + self.pointer = counter + return self +end + +--- LIFO Check Stack is empty +-- @param #LIFO self +-- @return #boolean empty +function LIFO:IsEmpty() + self:T(self.lid.."IsEmpty") + return self.counter == 0 and true or false +end + +--- LIFO Get stack size +-- @param #LIFO self +-- @return #number size +function LIFO:GetSize() + self:T(self.lid.."GetSize") + return self.counter +end + +--- LIFO Get stack size +-- @param #LIFO self +-- @return #number size +function LIFO:Count() + self:T(self.lid.."Count") + return self.counter +end + +--- LIFO Check Stack is NOT empty +-- @param #LIFO self +-- @return #boolean notempty +function LIFO:IsNotEmpty() + self:T(self.lid.."IsNotEmpty") + return not self:IsEmpty() +end + +--- LIFO Get the data stack by pointer +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetPointerStack() + self:T(self.lid.."GetPointerStack") + return self.stackbypointer +end + +--- LIFO Get the data stack by UniqueID +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetIDStack() + self:T(self.lid.."GetIDStack") + return self.stackbyid +end + +--- LIFO Get table of UniqueIDs sorted smallest to largest +-- @param #LIFO self +-- @return #table Table of #LIFO.IDEntry entries +function LIFO:GetIDStackSorted() + self:T(self.lid.."GetIDStackSorted") + + local stack = self:GetIDStack() + local idstack = {} + for _id,_entry in pairs(stack) do + idstack[#idstack+1] = _id + + self:T({"pre",_id}) + end + + local function sortID(a, b) + return a < b + end + + table.sort(idstack) + + return idstack +end + +--- LIFO Check if a certain UniqeID exists +-- @param #LIFO self +-- @return #boolean exists +function LIFO:HasUniqueID(UniqueID) + self:T(self.lid.."HasUniqueID") + return self.stackbyid[UniqueID] and true or false +end + +--- LIFO Print stacks to dcs.log +-- @param #LIFO self +-- @return #LIFO self +function LIFO:Flush() + self:T(self.lid.."FiFo Flush") + self:I("LIFO Flushing Stack by Pointer") + for _id,_data in pairs (self.stackbypointer) do + local data = _data -- #LIFO.IDEntry + self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("LIFO Flushing Stack by ID") + for _id,_data in pairs (self.stackbyid) do + local data = _data -- #LIFO.IDEntry + self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) + end + self:I("Counter = " .. self.counter) + self:I("Pointer = ".. self.pointer) + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End LIFO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end \ No newline at end of file diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index cae2cd1a5..54a58639c 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2355,584 +2355,3 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) end return datatable end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- FIFO -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -do ---- **UTILS** - FiFo Stack. --- --- **Main Features:** --- --- * Build a simple multi-purpose FiFo (First-In, First-Out) stack for generic data. --- --- === --- --- ### Author: **applevangelist** - ---- FIFO class. --- @type FIFO --- @field #string ClassName Name of the class. --- @field #string lid Class id string for output to DCS log file. --- @field #string version Version of FiFo --- @field #number counter --- @field #number pointer --- @field #table stackbypointer --- @field #table stackbyid --- @extends Core.Base#BASE - ---- --- @type FIFO.IDEntry --- @field #number pointer --- @field #table data --- @field #table uniqueID - ---- --- @field #FIFO -FIFO = { - ClassName = "FIFO", - lid = "", - version = "0.0.2", - counter = 0, - pointer = 0, - stackbypointer = {}, - stackbyid = {} -} - ---- Instantiate a new FIFO Stack --- @param #FIFO self --- @return #FIFO self -function FIFO:New() - -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) - self.pointer = 0 - self.counter = 0 - self.stackbypointer = {} - self.stackbyid = {} - self.uniquecounter = 0 - -- Set some string id for output to DCS.log file. - self.lid=string.format("%s (%s) | ", "FiFo", self.version) - self:T(self.lid .."Created.") - return self -end - ---- Empty FIFO Stack --- @param #FIFO self --- @return #FIFO self -function FIFO:Clear() - self:T(self.lid.."Clear") - self.pointer = 0 - self.counter = 0 - self.stackbypointer = nil - self.stackbyid = nil - self.stackbypointer = {} - self.stackbyid = {} - self.uniquecounter = 0 - return self -end - ---- FIFO Push Object to Stack --- @param #FIFO self --- @param #table Object --- @param #string UniqueID (optional) - will default to current pointer + 1 --- @return #FIFO self -function FIFO:Push(Object,UniqueID) - self:T(self.lid.."Push") - self:T({Object,UniqueID}) - self.pointer = self.pointer + 1 - self.counter = self.counter + 1 - local uniID = UniqueID - if not UniqueID then - self.uniquecounter = self.uniquecounter + 1 - uniID = self.uniquecounter - end - self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } - self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } - return self -end - ---- FIFO Pull Object from Stack --- @param #FIFO self --- @return #table Object or nil if stack is empty -function FIFO:Pull() - self:T(self.lid.."Pull") - if self.counter == 0 then return nil end - --local object = self.stackbypointer[self.pointer].data - --self.stackbypointer[self.pointer] = nil - local object = self.stackbypointer[1].data - self.stackbypointer[1] = nil - self.counter = self.counter - 1 - --self.pointer = self.pointer - 1 - self:Flatten() - return object -end - ---- FIFO Pull Object from Stack by Pointer --- @param #FIFO self --- @param #number Pointer --- @return #table Object or nil if stack is empty -function FIFO:PullByPointer(Pointer) - self:T(self.lid.."PullByPointer " .. tostring(Pointer)) - if self.counter == 0 then return nil end - local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry - self.stackbypointer[Pointer] = nil - self.stackbyid[object.uniqueID] = nil - self.counter = self.counter - 1 - self:Flatten() - return object.data -end - - ---- FIFO Read, not Pull, Object from Stack by Pointer --- @param #FIFO self --- @param #number Pointer --- @return #table Object or nil if stack is empty or pointer does not exist -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 -end - ---- FIFO Read, not Pull, Object from Stack by UniqueID --- @param #FIFO self --- @param #number UniqueID --- @return #table Object or nil if stack is empty or ID does not exist -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 -end - ---- FIFO Pull Object from Stack by UniqueID --- @param #FIFO self --- @param #tableUniqueID --- @return #table Object or nil if stack is empty -function FIFO:PullByID(UniqueID) - self:T(self.lid.."PullByID " .. tostring(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) -end - ---- FIFO Housekeeping --- @param #FIFO self --- @return #FIFO self -function FIFO:Flatten() - self:T(self.lid.."Flatten") - -- rebuild stacks - local pointerstack = {} - local idstack = {} - local counter = 0 - for _ID,_entry in pairs(self.stackbypointer) do - counter = counter + 1 - pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} - end - for _ID,_entry in pairs(pointerstack) do - idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} - end - self.stackbypointer = nil - self.stackbypointer = pointerstack - self.stackbyid = nil - self.stackbyid = idstack - self.counter = counter - self.pointer = counter - return self -end - ---- FIFO Check Stack is empty --- @param #FIFO self --- @return #boolean empty -function FIFO:IsEmpty() - self:T(self.lid.."IsEmpty") - return self.counter == 0 and true or false -end - ---- FIFO Get stack size --- @param #FIFO self --- @return #number size -function FIFO:GetSize() - self:T(self.lid.."GetSize") - return self.counter -end - ---- FIFO Get stack size --- @param #FIFO self --- @return #number size -function FIFO:Count() - self:T(self.lid.."Count") - return self.counter -end - ---- FIFO Check Stack is NOT empty --- @param #FIFO self --- @return #boolean notempty -function FIFO:IsNotEmpty() - self:T(self.lid.."IsNotEmpty") - return not self:IsEmpty() -end - ---- FIFO Get the data stack by pointer --- @param #FIFO self --- @return #table Table of #FIFO.IDEntry entries -function FIFO:GetPointerStack() - self:T(self.lid.."GetPointerStack") - return self.stackbypointer -end - ---- FIFO Check if a certain UniqeID exists --- @param #FIFO self --- @return #boolean exists -function FIFO:HasUniqueID(UniqueID) - self:T(self.lid.."HasUniqueID") - return self.stackbyid[UniqueID] and true or false -end - ---- FIFO Get the data stack by UniqueID --- @param #FIFO self --- @return #table Table of #FIFO.IDEntry entries -function FIFO:GetIDStack() - self:T(self.lid.."GetIDStack") - return self.stackbyid -end - ---- FIFO Get table of UniqueIDs sorted smallest to largest --- @param #FIFO self --- @return #table Table of #FIFO.IDEntry entries -function FIFO:GetIDStackSorted() - self:T(self.lid.."GetIDStackSorted") - - local stack = self:GetIDStack() - local idstack = {} - for _id,_entry in pairs(stack) do - idstack[#idstack+1] = _id - - self:T({"pre",_id}) - end - - local function sortID(a, b) - return a < b - end - - table.sort(idstack) - - return idstack -end - ---- FIFO Print stacks to dcs.log --- @param #FIFO self --- @return #FIFO self -function FIFO:Flush() - self:T(self.lid.."FiFo Flush") - self:I("FIFO Flushing Stack by Pointer") - for _id,_data in pairs (self.stackbypointer) do - local data = _data -- #FIFO.IDEntry - self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) - end - self:I("FIFO Flushing Stack by ID") - for _id,_data in pairs (self.stackbyid) do - local data = _data -- #FIFO.IDEntry - self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) - end - self:I("Counter = " .. self.counter) - self:I("Pointer = ".. self.pointer) - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- End FIFO -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- LIFO -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -do ---- **UTILS** - LiFo Stack. --- --- **Main Features:** --- --- * Build a simple multi-purpose LiFo (Last-In, First-Out) stack for generic data. --- --- === --- --- ### Author: **applevangelist** - ---- LIFO class. --- @type LIFO --- @field #string ClassName Name of the class. --- @field #string lid Class id string for output to DCS log file. --- @field #string version Version of LiFo --- @field #number counter --- @field #number pointer --- @field #table stackbypointer --- @field #table stackbyid --- @extends Core.Base#BASE - ---- --- @type LIFO.IDEntry --- @field #number pointer --- @field #table data --- @field #table uniqueID - ---- --- @field #LIFO -LIFO = { - ClassName = "LIFO", - lid = "", - version = "0.0.2", - counter = 0, - pointer = 0, - stackbypointer = {}, - stackbyid = {} -} - ---- Instantiate a new LIFO Stack --- @param #LIFO self --- @return #LIFO self -function LIFO:New() - -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) - self.pointer = 0 - self.counter = 0 - self.uniquecounter = 0 - self.stackbypointer = {} - self.stackbyid = {} - -- Set some string id for output to DCS.log file. - self.lid=string.format("%s (%s) | ", "LiFo", self.version) - self:T(self.lid .."Created.") - return self -end - ---- Empty LIFO Stack --- @param #LIFO self --- @return #LIFO self -function LIFO:Clear() - self:T(self.lid.."Clear") - self.pointer = 0 - self.counter = 0 - self.stackbypointer = nil - self.stackbyid = nil - self.stackbypointer = {} - self.stackbyid = {} - self.uniquecounter = 0 - return self -end - ---- LIFO Push Object to Stack --- @param #LIFO self --- @param #table Object --- @param #string UniqueID (optional) - will default to current pointer + 1 --- @return #LIFO self -function LIFO:Push(Object,UniqueID) - self:T(self.lid.."Push") - self:T({Object,UniqueID}) - self.pointer = self.pointer + 1 - self.counter = self.counter + 1 - local uniID = UniqueID - if not UniqueID then - self.uniquecounter = self.uniquecounter + 1 - uniID = self.uniquecounter - end - self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID } - self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID } - return self -end - ---- LIFO Pull Object from Stack --- @param #LIFO self --- @return #table Object or nil if stack is empty -function LIFO:Pull() - self:T(self.lid.."Pull") - if self.counter == 0 then return nil end - local object = self.stackbypointer[self.pointer].data - self.stackbypointer[self.pointer] = nil - --local object = self.stackbypointer[1].data - --self.stackbypointer[1] = nil - self.counter = self.counter - 1 - self.pointer = self.pointer - 1 - self:Flatten() - return object -end - ---- LIFO Pull Object from Stack by Pointer --- @param #LIFO self --- @param #number Pointer --- @return #table Object or nil if stack is empty -function LIFO:PullByPointer(Pointer) - self:T(self.lid.."PullByPointer " .. tostring(Pointer)) - if self.counter == 0 then return nil end - local object = self.stackbypointer[Pointer] -- #LIFO.IDEntry - self.stackbypointer[Pointer] = nil - self.stackbyid[object.uniqueID] = nil - self.counter = self.counter - 1 - self:Flatten() - return object.data -end - ---- LIFO Read, not Pull, Object from Stack by Pointer --- @param #LIFO self --- @param #number Pointer --- @return #table Object or nil if stack is empty or pointer does not exist -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 -end - ---- LIFO Read, not Pull, Object from Stack by UniqueID --- @param #LIFO self --- @param #number UniqueID --- @return #table Object or nil if stack is empty or ID does not exist -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 -end - ---- LIFO Pull Object from Stack by UniqueID --- @param #LIFO self --- @param #tableUniqueID --- @return #table Object or nil if stack is empty -function LIFO:PullByID(UniqueID) - self:T(self.lid.."PullByID " .. tostring(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) -end - ---- LIFO Housekeeping --- @param #LIFO self --- @return #LIFO self -function LIFO:Flatten() - self:T(self.lid.."Flatten") - -- rebuild stacks - local pointerstack = {} - local idstack = {} - local counter = 0 - for _ID,_entry in pairs(self.stackbypointer) do - counter = counter + 1 - pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID} - end - for _ID,_entry in pairs(pointerstack) do - idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID} - end - self.stackbypointer = nil - self.stackbypointer = pointerstack - self.stackbyid = nil - self.stackbyid = idstack - self.counter = counter - self.pointer = counter - return self -end - ---- LIFO Check Stack is empty --- @param #LIFO self --- @return #boolean empty -function LIFO:IsEmpty() - self:T(self.lid.."IsEmpty") - return self.counter == 0 and true or false -end - ---- LIFO Get stack size --- @param #LIFO self --- @return #number size -function LIFO:GetSize() - self:T(self.lid.."GetSize") - return self.counter -end - ---- LIFO Get stack size --- @param #LIFO self --- @return #number size -function LIFO:Count() - self:T(self.lid.."Count") - return self.counter -end - ---- LIFO Check Stack is NOT empty --- @param #LIFO self --- @return #boolean notempty -function LIFO:IsNotEmpty() - self:T(self.lid.."IsNotEmpty") - return not self:IsEmpty() -end - ---- LIFO Get the data stack by pointer --- @param #LIFO self --- @return #table Table of #LIFO.IDEntry entries -function LIFO:GetPointerStack() - self:T(self.lid.."GetPointerStack") - return self.stackbypointer -end - ---- LIFO Get the data stack by UniqueID --- @param #LIFO self --- @return #table Table of #LIFO.IDEntry entries -function LIFO:GetIDStack() - self:T(self.lid.."GetIDStack") - return self.stackbyid -end - ---- LIFO Get table of UniqueIDs sorted smallest to largest --- @param #LIFO self --- @return #table Table of #LIFO.IDEntry entries -function LIFO:GetIDStackSorted() - self:T(self.lid.."GetIDStackSorted") - - local stack = self:GetIDStack() - local idstack = {} - for _id,_entry in pairs(stack) do - idstack[#idstack+1] = _id - - self:T({"pre",_id}) - end - - local function sortID(a, b) - return a < b - end - - table.sort(idstack) - - return idstack -end - ---- LIFO Check if a certain UniqeID exists --- @param #LIFO self --- @return #boolean exists -function LIFO:HasUniqueID(UniqueID) - self:T(self.lid.."HasUniqueID") - return self.stackbyid[UniqueID] and true or false -end - ---- LIFO Print stacks to dcs.log --- @param #LIFO self --- @return #LIFO self -function LIFO:Flush() - self:T(self.lid.."FiFo Flush") - self:I("LIFO Flushing Stack by Pointer") - for _id,_data in pairs (self.stackbypointer) do - local data = _data -- #LIFO.IDEntry - self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) - end - self:I("LIFO Flushing Stack by ID") - for _id,_data in pairs (self.stackbyid) do - local data = _data -- #LIFO.IDEntry - self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID))) - end - self:I("Counter = " .. self.counter) - self:I("Pointer = ".. self.pointer) - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- End LIFO -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -end \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 6dfcd4f71..3dbffa91e 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -588,7 +588,7 @@ end --- Set text that is displayed in the marker panel. Note this does not show the marker. -- @param #MARKER self --- @param #string Text Marker text. Default is an empty sting "". +-- @param #string Text Marker text. Default is an empty string "". -- @return #MARKER self function MARKER:SetText(Text) self.text=Text and tostring(Text) or "" From bada6f64ae486ced110e6c6fefa8eacaf2e905a8 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:27:58 +0200 Subject: [PATCH 10/22] Update Moose.files (#1713) --- Moose Setup/Moose.files | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 33a49c594..e6dddf6ee 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -5,6 +5,7 @@ Utilities/Enums.lua Utilities/Profiler.lua Utilities/Templates.lua Utilities/STTS.lua +Utilities/FiFo.lua Core/Base.lua Core/Beacon.lua From 2033e22216484cdd9070cc83fe214c5d2863f855 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 18 Apr 2022 16:57:33 +0200 Subject: [PATCH 11/22] AWACS Alpha 0.0.2 (WIP) (#1714) TBD --- Moose Development/Moose/Ops/Awacs.lua | 2316 +++++++++++++++++++++++++ 1 file changed, 2316 insertions(+) create mode 100644 Moose Development/Moose/Ops/Awacs.lua diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua new file mode 100644 index 000000000..1eed0457a --- /dev/null +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -0,0 +1,2316 @@ +--- **Ops** - AWACS +-- +-- === +-- +-- ## Main Features: +-- +-- * TBD +-- +-- === +-- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/). +-- +-- === +-- +-- ### Author: **applevangelist** +-- Last Update April 2022 +-- +-- === +-- @module Ops.AWACS +-- @image MOOSE.JPG + +do +--- Ops AWACS Class +-- @type AWACS +-- @field #string ClassName Name of this class. +-- @field #string version Versioning. +-- @field #string lid LID for log entries. +-- @field #number coalition Colition side. +-- @field #string coalitiontxt = "blue" +-- @field Core.Zone#ZONE OpsZone, +-- @field Core.Zone#ZONE AnchorZone, +-- @field #number Frequency +-- @field #number Modulation +-- @field Wrapper.Airbase#AIRBASE Airbase +-- @field Ops.AirWing#AIRWING AirWing +-- @field #number AwacsAngels +-- @field Core.Zone#ZONE OrbitZone +-- @field #number CallSign +-- @field #number CallSignNo +-- @field #boolean debug +-- @field #number verbose +-- @field #table ManagedGrps +-- @field #number ManagedGrpID +-- @field #number ManagedTaskID +-- @field Utils.FiFo#FIFO AnchorStacks +-- @field Utils.FiFo#FIFO CAPIdleAI +-- @field Utils.FiFo#FIFO CAPIdleHuman +-- @field Utils.FiFo#FIFO TaskedCAPAI +-- @field Utils.FiFo#FIFO TaskedCAPHuman +-- @field Utils.FiFo#FIFO OpenTasks +-- @field Utils.FiFo#FIFO AssignedTasks +-- @field Utils.FiFo#FIFO PictureAO +-- @field Utils.FiFo#FIFO PictureEWR +-- @field Utils.FiFo#FIFO Contacts +-- @field Utils.FiFo#FIFO ContactsAO +-- @field Utils.FiFo#FIFO RadioQueue +-- @field #number AwacsTimeOnStation +-- @field #number AwacsTimeStamp +-- @field #number EscortsTimeOnStation +-- @field #number EscortsTimeStamp +-- @field #string AwacsROE +-- @field #string AwacsROT +-- @field Ops.Auftrag#AUFTRAG AwacsMission +-- @field Ops.Auftrag#AUFTRAG EscortMission +-- @field Utils.FiFo#FIFO AICAPMissions FIFO for Ops.Auftrag#AUFTRAG for AI CAP +-- @field #boolean MenuStrict +-- @field #number MaxAIonCAP +-- @field #number AIonCAP +-- @extends Core.Fsm#FSM + +--- +-- +-- @field #AWACS +AWACS = { + ClassName = "AWACS", -- #string + version = "alpha 0.0.2", -- #string + lid = "", -- #string + coalition = coalition.side.BLUE, -- #number + coalitiontxt = "blue", -- #string + OpsZone = nil, + AnchorZone = nil, + AirWing = nil, + Frequency = 271, -- #number + Modulation = radio.modulation.AM, -- #number + Airbase = nil, + AwacsAngels = 25, -- orbit at 25'000 ft + OrbitZone = nil, + CallSign = CALLSIGN.AWACS.Magic, -- #number + CallSignNo = 1, -- #number + debug = true, + verbose = true, + ManagedGrps = {}, + ManagedGrpID = 0, -- #number + ManagedTaskID = 0, -- #number + AnchorStacks = {}, -- Utils.FiFo#FIFO + CAPIdleAI = {}, + CAPIdleHuman = {}, + TaskedCAPAI = {}, + TaskedCAPHuman = {}, + OpenTasks = {}, -- Utils.FiFo#FIFO + AssignedTasks = {}, -- Utils.FiFo#FIFO + PictureAO = {}, -- Utils.FiFo#FIFO + PictureEWR = {}, -- Utils.FiFo#FIFO + Contacts = {}, -- Utils.FiFo#FIFO + ContactsAO = {}, -- Utils.FiFo#FIFO + RadioQueue = {}, -- Utils.FiFo#FIFO + AwacsTimeOnStation = 1, + AwacsTimeStamp = 0, + EscortsTimeOnStation = 0.5, + EscortsTimeStamp = 0, + AwacsROE = "", + AwacsROT = "", + MenuStrict = true, + MaxAIonCAP = 4, + AIonCAP = 0, + AICAPMissions = {}, -- Utils.FiFo#FIFO +} + +--- +--@field CallSignClear +AWACS.CallSignClear = { + [1]="Overlord", + [2]="Magic", + [3]="Wizard", + [4]="Focus", + [5]="Darkstar", +} + +--- +-- @field AnchorNames +AWACS.AnchorNames = { + [1] = "One", + [2] = "Two", + [3] = "Three", + [4] = "Four", + [5] = "Five", + [6] = "Six", + [7] = "Seven", + [8] = "Eight", + [9] = "Nine", + [10] = "Ten", +} + +--- +-- @field ROE +AWACS.ROE = { + POLICE = "Police", + VID = "Visual ID", + IFF = "IFF", + BVR = "Beyond Visual Range", +} + +--- +-- @field AWACS.ROT +AWACS.ROT = { + PASSIVE = "Passive Defense", + ACTIVE = "Active Defense", + LOCK = "Lock", + RETURNFIRE = "Return Fire", + OPENFIRE = "Open Fire", + } + +--- +--@field THREATLEVEL -- can be 1-10, thresholds +AWACS.THREATLEVEL = { + GREEN = 3, + AMBER = 7, + RED = 10, +} + +--- +-- @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_COMMAND showtask + +--- +-- @type AWACS.ManagedGroup +-- @field Wrapper.Group#GROUP Group +-- @field #string GroupName +-- @field Ops.FlightGroup#FLIGHTGROUP FlightGroup for AI +-- @field #boolean IsPlayer +-- @field #boolean IsAI +-- @field #string CallSign +-- @field #number CurrentTask +-- @field #boolean AssignedTask +-- @field #number ID +-- @field #number AnchorStackNo +-- @field #number AnchorStackAngels + +--- +-- @type AWACS.TaskDescription +AWACS.TaskDescription = { + ANCHOR = "Anchor", + REANCHOR = "Re-Anchor", + INTERCEPT = "Intercept", + SWEEP = "Sweep", + RTB = "RTB", +} + +--- +-- @type AWACS.TaskStatus +AWACS.TaskStatus = { + IDLE = "Idle", + ASSIGNED = "Assigned", + EXECUTING = "Executing", + SUCCESS = "Success", + FAILED = "Failed", +} + +--- +-- @type AWACS.ManagedTask +-- @field #number ID +-- @field #number AssignedGroupID +-- @field #boolean IsPlayerTask +-- @field Ops.Target#TARGET Target +-- @field Ops.Auftrag#AUFTRAG Auftrag +-- @field #AWACS.TaskStatus Status +-- @field #AWACS.TaskDescription ToDo +-- @field #string ScreenText Long descrition +-- @field Ops.Intelligence#INTEL.Contact Contact +-- @field Ops.Intelligence#INTEL.Cluster Cluster + +--- +-- @type AWACS.AnchorAssignedEntry +-- @field #number ID +-- @field #number Angels + +--- +-- @type AWACS.AnchorData +-- @field #number AnchorBaseAngels +-- @field #boolean AnchorZone +-- @field Core.Point#COORDINATE AnchorZoneCoordinate +-- @field #boolean AnchorZoneCoordinateText +-- @field Utils.FiFo#FIFO AnchorAssignedID FiFo of #AWACS.AnchorAssignedEntry +-- @field Utils.FiFo#FIFO Anchors FiFo of available stacks + +--- +--@type RadioEntry +--@field #string TextTTS +--@field #string TextScreen +--@field #boolean IsNew +--@field #boolean IsGroup +--@field #boolean GroupID +--@field #number Duration +--@field #boolean ToScreen + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO-List +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DEBUG - Escorts via AirWing not staying on +-- TODO - Link (multiple) AWs to the AWACS Controller +-- 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 - Radio Menu +-- DONE - Intel Detection +-- TODO - CHIEF / COMMANDER / AIRWING connection? +-- TODO - LotATC / IFF +-- TODO - ROE +-- TODO - Player tasking +-- TODO - Stack Management +-- TODO - Reporting +-- TODO - Missile launch callout +-- TODO - Localization +-- TODO - Shift Length AWACS/AI +-- TODO - 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 +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO Constructor + +--- Set up a new AI AWACS. +-- @param #AWACS self +-- @param #string Name Name of this AWACS for the radio menu. +-- @param #string AirWing The core Ops.AirWing#AIRWING managing the AWACS, Escort and (optionally) AI CAP planes for us. +-- @param #number Coalition Coalition, e.g. coalition.side.BLUE. Can also be passed as "blue", "red" or "neutral". +-- @param #string AirbaseName Name of the home airbase. +-- @param #string AwacsOrbit Name of the round, mission editor created zone where this AWACS orbits. +-- @param #string OpsZone Name of the round, mission editor created operations zone this AWACS controls. Can be passed as #ZONE_POLYGON +-- @param #string AnchorZone Name of the round, mission editor created anchor zone where CAP groups will be stacked. +-- @param #number Frequency Radio frequency, e.g. 271. +-- @param #number Modulation Radio modulation, e.g. radio.modulation.AM or radio.modulation.FM. +-- @return #AWACS self +function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZone,Frequency,Modulation) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E("ERROR: Unknown coalition in AWACS!") + end + else + self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) + end + + -- base setup + self.Name = Name -- #string + self.AirWing = AirWing -- Ops.AirWing#AIRWING object + self.AwacsFG = nil + --self.AwacsPayload = PayLoad -- Ops.AirWing#AIRWING.Payload + self.ModernEra = true -- use of EPLRS + self.RadarBlur = 10 -- 10% detection precision i.e. 90-110 reported group size + if type(OpsZone) == "string" then + self.OpsZone = ZONE:New(OpsZone) -- Core.Zone#ZONE + elseif type(OpsZone) == "table" and OpsZone.ClassName and OpsZone.ClassName == "ZONE_POLYGON" then + self.OpsZone = OpsZone + else + self:E("AWACS - Invalid OpsZone passed!") + return + end + + self.AOCoordinate = self.OpsZone:GetCoordinate() + self.UseBullsAO = false + 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.CallSign = CALLSIGN.AWACS.Magic -- #number + self.CallSignNo = 1 -- #number + self.NoHelos = true + self.MaxAIonCAP = 4 + self.AIonCAP = 0 + self.AICAPMissions = FIFO:New() -- Utils.FiFo#FIFO + + local speed = 250 + self.SpeedBase = speed + self.Speed = UTILS.KnotsToAltKIAS(speed,self.AwacsAngels*1000) + self.CapSpeedBase = 200 + self.Heading = 0 -- north + self.Leg = 50 -- nm + self.invisible = true + self.immortal = true + self.callsigntxt = "AWACS" + + self.AwacsTimeOnStation = 2 + self.AwacsTimeStamp = 0 + self.EscortsTimeOnStation = 2 + self.EscortsTimeStamp = 0 + self.ShiftChangeTime = 0.5 + + self.AwacsROE = AWACS.ROE.POLICE + self.AwacsROT = AWACS.ROT.PASSIVE + + self.MenuStrict = true + + -- Escorts + self.HasEscorts = false + self.EscortTemplate = "" + + -- SRS + self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.Gender = "male" + self.Culture = "en-US" + self.Voice = nil + self.Port = 5002 + self.clientset = SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(self.coalitiontxt):FilterStart() + self.RadioQueue = FIFO:New() -- Utils.FiFo#FIFO + self.maxspeakentries = 3 + + -- managed groups + self.ManagedGrps = {} -- #table of #AWACS.ManagedGroup entries + self.ManagedGrpID = 0 + + self.AICAPCAllName = CALLSIGN.F16.Viper + self.AICAPCAllNumber = 0 + + -- Anchor stacks init + self.AnchorStacks = FIFO:New() -- Utils.FiFo#FIFO + self.AnchorBaseAngels = 22 + self.AnchorStackDistance = 2 + self.AnchorMaxStacks = 4 + self.AnchorMaxAnchors = 2 + self.AnchorMaxZones = 6 + self.AnchorCurrZones = 1 + self.AnchorTurn = -(360/self.AnchorMaxZones) + + self:_CreateAnchorStack() + + -- Task lists + self.AssignedTasks = FIFO:New() -- Utils.FiFo#FIFO + self.OpenTasks = FIFO:New() -- Utils.FiFo#FIFO + + -- Pilot lists + + self.CAPIdleAI = FIFO:New() -- Utils.FiFo#FIFO + self.CAPIdleHuman = FIFO:New() -- Utils.FiFo#FIFO + self.TaskedCAPAI = FIFO:New() -- Utils.FiFo#FIFO + self.TaskedCAPHuman = FIFO:New() -- Utils.FiFo#FIFO + + -- Picture, Contacts, Bogeys + self.PictureAO = FIFO:New() -- Utils.FiFo#FIFO + self.PictureEWR = FIFO:New() -- Utils.FiFo#FIFO + self.Contacts = FIFO:New() -- Utils.FiFo#FIFO + self.ContactsAO = FIFO:New() -- Utils.FiFo#FIFO + + -- SET for Intel Detection + self.DetectionSet=SET_GROUP:New() + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.Name, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- Status update. + self:AddTransition("*", "CheckedIn", "*") + self:AddTransition("*", "CheckedOut", "*") + self:AddTransition("*", "AssignAnchor", "*") + self:AddTransition("*", "AssignedAnchor", "*") + self:AddTransition("*", "NewCluster", "*") + self:AddTransition("*", "NewContact", "*") + self:AddTransition("*", "LostCluster", "*") + self:AddTransition("*", "LostContact", "*") + self:AddTransition("*", "CheckRadioQueue", "*") + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- self:__Start(math.random(2,5)) + + local text = string.format("%sAWACS Version %s Initiated",self.lid,self.version) + + self:I(text) + + -- debug zone markers + if self.debug then + self.OpsZone:DrawZone(-1,{1,0,0},1,{1,0,0},0.2,5,true) + MARKER:New(self.OpsZone:GetCoordinate(),"AO Zone"):ToAll() + self.AnchorZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + MARKER:New(self.AnchorZone:GetCoordinate(),"Anchor Zone"):ToAll() + self.OrbitZone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,5,true) + MARKER:New(self.OrbitZone:GetCoordinate(),"Orbit Zone"):ToAll() + end + + return self +end + +-- TODO Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- [User] Set AWACS flight details +-- @param #AWACS self +-- @param #number CallSign Defaults to CALLSIGN.AWACS.Magic +-- @param #number CallSignNo Defaults to 1 +-- @param #number Angels Defaults to 25 (i.e. 25000 ft) +-- @param #number Speed Defaults to 250kn +-- @param #number Heading Defaults to 0 (North) +-- @param #number Leg Defaults to 25nm +-- @return #AWACS self +function AWACS:SetAwacsDetails(CallSign,CallSignNo,Angels,Speed,Heading,Leg) + self:T(self.lid.."SetAwacsDetails") + self.CallSign = CallSign or CALLSIGN.AWACS.Magic + self.CallSignNo = CallSignNo or 1 + self.Angels = Angels or 25 + local speed = Speed or 250 + self.SpeedBase = speed + self.Speed = UTILS.KnotsToAltKIAS(speed,self.Angels*1000) + self.Heading = Heading or 0 + self.Leg = Leg or 25 + return self +end + +--- [User] Add a radar GROUP object to the INTEL detection SET_GROUP +-- @param Wrapper.Group#GROUP Group The GROUP to be added. Can be passed as SET_GROUP. +-- @return #AWACS self +function AWACS:AddGroupToDetection(Group) + self:T(self.lid.."AddGroupToDetection") + if Group and Group.ClassName and Group.ClassName == "GROUP" then + self.DetectionSet:AddGroup(Group) + elseif Group and Group.ClassName and Group.ClassName == "SET_GROUP" then + self.DetectionSet:AddSet(Group) + end + return self +end + +--- [User] Set AWACS SRS TTS details - see @{#MSRS} for details +-- @param #AWACS self +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- @param #string Gender Defaults to "male" +-- @param #string Culture Defaults to "en-US" +-- @param #number Port Defaults to 5002 +-- @param #string Voice (Optional) Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. +-- Note that this must be installed on your windows system. +-- @return #AWACS self +function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice) + self:T(self.lid.."SetSRS") + self.PathToSRS = PathToSRS or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.Gender = Gender or "male" + self.Culture = Culture or "en-US" + self.Port = Port or 5002 + self.Voice = Voice + return self +end + +--- [User] Set AWACS Escorts Template +-- @param #AWACS self +-- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts. +-- @return #AWACS self +function AWACS:SetEscort(EscortNumber) + self:T(self.lid.."SetEscort") + if EscortNumber and EscortNumber > 0 then + self.HasEscorts = true + self.EscortNumber = EscortNumber + end + return self +end + +--- [Internal] Start AWACS Escorts FlightGroup +-- @param #AWACS self +-- @return #AWACS self +function AWACS:_StartEscorts() + self:T(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"}) + mission:SetRequiredAssets(self.EscortNumber) + + local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds + mission:SetTime(nil,timeonstation) + + self.AirWing:AddMission(mission) + + self.EscortMission = mission + + return self +end + +--- [Internal] AWACS further Start Settings +-- @param #AWACS self +-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup +-- @param Ops.Auftrag#AUFTRAG Mission +-- @return #AWACS self +function AWACS:_StartSettings(FlightGroup,Mission) + self:T(self.lid.."_StartSettings") + + local Mission = Mission -- Ops.Auftrag#AUFTRAG + local AwacsFG = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP + + -- Is this our Awacs mission? + if self.AwacsMission:GetName() == Mission:GetName() then + AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) + AwacsFG:SwitchRadio(self.Frequency,self.Modulation) + AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) + AwacsFG:SetHomebase(self.Airbase) + AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo) + AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold) + AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) + AwacsFG:SetDefaultEPLRS(self.ModernEra) + AwacsFG:SetDespawnAfterLanding() + AwacsFG:SetFuelLowRTB(true) + AwacsFG:SetFuelLowThreshold(20) + + local group = AwacsFG:GetGroup() -- Wrapper.Group#GROUP + + group:SetCommandInvisible(self.invisible) + group:SetCommandImmortal(self.immortal) + + 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") + self:T(self.lid..text) + + AwacsFG:RadioTransmission(text,1,false) + + self.AwacsFG = AwacsFG + + self:__CheckRadioQueue(10) + + if self.HasEscorts then + --mission:SetRequiredEscorts(self.EscortNumber) + self:_StartEscorts() + end + + self.AwacsTimeStamp = timer.getTime() + self.EscortsTimeStamp = timer.getTime() + + else + -- check for CAP missions, Auftragsnummer id UniqueID in FIFO + -- TODO + if self.AICAPMissions:HasUniqueID(Mission.auftragsnummer) then + -- one of ours + -- Register Group + --self:_CheckInAI(FlightGroup,FlightGroup:GetGroup(),Mission.auftragsnummer) + end + end + return self +end + +--- [Internal] Check if a group has checked in +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to check +-- @return #number ID +-- @return #boolean CheckedIn +function AWACS:_GetManagedGrpID(Group) + self:T(self.lid.."_GetManagedGrpID for "..Group:GetName()) + local ID = 0 + local Outcome = false + local nametocheck = Group:GetName() + local managedgrps = self.ManagedGrps or {} + for _,_managed in pairs (managedgrps) do + local managed = _managed -- #AWACS.ManagedGroup + if managed.GroupName == nametocheck then + ID = managed.ID + Outcome = true + end + end + return ID, Outcome +end + +--- [Internal] AWACS Get TTS compatible callsign +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #string Callsign +function AWACS:_GetCallSign(Group) + self:T(self.lid.."_GetCallSign") + local callsign = "" + local shortcallsign = Group:GetCallsign() -- e.g.Uzi11, but we want Uzi 1 1 + local callnumber = string.match(shortcallsign, "(%d+)$" ) + 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) + return callsign +end + +--- [Internal] AWACS Speak Picture AO/EWR entries +-- @param #AWACS self +-- @param #boolean AO If true this is for AO, else EWR +-- @param #string Callsign Callsign to address +-- @param #number GID GroupID for comms +-- @return #AWACS self +function AWACS:_CreatePicture(AO,Callsign,GID) + 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 fifo = self.PictureAO -- Utils.FIFO#FIFO + local maxentries = self.maxspeakentries + local counter = 0 + + if not AO then + fifo = self.PictureEWR + end + + local entries = fifo:GetSize() + + if entries < maxentries then maxentries = entries end + + local text = "First group." + local textScreen = text + + 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) + text = text .. " "..refBRAA.."." + textScreen = textScreen .." "..refBRAA..".\n" + if counter < maxentries then + text = text .. " Next group." + textScreen = textScreen .. text .. "\n" + end + end + end + + -- empty queue from leftovers + fifo:Clear() + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = GID + RadioEntry.IsGroup = managedgroup.IsPlayer + RadioEntry.ToScreen = managedgroup.IsPlayer + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + return self +end + +--- [Internal] AWACS Speak Bogey Dope entries +-- @param #AWACS self +-- @param #string Callsign Callsign to address +-- @param #number GID GroupID for comms +-- @return #AWACS self +function AWACS:_CreateBogeyDope(Callsign,GID) + self:T(self.lid.."_CreateBogeyDope for "..Callsign.." GID "..GID) + + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + local group = managedgroup.Group -- Wrapper.Group#GROUP + local groupcoord = group:GetCoordinate() + + local fifo = self.ContactsAO -- Utils.FIFO#FIFO + local maxentries = self.maxspeakentries + local counter = 0 + + local entries = fifo:GetSize() + + if entries < maxentries then maxentries = entries end + + local sortedIDs = fifo:GetIDStackSorted() -- sort by distance + + while counter < maxentries do + counter = counter + 1 + local cluster = fifo:PullByID(sortedIDs[counter]) -- Ops.Intelligence#INTEL.Contact + self:T({cluster}) + if cluster and cluster.position then + self:_AnnounceContact(cluster,false,group,true) + end + end + + -- empty queue from leftovers + fifo:Clear() + + return self +end + +--- [Internal] AWACS Menu for Picture +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_Picture(Group) + self:T(self.lid.."_Picture") + local text = "Picture WIP" + local textScreen = text + + if not self.intel then + -- no intel yet! + text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + textScreen = text + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + return self + end + + local ID, Outcome = self:_GetManagedGrpID(Group) + + if Outcome then + -- Pilot is checked in + -- get clusters from Intel + local clustertable = self.intel:GetClusterTable() + -- sort into buckets + for _,_cluster in pairs(clustertable) do + local cluster = _cluster -- Ops.Intelligence#INTEL.Cluster + local coordVec2 = cluster.coordinate:GetVec2() + + if self.OpsZone:IsVec2InZone(coordVec2) then + self.PictureAO:Push(cluster) + elseif self.ControlZone:IsVec2InZone(coordVec2) then + self.PictureEWR:Push(cluster) + end + end + + local clustersAO = self.PictureAO:GetSize() + local clustersEWR = self.PictureEWR:GetSize() + + if clustersAO == 0 and clustersEWR == 0 then + -- clear + text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + textScreen = text + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = ID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + 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") + if clustersAO == 1 then + text = text .. "One group. " + textScreen = textScreen .. "One group.\n" + else + text = text .. clustersAO .. " groups. " + textScreen = textScreen .. clustersAO .. " groups.\n" + end + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = ID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + self.RadioQueue:Push(RadioEntry) + + self:_CreatePicture(true,self:_GetCallSign(Group) or "Unknown 1 1",ID) + 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") + if clustersEWR == 1 then + text = text .. "One group. " + textScreen = textScreen .. "One group.\n" + else + text = text .. clustersEWR .. " groups. " + textScreen = textScreen .. clustersAO .. " groups.\n" + end + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = ID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + self.RadioQueue:Push(RadioEntry) + + self:_CreatePicture(false,self:_GetCallSign(Group) or "Unknown 1 1",ID) + 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") + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = ID + 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 Bogey Dope +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_BogeyDope(Group) + self:T(self.lid.."_BogeyDope") + local text = "BogeyDope WIP" + local textScreen = "BogeyDope WIP" + + if not self.intel then + -- no intel yet! + text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + textScreen = text + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = 0 + RadioEntry.IsGroup = false + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + return self + end + + local ID, Outcome = self:_GetManagedGrpID(Group) + + if Outcome then + -- Pilot is checked in + + local managedgroup = self.ManagedGrps[ID] -- #AWACS.ManagedGroup + local pilotgroup = managedgroup.Group + local pilotcoord = managedgroup.Group:GetCoordinate() + + -- get contacts from Intel + local contactstable = self.intel:GetContactTable() + + -- sort into buckets - AO only for bogey dope! + for _,_contact in pairs(contactstable) do + local cluster = _contact -- Ops.Intelligence#INTEL.Contact + local coordVec2 = cluster.position:GetVec2() + + -- Get distance for sorting + local dist = pilotcoord:Get2DDistance(cluster.position) + + if self.OpsZone:IsVec2InZone(coordVec2) then + self.ContactsAO:Push(cluster,dist) + end + end + + local contactsAO = self.ContactsAO:GetSize() + + if contactsAO == 0 then + -- clear + text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + textScreen = text + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = ID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = Outcome + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + else + + if contactsAO > 0 then + text = string.format("%s. %s. Bogey Dope. ",self.callsigntxt,self:_GetCallSign(Group) 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" + end + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = ID + RadioEntry.IsGroup = Outcome + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + self.RadioQueue:Push(RadioEntry) + + self:_CreateBogeyDope(self:_GetCallSign(Group) or "Unknown 1 1",ID) + 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") + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = ID + 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 Declare +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_Declare(Group) + self:T(self.lid.."_Declare") + + local ID, Outcome = self:_GetManagedGrpID(Group) + local text = "Declare 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) or "Unknown 1 1") + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = ID + 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 Showtask +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_Showtask(Group) + self:T(self.lid.."_Showtask") + + local ID, Outcome = self:_GetManagedGrpID(Group) + local text = "Showtask WIP" + + if Outcome then + -- known group + + -- Do we have a task? + local managedgroup = self.ManagedGrps[ID] -- #AWACS.ManagedGroup + + if managedgroup.IsPlayer and self.TaskedCAPHuman:HasUniqueID(ID) then + + if managedgroup.CurrentTask >0 and self.AssignedTasks:HasUniqueID(managedgroup.CurrentTask) then + -- get task structure + local currenttask = self.AssignedTasks: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) + + 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: %s with Status: %s",ToDo,status)) + taskreport:Add(string.format("Target of Type: %s",targettype)) + taskreport:Add(string.format("Target in State: %s",targetstatus)) + taskreport:Add("===============") + self:I(taskreport:Text()) + end + + MESSAGE:New(description,30,"AWACS",true):ToGroup(Group) + + end + 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") + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = ID + 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 Check in +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_CheckIn(Group) + self:I(self.lid.."_CheckIn "..Group:GetName()) + -- check if already known + local ID, Outcome = self:_GetManagedGrpID(Group) + local text = "" + if not Outcome then + self.ManagedGrpID = self.ManagedGrpID + 1 + local managedgroup = {} -- #AWACS.ManagedGroup + managedgroup.Group = Group + managedgroup.GroupName = Group:GetName() + managedgroup.IsPlayer = true + managedgroup.IsAI = false + managedgroup.CallSign = self:_GetCallSign(Group) or "Unknown 1 1" + managedgroup.CurrentTask = 0 + managedgroup.AssignedTask = false + managedgroup.ID = self.ManagedGrpID + ID = managedgroup.ID + self.ManagedGrps[self.ManagedGrpID]=managedgroup + text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign) + self:__CheckedIn(1,managedgroup.ID) + self:__AssignAnchor(5,managedgroup.ID) + elseif self.AwacsFG then + text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = ID + RadioEntry.IsGroup = true + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + return self +end + +--- [Internal] AWACS Menu for CheckInAI +-- @param #AWACS self +-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup to use +-- @param Wrapper.Group#GROUP Group Group to use +-- @param #number AuftragsNr Ops.Auftrag#AUFTRAG.auftragsnummer +-- @return #AWACS self +function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) + self:I(self.lid.."_CheckInAI "..Group:GetName()) + -- check if already known + local ID, Outcome = self:_GetManagedGrpID(Group) + local text = "" + if not Outcome then + self.ManagedGrpID = self.ManagedGrpID + 1 + local managedgroup = {} -- #AWACS.ManagedGroup + managedgroup.Group = Group + managedgroup.GroupName = Group:GetName() + managedgroup.FlightGroup = FlightGroup + managedgroup.IsPlayer = false + managedgroup.IsAI = true + managedgroup.CallSign = self:_GetCallSign(Group) or "Unknown 1 1" + managedgroup.CurrentTask = AuftragsNr + managedgroup.AssignedTask = false + managedgroup.ID = self.ManagedGrpID + self.ManagedGrps[self.ManagedGrpID]=managedgroup + text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign) + self:__CheckedIn(1,managedgroup.ID) + self:__AssignAnchor(5,managedgroup.ID) + else + text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1") + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.ToScreen = false + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + return self +end + +--- [Internal] AWACS Menu for Check Out +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @return #AWACS self +function AWACS:_CheckOut(Group) + self:I(self.lid.."_CheckOut") + + -- check if already known + local ID, Outcome = self:_GetManagedGrpID(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") + -- grab some data before we nil the entry + local AnchorAssigned = self.ManagedGrps[ID] -- #AWACS.ManagedGroup + local Stack = AnchorAssigned.AnchorStackNo + local Angels = AnchorAssigned.AnchorStackAngels + self.ManagedGrps[ID] = nil + self:__CheckedOut(1,ID,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") + end + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + + return self +end + +--- [Internal] AWACS set client menus +-- @param #AWACS self +-- @return #AWACS self +function AWACS:_SetClientMenus() + self:T(self.lid.."_SetClientMenus") + local clientset = self.clientset -- Core.Set#SET_GROUP + local aliveset = clientset:GetAliveSet() -- #table of #GROUP objects + --local aliveobjects = aliveset:GetSetObjects() or {} + local clientmenus = {} + 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 + local GID, checkedin = self:_GetManagedGrpID(grp) + if checkedin then + -- full menu minus checkin + 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) + 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 showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",basemenu,self._Showtask,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, + } + 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) + checkin:SetTag(grp:GetName()) + checkin: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, + } + 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 showtask = MENU_COALITION_COMMAND:New(self.coalition,"Showtask",basemenu,self._Showtask,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 + groupname = grp:GetName(), + menuset = true, + basemenu = basemenu, + checkin = checkin, + checkout= checkout, + picture = picture, + bogeydope = bogeydope, + declare = declare, + showtask = showtask, + } + end + end + end + self.clientmenus = clientmenus + return self +end + +--- [Internal] AWACS Create a new Anchor Stack +-- @param #AWACS self +-- @return #boolean success +-- @return #nunber AnchorStackNo +function AWACS:_CreateAnchorStack() + self:T(self.lid.."_CreateAnchorStack") + local stackscreated = self.AnchorStacks:GetSize() + if stackscreated == self.AnchorMaxAnchors then + -- only create self.AnchorMaxAnchors Anchors + return false, 0 + end + local AnchorStackOne = {} -- #AWACS.AnchorData + AnchorStackOne.AnchorBaseAngels = self.AnchorBaseAngels + AnchorStackOne.Anchors = FIFO:New() -- Utils.FiFo#FIFO + AnchorStackOne.AnchorAssignedID = FIFO:New() -- Utils.FiFo#FIFO + local newname = "" + for i=1,self.AnchorMaxStacks do + AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) + end + if self.debug then + --AnchorStackOne.Anchors:Flush() + end + if stackscreated == 0 then + AnchorStackOne.AnchorZone = self.AnchorZone + AnchorStackOne.AnchorZoneCoordinate = self.AnchorZone:GetCoordinate() + AnchorStackOne.AnchorZoneCoordinateText = self.AnchorZone:GetCoordinate():ToStringLLDDM() + --push to AnchorStacks + self.AnchorStacks:Push(AnchorStackOne,"One") + else + local newsubname = AWACS.AnchorNames[stackscreated+1] or tostring(stackscreated+1) + newname = self.AnchorZone:GetName() .. "-"..newsubname + local anchorbasecoord = self.OpsZone:GetCoordinate() -- Core.Point#COORDINATE + -- OpsZone can be Polygon, so use distance to AnchorZone as radius + local anchorradius = anchorbasecoord:Get2DDistance(self.AnchorZone:GetCoordinate()) + --local anchorradius = self.OpsZone:GetRadius() -- #number + --anchorradius = anchorradius + self.AnchorZone:GetRadius() + local angel = self.AnchorZone:GetCoordinate():GetAngleDegrees(self.OpsZone:GetVec3()) + self:T("Angel Radians= " .. angel) + local turn = math.fmod(self.AnchorTurn*stackscreated,360) -- #number + if self.AnchorTurn < 0 then turn = -turn end + local newanchorbasecoord = anchorbasecoord:Translate(anchorradius,turn+angel) -- Core.Point#COORDINATE + AnchorStackOne.AnchorZone = ZONE_RADIUS:New(newname, newanchorbasecoord:GetVec2(), self.AnchorZone:GetRadius()) + AnchorStackOne.AnchorZoneCoordinate = newanchorbasecoord + AnchorStackOne.AnchorZoneCoordinateText = newanchorbasecoord:ToStringLLDDM() + --push to AnchorStacks + self.AnchorStacks:Push(AnchorStackOne,newname) + end + + if self.debug then + --self.AnchorStacks:Flush() + AnchorStackOne.AnchorZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + MARKER:New(AnchorStackOne.AnchorZone:GetCoordinate(),"Anchor Zone: "..newname):ToAll() + end + + return true,self.AnchorStacks:GetSize() + +end + +--- [Internal] AWACS get free anchor stack for managed groups +-- @param #AWACS self +-- @return #number AnchorStackNo +-- @return #boolean free +function AWACS:_GetFreeAnchorStack() + self:T(self.lid.."_GetFreeAnchorStack") + local AnchorStackNo, Free = 0, false + --return AnchorStackNo, Free + local availablestacks = self.AnchorStacks:GetPointerStack() or {} -- #table + for _id,_entry in pairs(availablestacks) do + local entry = _entry -- Utils.FIFO#FIFO.IDEntry + local data = entry.data -- #AWACS.AnchorData + if data.Anchors:IsNotEmpty() then + AnchorStackNo = _id + Free = true + break + end + end + -- TODO - extension of anchor stacks to max, send AI home + if not Free then + -- try to create another stack + local created, number = self:_CreateAnchorStack() + if created then + -- we could create a new one - phew! + self:_GetFreeAnchorStack() + end + end + return AnchorStackNo, Free +end + +--- [Internal] AWACS Assign Anchor Position to a Group +-- @param #AWACS self +-- @return #number ID Managed Group ID +-- @return #AWACS self +function AWACS:_AssignAnchorToID(ID) + self:T(self.lid.."_AssignAnchorToID") + local AnchorStackNo, Free = self:_GetFreeAnchorStack() + if Free then + -- get the Anchor from the stack + local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData + -- pull one free angels + local freeangels = Anchor.Anchors:Pull() + -- push ID on anchor + Anchor.AnchorAssignedID:Push(ID) + if self.debug then + --Anchor.AnchorAssignedID:Flush() + --Anchor.Anchors:Flush() + end + -- push back to AnchorStacks + self.AnchorStacks:Push(Anchor) + self:T({Anchor,freeangels}) + self:__AssignedAnchor(5,ID,Anchor,AnchorStackNo,freeangels) + else + self:E(self.lid .. "Cannot assing free anchor stack to ID ".. ID) + -- try again ... + self:__AssignAnchor(10,ID) + end + return self +end + +--- [Internal] Remove ID (group) from Anchor Stack +-- @param #AWACS self +-- @param #AWACS.ManagedGroup.ID ID +-- @param #number AnchorStackNo +-- @param #number Angels +-- @return #AWACS self +function AWACS:_RemoveIDFromAnchor(ID,AnchorStackNo,Angels) + self:I(self.lid.."_RemoveIDFromAnchor for ID="..ID.." Stack="..AnchorStackNo.." Angels="..Angels) + -- pull correct anchor + local AnchorStackNo = AnchorStackNo or 1 + local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData + -- pull ID from stack + local removedID = Anchor.AnchorAssignedID:PullByID(ID) + -- push free angels to stack + Anchor.Anchors:Push(Angels) + -- push back AnchorStack + self.AnchorStacks:Push(Anchor) + return self +end + +--- [Internal] Start INTEL detection when we reach the AWACS Orbit Zone +-- @param #AWACS self +-- @param Wrapper.Group#GROUP awacs +-- @return #AWACS self +function AWACS:_StartIntel(awacs) + self:T(self.lid.."_StartIntel") + + self.DetectionSet:AddGroup(awacs) + + local intel = INTEL:New(self.DetectionSet,self.coalition,self.callsigntxt) + --intel:SetVerbosity(2) + intel:SetClusterAnalysis(true,self.debug) + if self.NoHelos then + intel:SetFilterCategory({Unit.Category.AIRPLANE}) + else + intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) + end + + -- Callbacks + local function NewCluster(Cluster) + self:__NewCluster(2,Cluster) + end + function intel:OnAfterNewCluster(From,Event,To,Cluster) + NewCluster(Cluster) + end + + local function NewContact(Contact) + self:__NewContact(2,Contact) + end + function intel:OnAfterNewContact(From,Event,To,Contact) + NewContact(Contact) + end + + local function LostContact(Contact) + self:__LostContact(2,Contact) + end + function intel:OnAfterLostContact(From,Event,To,Contact) + LostContact(Contact) + end + + local function LostCluster(Cluster,Mission) + self:__LostCluster(2,Cluster,Mission) + end + function intel:OnAfterLostCluster(From,Event,To,Cluster,Mission) + LostCluster(Cluster,Mission) + end + + intel:__Start(2) + + self.intel = intel + return self +end + +--- [Internal] Get blurred size of group or cluster +-- @param #AWACS self +-- @param #number size +-- @return #number adjusted size +function AWACS:_GetBlurredSize(size) + self:T(self.lid.."_GetBlurredSize") + local threatsize = 0 + local blur = self.RadarBlur + local blurmin = 100 - blur + local blurmax = 100 + blur + local actblur = math.random(blurmin,blurmax) / 100 + threatsize = math.floor(size * actblur) + return threatsize +end + +--- [Internal] Get threat level as clear test +-- @param #AWACS self +-- @param #number threatlevel +-- @return #string threattext +function AWACS:_GetThreatLevelText(threatlevel) + self:T(self.lid.."_GetThreatLevelText") + local threattext = "GREEN" + if threatlevel <= AWACS.THREATLEVEL.GREEN then + threattext = "GREEN" + elseif threatlevel <= AWACS.THREATLEVEL.AMBER then + threattext = "AMBER" + else + threattext = "RED" + end + return threattext +end + +--- [Internal] Get BRA text for TTS +-- @param #AWACS self +-- @param Core.Point#COORDINATE clustercoordinate +-- @return #string BRAText +function AWACS:_GetBRAfromBullsOrAO(clustercoordinate) + self:T(self.lid.."__GetBRAfromBullsOrAO") + local refcoord = self.AOCoordinate -- Core.Point#COORDINATE + local BRAText = "" + if not self.UseBullsAO then + -- get BR from AO + BRAText = "AO "..refcoord:ToStringBR(clustercoordinate) + else + -- get BR from Bulls + BRAText = clustercoordinate:ToStringBULLS(self.coalition) + end + return BRAText +end + +--- [Internal] Register Task for Group by ID +-- @param #AWACS self +-- @param #number GroupID ManagedGroup ID +-- @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) + self:I(self.lid.."_CreateTaskForGroup "..GroupID .." Description: "..Description) + local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup + local task = {} -- #AWACS.ManagedTask + self.ManagedTaskID = self.ManagedTaskID + 1 + task.ID = self.ManagedTaskID + task.AssignedGroupID = GroupID + task.Status = AWACS.TaskStatus.ASSIGNED + task.ToDo = Description + task.Target = TARGET:New(Object) + task.ScreenText = ScreenText + if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then + task.Target.Type = TARGET.ObjectType.ZONE + end + self.AssignedTasks:Push(task,task.ID) + --self:I({task}) + managedgroup.AssignedTask = true + managedgroup.CurrentTask = task.ID + self.ManagedGrps[GroupID] = managedgroup + if managedgroup.IsPlayer then + self.TaskedCAPHuman:Push(GroupID) + elseif managedgroup.IsAI then + self.TaskedCAPAI:Push(GroupID) + end + return self +end + +--- [Internal] Get registered Task for Group by its ID +-- @param #AWACS self +-- @param #number GroupID ManagedGroup ID +-- @return #AWACS.ManagedTask Task or nil if n/e +function AWACS:_GetAssignedTaskFromID(GroupID) + self:T(self.lid.."_GetAssignedTaskFromID "..GroupID) + local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup + if managedgroup and managedgroup.AssignedTask then + local TaskID = managedgroup.CurrentTask + if self.AssignedTasks:HasUniqueID(TaskID) then + return self.AssignedTasks:PullByID(TaskID) + end + end + return nil +end + +--- [Internal] Create new idle task from contact to pick up later +-- @param #AWACS self +-- @param #string Description Task Type +-- @param #table Object Object of TARGET +-- @param Ops.Intelligence#INTEL.Contact Contact +-- @return #AWACS self +function AWACS:_CreateIdleTaskForContact(Description,Object,Contact) + self:T(self.lid.."_CreateIdleTaskForContact "..Description) + local task = {} -- #AWACS.ManagedTask + self.ManagedTaskID = self.ManagedTaskID + 1 + task.ID = self.ManagedTaskID + task.AssignedGroupID = 0 + task.Status = AWACS.TaskStatus.IDLE + task.ToDo = Description + task.Target = TARGET:New(Object) + task.Contact = Contact + 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.ID) + return self +end + +--- [Internal] Create new idle task from cluster to pick up later +-- @param #AWACS self +-- @param #string Description Task Type +-- @param #table Object Object of TARGET +-- @param Ops.Intelligence#INTEL.Cluster Cluster +-- @return #AWACS self +function AWACS:_CreateIdleTaskForCluster(Description,Object,Cluster) + self:T(self.lid.."_CreateIdleTaskForCluster "..Description) + local task = {} -- #AWACS.ManagedTask + self.ManagedTaskID = self.ManagedTaskID + 1 + task.ID = self.ManagedTaskID + task.AssignedGroupID = 0 + task.Status = AWACS.TaskStatus.IDLE + task.ToDo = Description + --self:T({Cluster.Contacts}) + --task.Target = TARGET:New(Cluster.Contacts[1]) + task.Target = TARGET:New(self.intel:GetClusterCoordinate(Cluster)) + task.Cluster = Cluster + 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.ID) + return self +end + +--- [Internal] Check available tasks and status +-- @param #AWACS self +-- @return #AWACS self +function AWACS:_CheckTaskQueue() + self:I(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 -- Utils.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:I({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() + for _id,_entry in pairs(taskstack) do + local data = _entry -- Utils.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:I({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.AssignedTasks: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 + + return self +end + +--- [Internal] Write stats to log +-- @param #AWACS self +-- @return #AWACS self +function AWACS:_LogStatistics() + self:T(self.lid.."_LogStatistics") + return self +end + +--- [Internal] Announce a new contact +-- @param #AWACS self +-- @param Ops.Intelligence#INTEL.Contact Contact +-- @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 +-- @return #AWACS self +function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) + self:T(self.lid.."_AnnounceContact") + -- do we have a group to talk to? + local isGroup = false + local ID = 0 + local grpcallsign = "Unknown 1 1" + if Group and Group:IsAlive() then + ID, isGroup = self:_GetManagedGrpID(Group) + self:T("ID="..ID.." CheckedIn = "..tostring(isGroup)) + grpcallsign = self:_GetCallSign(Group) 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 threatlevel = contact.threatlevel + local threattext = self:_GetThreatLevelText(threatlevel) + local clustercoordinate = contact.position + + local BRAfromBulls = self:_GetBRAfromBullsOrAO(clustercoordinate) + if isGroup then + BRAfromBulls = clustercoordinate:ToStringBRA(Group:GetCoordinate()) + end + + local Warnlevel = "Early Warning." + + if self.OpsZone:IsVec2InZone(clustercoordinate:GetVec2()) and not IsBogeyDope then + Warnlevel = "Warning." + elseif IsBogeyDope then + Warnlevel = "" + end + + if IsNew then + Warnlevel = Warnlevel .. " New" + end + + 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 %d ship contact. %s",self.callsigntxt,Warnlevel,threatsize,BRAfromBulls) + + -- TextOutput + local TextScreen = string.format("%s. %s %d ship contact.\n%s\nThreatlevel %s",self.callsigntxt,Warnlevel,threatsize,BRAfromBulls,threattext) + + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.TextTTS = TextTTS + RadioEntry.TextScreen = TextScreen + RadioEntry.IsNew = IsNew + RadioEntry.IsGroup = isGroup + RadioEntry.GroupID = ID + RadioEntry.Duration = STTS.getSpeechTime(TextTTS,1.2,false)+2 or 16 + RadioEntry.ToScreen = true + + self.RadioQueue:Push(RadioEntry) + + return self +end + +--- [Internal] Check for alive OpsGroup from Mission OpsGroups table +-- @param #AWACS self +-- @param #table OpsGroups +-- @return Ops.OpsGroup#OPSGROUP or nil +function AWACS:_GetAliveOpsGroupFromTable(OpsGroups) + self:T(self.lid.."_GetAliveOpsGroupFromTable") + 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 + handback = OG + self:T("Handing back OG: " .. OG:GetName()) + break + end + end + return handback +end + +--- [Internal] Check Enough AI CAP on Station +-- @param #AWACS self +-- @return #AWACS self +function AWACS:_CheckAICAPOnStation() + self:I(self.lid.."_CheckAICAPOnStation") + if self.MaxAIonCAP > 0 then + local onstation = self.AICAPMissions:Count() + -- control number of AI CAP Flights + if onstation < self.MaxAIonCAP then + -- not enough + local AnchorStackNo,free = self:_GetFreeAnchorStack() + if free then + local mission = AUFTRAG:NewCAP(self.AnchorZone,20000,350,self.AnchorZone:GetCoordinate(),nil,nil,{}) + -- local mission = AUFTRAG:NewNOTHING() + self.AirWing:AddMission(mission) + self.AICAPMissions:Push(mission,mission.auftragsnummer) + end + elseif onstation > self.MaxAIonCAP then + -- too many, send one home + local mission = self.AICAPMissions:Pull() -- Ops.Auftrag#AUFTRAG + local Groups = mission:GetOpsGroups() + local OpsGroup = self:_GetAliveOpsGroupFromTable(Groups) + mission:__Cancel(5) + self:_CheckOut(OpsGroup) + end + -- Check Alert5 Mission states + if onstation > 0 then + local missionIDs = self.AICAPMissions:GetIDStackSorted() + --self:_CheckInAI(FlightGroup,FlightGroup:GetGroup(),Mission.auftragsnummer) + -- 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) + local type = mission:GetType() + local state = mission:GetState() + if type == AUFTRAG.Type.CAP then + local OpsGroups = mission:GetOpsGroups() + local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) + local FGstate = mission:GetGroupStatus(OpsGroup) + -- FG ready? + if FGstate == AUFTRAG.Status.STARTED or FGstate == AUFTRAG.Status.EXECUTING then + -- has this group checked in already? Avoid double tasking + local GID, CheckedInAlready = self:_GetManagedGrpID(OpsGroup:GetGroup()) + if not CheckedInAlready then + self:_SetAIROE(OpsGroup,OpsGroup:GetGroup()) + self:_CheckInAI(OpsGroup,OpsGroup:GetGroup(),mission.auftragsnummer) + end + end + 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 i = 1 + for _,_MissionID in pairs(missionIDs) do + --for i=1,self.MaxAIonCAP do + local mission = self.AICAPMissions:ReadByID(_MissionID) -- Ops.Auftrag#AUFTRAG + --local mission = self.AICAPMissions:ReadByPointer(i) -- Ops.Auftrag#AUFTRAG + if mission then + i = i + 1 + report:Add(string.format("Entry %d",i)) + report:Add(string.format("Mission No %d",mission.auftragsnummer)) + report:Add(string.format("Mission Type %s",mission:GetType())) + report:Add(string.format("Mission State %s",mission:GetState())) + local OpsGroups = mission:GetOpsGroups() + local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP + local OpsName = OpsGroup:GetName() or "Unknown" + report:Add(string.format("Mission FG %s",OpsName)) + report:Add(string.format("Mission FG State %s",mission:GetGroupStatus(OpsGroup))) + report:Add(string.format("Target Type %s",mission:GetTargetType())) + end + report:Add("===============") + end + if self.debug then + self:I(report:Text()) + end + end + end + return self +end + +--- [Internal] Set ROE for AI CAP +-- @param #AWACS self +-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup +-- @param Wrapper.Group#GROUP Group +-- @return #AWACS self +function AWACS:_SetAIROE(FlightGroup,Group) + self:T(self.lid.."_SetAIROE") + local ROE = self.AwacsROE or AWACS.ROE.POLICE + local ROT = self.AwacsROT or AWACS.ROT.PASSIVE + + -- TODO adjust to AWACS set ROE + -- for the time being set to be defensive + Group:OptionAlarmStateGreen() + Group:OptionECM_OnlyLockByRadar() + Group:OptionROEHoldFire() + Group:OptionROTEvadeFire() + Group:OptionRTBBingoFuel(true) + Group:OptionKeepWeaponsOnThreat() + local callname = self.AICAPCAllName or CALLSIGN.F16.Viper + 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:SetDefaultROE(ENUMS.ROE.WeaponHold) + FlightGroup:SetDefaultROT(ENUMS.ROT.EvadeFire) + FlightGroup:SetFuelLowRTB(true) + FlightGroup:SetFuelLowThreshold(0.2) + FlightGroup:SetEngageDetectedOff() + FlightGroup:SetOutOfAAMRTB(true) + return self +end + +-- TODO FSMs +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- [Internal] onafterStart +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #AWACS self +function AWACS:onafterStart(From, Event, To) + self:I({From, Event, To}) + + -- Set up control zone + self.ControlZone = ZONE_RADIUS:New(self.OpsZone:GetName(),self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius)) + if self.debug then + self.ControlZone:DrawZone(-1,{0,1,0},1,{1,0,0},0.05,3,true) + MARKER:New(self.ControlZone:GetCoordinate(),"Control Zone"):ToAll() + end + + -- 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) + local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 + mission:SetTime(nil,timeonstation) + + 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:__Status(-30) + 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() + + local awacs = nil + if self.AwacsFG then + awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP + end + if awacs and awacs:IsAlive() and not self.AwacsInZone then + -- check if we arrived + local orbitzone = self.OrbitZone -- Core.Zone#ZONE + if awacs:IsInZone(orbitzone) then + -- arrived + 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 RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 + + self.RadioQueue:Push(RadioEntry) + self:_StartIntel(awacs) + end + end + if (awacs and awacs:IsAlive()) then + -- Check on Awacs Mission Status + if self.debug then + local AWmission = self.AwacsMission -- Ops.Auftrag#AUFTRAG + local awstatus = AWmission:GetState() + local AWmissiontime = (timer.getTime() - self.AwacsTimeStamp) + + local AWTOSLeft = UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600) - AWmissiontime),0) -- seconds + + AWTOSLeft = UTILS.Round(AWTOSLeft/60,0) -- minutes + + local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) + + local Changedue = "No" + + if AWTOSLeft <= ChangeTime then Changedue = "Yes" end + + local report = REPORT:New("AWACS:") + report:Add("====================") + report:Add(string.format("Auftrag Status: %s",awstatus)) + report:Add(string.format("TOS Left: %d min",AWTOSLeft)) + report:Add(string.format("Needs ShiftChange: %s",Changedue)) + if self.HasEscorts then + local ESmission = self.EscortMission -- Ops.Auftrag#AUFTRAG + local esstatus = ESmission:GetState() + local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp) + local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds + ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes + local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) + local Changedue = "No" + if ESTOSLeft <= ChangeTime then Changedue = "Yes" end + report:Add("====================") + report:Add("ESCORTS:") + report:Add(string.format("Auftrag Status: %s",esstatus)) + report:Add(string.format("TOS Left: %d min",ESTOSLeft)) + report:Add(string.format("Needs ShiftChange: %s",Changedue)) + report:Add("====================") + end + self:I(report:Text()) + end + self:_CheckAICAPOnStation() + else + -- do other stuff + end + -- Check task queue + self:_CheckTaskQueue() + -- Do some stats + --self:_LogStatistics() + self:__Status(30) + return self +end + +--- [Internal] onafterStop +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #AWACS self +function AWACS:onafterStop(From, Event, To) + self:T({From, Event, To}) + return self +end + +--- [Internal] onafterAssignAnchor +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #AWACS self +function AWACS:onafterAssignAnchor(From, Event, To, ID) + self:T({From, Event, To, "ID = " .. ID}) + self:_AssignAnchorToID(ID) + return self +end + +--- [Internal] onafterCheckedOut +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #AWACS.ManagedGroup.ID ID +-- @param #number AnchorStackNo +-- @param #number Angels +-- @return #AWACS self +function AWACS:onafterCheckedOut(From, Event, To, ID, AnchorStackNo, Angels) + self:T({From, Event, To, "ID = " .. ID}) + self:_RemoveIDFromAnchor(ID,AnchorStackNo,Angels) + return self +end + +--- [Internal] onafterAssignedAnchor +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number ID Managed Group ID +-- @param #AWACS.AnchorData Anchor +-- @param #number AnchorStackNo +-- @return #AWACS self +function AWACS:onafterAssignedAnchor(From, Event, To, ID, Anchor, AnchorStackNo, AnchorAngels) + self:I({From, Event, To, "ID=" .. ID, "Stack=" .. AnchorStackNo}) + -- TODO + local managedgroup = self.ManagedGrps[ID] -- #AWACS.ManagedGroup + managedgroup.AnchorStackNo = AnchorStackNo + managedgroup.AnchorStackAngels = AnchorAngels + self.ManagedGrps[ID] = managedgroup + local isPlayer = managedgroup.IsPlayer + local isAI = managedgroup.IsAI + local Group = managedgroup.Group + local CallSign = managedgroup.CallSign or "unknown 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 200 + local AuftragsNr = managedgroup.CurrentTask + + 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 + 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 RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = textTTS + RadioEntry.TextScreen = textScreen + RadioEntry.GroupID = ID + RadioEntry.IsGroup = isPlayer + RadioEntry.Duration = STTS.getSpeechTime(textTTS,1.0,false) or 10 + RadioEntry.ToScreen = isPlayer + + self.RadioQueue:Push(RadioEntry) + + self:_CreateTaskForGroup(ID,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[ID] = managedgroup + + end + + return self +end + +--- [Internal] onafterNewCluster +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Ops.Intelligence#INTEL.Cluster Cluster +-- @return #AWACS self +function AWACS:onafterNewCluster(From,Event,To,Cluster) + self:T({From, Event, To, Cluster}) + return self +end + +--- [Internal] onafterNewContact +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Ops.Intelligence#INTEL.Contact Contact +-- @return #AWACS self +function AWACS:onafterNewContact(From,Event,To,Contact) + self:T({From, Event, To, Contact}) + self.Contacts:Push(Contact) + self:_AnnounceContact(Contact,true,nil,false) + return self +end + +--- [Internal] onafterLostContact +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Ops.Intelligence#INTEL.Contact Contact +-- @return #AWACS self +function AWACS:onafterLostContact(From,Event,To,Contact) + self:T({From, Event, To, Contact}) + -- TODO Check Idle, Assigned Tasks for status + return self +end + +--- [Internal] onafterLostCluster +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Ops.Intelligence#INTEL.Cluster Cluster +-- @param Ops.Auftrag#AUFTRAG Mission +-- @return #AWACS self +function AWACS:onafterLostCluster(From,Event,To,Cluster,Mission) + self:T({From, Event, To}) + -- TODO Remove Cluster from Picture + return self +end + +--- [Internal] onafterCheckRadioQueue +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #AWACS self +function AWACS:onafterCheckRadioQueue(From,Event,To) + self:T({From, Event, To}) + -- do we have messages queued? + local nextcall = 10 + if self.RadioQueue:IsNotEmpty() then + local RadioEntry = self.RadioQueue:Pull() -- #AWACS.RadioEntry + self:T({RadioEntry}) + self.AwacsFG:RadioTransmission(RadioEntry.TextTTS,1,false) + if RadioEntry.Duration then nextcall = RadioEntry.Duration end + if RadioEntry.ToScreen and RadioEntry.TextScreen then + if RadioEntry.GroupID and RadioEntry.GroupID ~= 0 then + 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) + end + else + MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition) + end + end + end + self:__CheckRadioQueue(nextcall+2) + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- END AWACS +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end -- end do + +--- Testing +_SETTINGS:SetLocale("en") +_SETTINGS:SetImperial() +_SETTINGS:SetPlayerMenuOff() + +-- We need an AirWing +local AwacsAW = AIRWING:New("AirForce WH-1","AirForce One") +AwacsAW:SetReportOn() +AwacsAW:SetMarker(true) +AwacsAW:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Kutaisi)) +AwacsAW:SetRespawnAfterDestroyed(900) +AwacsAW:Start() + +-- 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:SetFuelLowThreshold(0.2) +Squad_One:SetTurnoverTime(10,20) +AwacsAW:AddSquadron(Squad_One) +AwacsAW:NewPayload("Awacs One One",-1,{AUFTRAG.Type.ORBIT},100) + +-- Escorts +local Squad_Two = SQUADRON:New("Escorts",10,"Escorts North") +Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT}) +Squad_Two:SetFuelLowRefuel(true) +Squad_Two:SetFuelLowThreshold(0.3) +Squad_Two:SetTurnoverTime(10,20) +AwacsAW:AddSquadron(Squad_Two) +AwacsAW:NewPayload("Escorts",-1,{AUFTRAG.Type.ESCORT},100) + +-- CAP +local Squad_Three = SQUADRON:New("CAP",10,"CAP North") +Squad_Three:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) +Squad_Three:SetFuelLowRefuel(true) +Squad_Three:SetFuelLowThreshold(0.3) +Squad_Three:SetTurnoverTime(10,20) +AwacsAW:AddSquadron(Squad_Three) +AwacsAW:NewPayload("CAP",-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.Darkstar,1,22,230,61,15) +testawacs:SetSRS("E:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010,nil) +testawacs:__Start(5) From 2d0f5817d02cc080c161090263c23162da9cc1dc Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 19 Apr 2022 15:36:39 +0200 Subject: [PATCH 12/22] Minor Changes --- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 44 ++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 06f7acab9..1ae612d69 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1956,7 +1956,7 @@ function OPSGROUP:RadioTransmission(Text, Delay, SayCallsign) end -- Debug info. - self:I(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) + self:T(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/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 54a58639c..5c182524c 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1475,7 +1475,49 @@ function UTILS.GetCallsignName(Callsign) return name end end - + + for name, value in pairs(CALLSIGN.B1B) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.B52) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.F15E) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.F16) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.F18) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.FARP) do + if value==Callsign then + return name + end + end + + for name, value in pairs(CALLSIGN.TransportAircraft) do + if value==Callsign then + return name + end + end + return "Ghostrider" end From f93033695d066c66b33fdd274693205a677a2949 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 19 Apr 2022 15:38:29 +0200 Subject: [PATCH 13/22] Alpha Updates --- Moose Development/Moose/Ops/Awacs.lua | 633 ++++++++++++++++++++------ 1 file changed, 484 insertions(+), 149 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 1eed0457a..212784e92 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -64,10 +64,16 @@ do -- @field #string AwacsROT -- @field Ops.Auftrag#AUFTRAG AwacsMission -- @field Ops.Auftrag#AUFTRAG EscortMission +-- @field Ops.Auftrag#AUFTRAG AwacsMissionReplacement +-- @field Ops.Auftrag#AUFTRAG EscortMissionReplacement -- @field Utils.FiFo#FIFO AICAPMissions FIFO for Ops.Auftrag#AUFTRAG for AI CAP -- @field #boolean MenuStrict -- @field #number MaxAIonCAP -- @field #number AIonCAP +-- @field #boolean ShiftChangeAwacsFlag +-- @field #boolean ShiftChangeEscortsFlag +-- @field #boolean ShiftChangeAwacsRequested +-- @field #boolean ShiftChangeEscortsRequested -- @extends Core.Fsm#FSM --- @@ -75,7 +81,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "alpha 0.0.2", -- #string + version = "alpha 0.0.3", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -116,6 +122,10 @@ AWACS = { MaxAIonCAP = 4, AIonCAP = 0, AICAPMissions = {}, -- Utils.FiFo#FIFO + ShiftChangeAwacsFlag = false, + ShiftChangeEscortsFlag = false, + ShiftChangeAwacsRequested = false, + ShiftChangeEscortsRequested = false, } --- @@ -191,8 +201,8 @@ AWACS.THREATLEVEL = { -- @field #boolean IsAI -- @field #string CallSign -- @field #number CurrentTask --- @field #boolean AssignedTask --- @field #number ID +-- @field #boolean HasAssignedTask +-- @field #number GID -- @field #number AnchorStackNo -- @field #number AnchorStackAngels @@ -218,9 +228,10 @@ AWACS.TaskStatus = { --- -- @type AWACS.ManagedTask --- @field #number ID +-- @field #number TID -- @field #number AssignedGroupID -- @field #boolean IsPlayerTask +-- @field #boolean IsUnassigned -- @field Ops.Target#TARGET Target -- @field Ops.Auftrag#AUFTRAG Auftrag -- @field #AWACS.TaskStatus Status @@ -252,6 +263,7 @@ AWACS.TaskStatus = { --@field #boolean GroupID --@field #number Duration --@field #boolean ToScreen +--@field #boolean FromAI ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO-List @@ -268,13 +280,13 @@ AWACS.TaskStatus = { -- DONE - Intel Detection -- TODO - CHIEF / COMMANDER / AIRWING connection? -- TODO - LotATC / IFF --- TODO - ROE +-- DONE - ROE -- TODO - Player tasking -- TODO - Stack Management -- TODO - Reporting -- TODO - Missile launch callout -- TODO - Localization --- TODO - Shift Length AWACS/AI +-- DONE - Shift Length AWACS/AI -- TODO - Shift Change, Change on asset RTB or dead or mission done -- TODO - Borders for INTEL -- TODO - FIFO for checkin/checkout and tasking @@ -355,7 +367,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ local speed = 250 self.SpeedBase = speed self.Speed = UTILS.KnotsToAltKIAS(speed,self.AwacsAngels*1000) - self.CapSpeedBase = 200 + self.CapSpeedBase = 220 self.Heading = 0 -- north self.Leg = 50 -- nm self.invisible = true @@ -364,9 +376,11 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.AwacsTimeOnStation = 2 self.AwacsTimeStamp = 0 - self.EscortsTimeOnStation = 2 + self.EscortsTimeOnStation = 0.5 self.EscortsTimeStamp = 0 - self.ShiftChangeTime = 0.5 + self.ShiftChangeTime = 0.25 -- 15mins + self.ShiftChangeAwacsFlag = false + self.ShiftChangeEscortsFlag = false self.AwacsROE = AWACS.ROE.POLICE self.AwacsROT = AWACS.ROT.PASSIVE @@ -383,10 +397,16 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.Culture = "en-US" self.Voice = nil self.Port = 5002 - self.clientset = SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(self.coalitiontxt):FilterStart() self.RadioQueue = FIFO:New() -- Utils.FiFo#FIFO self.maxspeakentries = 3 + self.CAPGender = "male" + self.CAPCulture = "en-US" + self.CAPVoice = nil + + -- Client SET + self.clientset = SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(self.coalitiontxt):FilterStart() + -- managed groups self.ManagedGrps = {} -- #table of #AWACS.ManagedGroup entries self.ManagedGrpID = 0 @@ -411,12 +431,13 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.OpenTasks = FIFO:New() -- Utils.FiFo#FIFO -- Pilot lists - + --[[ ToDo - Maybe only 2? Move to managedgroups self.CAPIdleAI = FIFO:New() -- Utils.FiFo#FIFO self.CAPIdleHuman = FIFO:New() -- Utils.FiFo#FIFO self.TaskedCAPAI = FIFO:New() -- Utils.FiFo#FIFO self.TaskedCAPHuman = FIFO:New() -- Utils.FiFo#FIFO - + --]] + -- Picture, Contacts, Bogeys self.PictureAO = FIFO:New() -- Utils.FiFo#FIFO self.PictureEWR = FIFO:New() -- Utils.FiFo#FIFO @@ -445,6 +466,9 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self:AddTransition("*", "LostCluster", "*") self:AddTransition("*", "LostContact", "*") self:AddTransition("*", "CheckRadioQueue", "*") + self:AddTransition("*", "EscortShiftChange", "*") + self:AddTransition("*", "AwacsShiftChange", "*") + -- self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- self:__Start(math.random(2,5)) @@ -540,8 +564,9 @@ end --- [Internal] Start AWACS Escorts FlightGroup -- @param #AWACS self +-- @param #boolean Shiftchange This is a shift change call -- @return #AWACS self -function AWACS:_StartEscorts() +function AWACS:_StartEscorts(Shiftchange) self:T(self.lid.."_StartEscorts") local AwacsFG = self.AwacsFG -- Ops.FlightGroup#FLIGHTGROUP @@ -553,8 +578,12 @@ function AWACS:_StartEscorts() mission:SetTime(nil,timeonstation) self.AirWing:AddMission(mission) - - self.EscortMission = mission + + if Shiftchange then + self.EscortMissionReplacement = mission + else + self.EscortMission = mission + end return self end @@ -567,11 +596,14 @@ 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 + AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) AwacsFG:SwitchRadio(self.Frequency,self.Modulation) AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) @@ -588,6 +620,9 @@ function AWACS:_StartSettings(FlightGroup,Mission) group:SetCommandInvisible(self.invisible) group:SetCommandImmortal(self.immortal) + group:CommandSetCallsign(self.CallSign,self.CallSignNo,2) + -- 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) self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) @@ -609,14 +644,50 @@ function AWACS:_StartSettings(FlightGroup,Mission) self.AwacsTimeStamp = timer.getTime() self.EscortsTimeStamp = timer.getTime() - else - -- check for CAP missions, Auftragsnummer id UniqueID in FIFO - -- TODO - if self.AICAPMissions:HasUniqueID(Mission.auftragsnummer) then - -- one of ours - -- Register Group - --self:_CheckInAI(FlightGroup,FlightGroup:GetGroup(),Mission.auftragsnummer) + elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName() == Mission:GetName() then + + -- manage AWACS Replacement + AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) + AwacsFG:SwitchRadio(self.Frequency,self.Modulation) + AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) + AwacsFG:SetHomebase(self.Airbase) + self.CallSignNo = self.CallSignNo+1 + AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo) + AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold) + AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) + AwacsFG:SetDefaultEPLRS(self.ModernEra) + AwacsFG:SetDespawnAfterLanding() + AwacsFG:SetFuelLowRTB(true) + AwacsFG:SetFuelLowThreshold(20) + + local group = AwacsFG:GetGroup() -- Wrapper.Group#GROUP + + group:SetCommandInvisible(self.invisible) + group:SetCommandImmortal(self.immortal) + group:CommandSetCallsign(self.CallSign,self.CallSignNo,2) + -- 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) + self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) + + local text = string.format("%s shift change for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") + self:T(self.lid..text) + + AwacsFG:RadioTransmission(text,1,false) + + self.AwacsFG = AwacsFG + + --self:__CheckRadioQueue(10) + + if self.HasEscorts then + --mission:SetRequiredEscorts(self.EscortNumber) + self:_StartEscorts(true) end + + self.AwacsTimeStamp = timer.getTime() + self.EscortsTimeStamp = timer.getTime() + end return self end @@ -628,18 +699,18 @@ end -- @return #boolean CheckedIn function AWACS:_GetManagedGrpID(Group) self:T(self.lid.."_GetManagedGrpID for "..Group:GetName()) - local ID = 0 + local GID = 0 local Outcome = false local nametocheck = Group:GetName() local managedgrps = self.ManagedGrps or {} for _,_managed in pairs (managedgrps) do local managed = _managed -- #AWACS.ManagedGroup if managed.GroupName == nametocheck then - ID = managed.ID + GID = managed.GID Outcome = true end end - return ID, Outcome + return GID, Outcome end --- [Internal] AWACS Get TTS compatible callsign @@ -780,7 +851,7 @@ function AWACS:_Picture(Group) return self end - local ID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome = self:_GetManagedGrpID(Group) if Outcome then -- Pilot is checked in @@ -809,7 +880,7 @@ function AWACS:_Picture(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = textScreen - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 @@ -831,13 +902,13 @@ function AWACS:_Picture(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = text - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreatePicture(true,self:_GetCallSign(Group) or "Unknown 1 1",ID) + self:_CreatePicture(true,self:_GetCallSign(Group) or "Unknown 1 1",GID) end if clustersEWR > 0 then @@ -854,13 +925,13 @@ function AWACS:_Picture(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = textScreen - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreatePicture(false,self:_GetCallSign(Group) or "Unknown 1 1",ID) + self:_CreatePicture(false,self:_GetCallSign(Group) or "Unknown 1 1",GID) end end @@ -871,7 +942,7 @@ function AWACS:_Picture(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = text - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 @@ -907,12 +978,12 @@ function AWACS:_BogeyDope(Group) return self end - local ID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome = self:_GetManagedGrpID(Group) if Outcome then -- Pilot is checked in - local managedgroup = self.ManagedGrps[ID] -- #AWACS.ManagedGroup + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local pilotgroup = managedgroup.Group local pilotcoord = managedgroup.Group:GetCoordinate() @@ -942,7 +1013,7 @@ function AWACS:_BogeyDope(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = textScreen - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = Outcome RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 @@ -963,13 +1034,13 @@ function AWACS:_BogeyDope(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = textScreen - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreateBogeyDope(self:_GetCallSign(Group) or "Unknown 1 1",ID) + self:_CreateBogeyDope(self:_GetCallSign(Group) or "Unknown 1 1",GID) end end @@ -980,7 +1051,7 @@ function AWACS:_BogeyDope(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = text - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 @@ -998,7 +1069,7 @@ end function AWACS:_Declare(Group) self:T(self.lid.."_Declare") - local ID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome = self:_GetManagedGrpID(Group) local text = "Declare Not yet implemented" if Outcome then --[[ yes, known @@ -1013,7 +1084,7 @@ function AWACS:_Declare(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = text - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 @@ -1030,16 +1101,16 @@ end function AWACS:_Showtask(Group) self:T(self.lid.."_Showtask") - local ID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome = self:_GetManagedGrpID(Group) local text = "Showtask WIP" if Outcome then -- known group -- Do we have a task? - local managedgroup = self.ManagedGrps[ID] -- #AWACS.ManagedGroup + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup - if managedgroup.IsPlayer and self.TaskedCAPHuman:HasUniqueID(ID) then + if managedgroup.IsPlayer and self.TaskedCAPHuman:HasUniqueID(GID) then if managedgroup.CurrentTask >0 and self.AssignedTasks:HasUniqueID(managedgroup.CurrentTask) then -- get task structure @@ -1077,7 +1148,7 @@ function AWACS:_Showtask(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = text - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = Outcome RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 @@ -1094,7 +1165,7 @@ end function AWACS:_CheckIn(Group) self:I(self.lid.."_CheckIn "..Group:GetName()) -- check if already known - local ID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome = self:_GetManagedGrpID(Group) local text = "" if not Outcome then self.ManagedGrpID = self.ManagedGrpID + 1 @@ -1105,13 +1176,13 @@ function AWACS:_CheckIn(Group) managedgroup.IsAI = false managedgroup.CallSign = self:_GetCallSign(Group) or "Unknown 1 1" managedgroup.CurrentTask = 0 - managedgroup.AssignedTask = false - managedgroup.ID = self.ManagedGrpID - ID = managedgroup.ID + managedgroup.HasAssignedTask = false + managedgroup.GID = self.ManagedGrpID + GID = managedgroup.GID self.ManagedGrps[self.ManagedGrpID]=managedgroup text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign) - self:__CheckedIn(1,managedgroup.ID) - self:__AssignAnchor(5,managedgroup.ID) + 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") end @@ -1120,7 +1191,7 @@ function AWACS:_CheckIn(Group) RadioEntry.IsNew = true RadioEntry.TextTTS = text RadioEntry.TextScreen = text - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = true RadioEntry.ToScreen = true RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8 @@ -1139,7 +1210,7 @@ end function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) self:I(self.lid.."_CheckInAI "..Group:GetName()) -- check if already known - local ID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome = self:_GetManagedGrpID(Group) local text = "" if not Outcome then self.ManagedGrpID = self.ManagedGrpID + 1 @@ -1151,12 +1222,21 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) managedgroup.IsAI = true managedgroup.CallSign = self:_GetCallSign(Group) or "Unknown 1 1" managedgroup.CurrentTask = AuftragsNr - managedgroup.AssignedTask = false - managedgroup.ID = self.ManagedGrpID + managedgroup.HasAssignedTask = false + managedgroup.GID = self.ManagedGrpID + + -- SRS voice for CAP + --FlightGroup:SetSRS(PathToSRS,Gender,Culture,Voice,Port,PathToGoogleKey) + + FlightGroup:SetDefaultRadio(self.Frequency,self.Modulation,false) + FlightGroup:SwitchRadio(self.Frequency,self.Modulation) + + FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,self.CAPVoice,self.Port,nil) + self.ManagedGrps[self.ManagedGrpID]=managedgroup text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign) - self:__CheckedIn(1,managedgroup.ID) - self:__AssignAnchor(5,managedgroup.ID) + 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") end @@ -1180,17 +1260,17 @@ function AWACS:_CheckOut(Group) self:I(self.lid.."_CheckOut") -- check if already known - local ID, Outcome = self:_GetManagedGrpID(Group) + local GID, Outcome = self:_GetManagedGrpID(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") -- grab some data before we nil the entry - local AnchorAssigned = self.ManagedGrps[ID] -- #AWACS.ManagedGroup + local AnchorAssigned = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local Stack = AnchorAssigned.AnchorStackNo local Angels = AnchorAssigned.AnchorStackAngels - self.ManagedGrps[ID] = nil - self:__CheckedOut(1,ID,Stack,Angels) + self.ManagedGrps[GID] = nil + 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") @@ -1383,9 +1463,9 @@ end --- [Internal] AWACS Assign Anchor Position to a Group -- @param #AWACS self --- @return #number ID Managed Group ID +-- @return #number GID Managed Group ID -- @return #AWACS self -function AWACS:_AssignAnchorToID(ID) +function AWACS:_AssignAnchorToID(GID) self:T(self.lid.."_AssignAnchorToID") local AnchorStackNo, Free = self:_GetFreeAnchorStack() if Free then @@ -1393,8 +1473,8 @@ function AWACS:_AssignAnchorToID(ID) local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData -- pull one free angels local freeangels = Anchor.Anchors:Pull() - -- push ID on anchor - Anchor.AnchorAssignedID:Push(ID) + -- push GID on anchor + Anchor.AnchorAssignedID:Push(GID) if self.debug then --Anchor.AnchorAssignedID:Flush() --Anchor.Anchors:Flush() @@ -1402,28 +1482,28 @@ function AWACS:_AssignAnchorToID(ID) -- push back to AnchorStacks self.AnchorStacks:Push(Anchor) self:T({Anchor,freeangels}) - self:__AssignedAnchor(5,ID,Anchor,AnchorStackNo,freeangels) + self:__AssignedAnchor(5,GID,Anchor,AnchorStackNo,freeangels) else - self:E(self.lid .. "Cannot assing free anchor stack to ID ".. ID) + self:E(self.lid .. "Cannot assing free anchor stack to GID ".. GID) -- try again ... - self:__AssignAnchor(10,ID) + self:__AssignAnchor(10,GID) end return self end ---- [Internal] Remove ID (group) from Anchor Stack +--- [Internal] Remove GID (group) from Anchor Stack -- @param #AWACS self --- @param #AWACS.ManagedGroup.ID ID +-- @param #AWACS.ManagedGroup.GID ID -- @param #number AnchorStackNo -- @param #number Angels -- @return #AWACS self -function AWACS:_RemoveIDFromAnchor(ID,AnchorStackNo,Angels) - self:I(self.lid.."_RemoveIDFromAnchor for ID="..ID.." Stack="..AnchorStackNo.." Angels="..Angels) +function AWACS:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels) + self:I(self.lid.."_RemoveIDFromAnchor for GID="..GID.." Stack="..AnchorStackNo.." Angels="..Angels) -- pull correct anchor local AnchorStackNo = AnchorStackNo or 1 local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData - -- pull ID from stack - local removedID = Anchor.AnchorAssignedID:PullByID(ID) + -- pull GID from stack + local removedID = Anchor.AnchorAssignedID:PullByID(GID) -- push free angels to stack Anchor.Anchors:Push(Angels) -- push back AnchorStack @@ -1534,7 +1614,7 @@ function AWACS:_GetBRAfromBullsOrAO(clustercoordinate) return BRAText end ---- [Internal] Register Task for Group by ID +--- [Internal] Register Task for Group by GID -- @param #AWACS self -- @param #number GroupID ManagedGroup ID -- @param #AWACS.TaskDescription Description Short Description Task Type @@ -1546,7 +1626,7 @@ function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object) local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup local task = {} -- #AWACS.ManagedTask self.ManagedTaskID = self.ManagedTaskID + 1 - task.ID = self.ManagedTaskID + task.TID = self.ManagedTaskID task.AssignedGroupID = GroupID task.Status = AWACS.TaskStatus.ASSIGNED task.ToDo = Description @@ -1555,35 +1635,50 @@ 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.ID) + self.AssignedTasks:Push(task,task.TID) --self:I({task}) - managedgroup.AssignedTask = true - managedgroup.CurrentTask = task.ID + 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 + --if managedgroup.IsPlayer then + --self.TaskedCAPHuman:Push(GroupID) + --elseif managedgroup.IsAI then + --self.TaskedCAPAI:Push(GroupID) + --end return self end ---- [Internal] Get registered Task for Group by its ID +--- [Internal] Read registered Task for Group by its ID -- @param #AWACS self -- @param #number GroupID ManagedGroup ID -- @return #AWACS.ManagedTask Task or nil if n/e -function AWACS:_GetAssignedTaskFromID(GroupID) - self:T(self.lid.."_GetAssignedTaskFromID "..GroupID) +function AWACS:_ReadAssignedTaskFromGID(GroupID) + self:T(self.lid.."_GetAssignedTaskFromGID "..GroupID) local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup - if managedgroup and managedgroup.AssignedTask then + if managedgroup and managedgroup.HasAssignedTask then local TaskID = managedgroup.CurrentTask if self.AssignedTasks:HasUniqueID(TaskID) then - return self.AssignedTasks:PullByID(TaskID) + return self.AssignedTasks:ReadByID(TaskID) end end return nil end +--- [Internal] Read assigned Group from a TaskID +-- @param #AWACS self +-- @param #number TaskID ManagedTask ID +-- @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 task and task.AssignedGroupID and task.AssignedGroupID > 0 then + return self.ManagedGrps[task.AssignedGroupID] + end + end + return nil +end + --- [Internal] Create new idle task from contact to pick up later -- @param #AWACS self -- @param #string Description Task Type @@ -1594,7 +1689,7 @@ function AWACS:_CreateIdleTaskForContact(Description,Object,Contact) self:T(self.lid.."_CreateIdleTaskForContact "..Description) local task = {} -- #AWACS.ManagedTask self.ManagedTaskID = self.ManagedTaskID + 1 - task.ID = self.ManagedTaskID + task.TID = self.ManagedTaskID task.AssignedGroupID = 0 task.Status = AWACS.TaskStatus.IDLE task.ToDo = Description @@ -1605,7 +1700,7 @@ function AWACS:_CreateIdleTaskForContact(Description,Object,Contact) if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then task.Target.Type = TARGET.ObjectType.ZONE end - self.OpenTasks:Push(task,task.ID) + self.OpenTasks:Push(task,task.TID) return self end @@ -1619,7 +1714,7 @@ function AWACS:_CreateIdleTaskForCluster(Description,Object,Cluster) self:T(self.lid.."_CreateIdleTaskForCluster "..Description) local task = {} -- #AWACS.ManagedTask self.ManagedTaskID = self.ManagedTaskID + 1 - task.ID = self.ManagedTaskID + task.TID = self.ManagedTaskID task.AssignedGroupID = 0 task.Status = AWACS.TaskStatus.IDLE task.ToDo = Description @@ -1632,10 +1727,36 @@ function AWACS:_CreateIdleTaskForCluster(Description,Object,Cluster) if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then task.Target.Type = TARGET.ObjectType.ZONE end - self.OpenTasks:Push(task,task.ID) + self.OpenTasks:Push(task,task.TID) return self end +--- [Internal] Create radio entry to tell players that CAP is on station in Anchor +-- @param #AWACS self +-- @param #number GID Group ID +-- @return #AWACS self +function AWACS:_MessageAIReadyForTasking(GID) + self:I(self.lid.."_MessageAIReadyForTasking") + -- obtain group details + if GID >0 and self.ManagedGrps[GID] then + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + local GFCallsign = self:_GetCallSign(managedgroup.Group) + local TextTTS = string.format("%s. %s. On station over anchor %d at angels %d. Ready for tasking.",GFCallsign,self.callsigntxt,managedgroup.AnchorStackNo or 1,managedgroup.AnchorStackAngels or 25) + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.TextTTS = TextTTS + RadioEntry.TextScreen = "" + RadioEntry.IsNew = true + RadioEntry.IsGroup = false + RadioEntry.GroupID = GID + RadioEntry.Duration = STTS.getSpeechTime(TextTTS,1.2,false)+2 or 16 + RadioEntry.ToScreen = false + RadioEntry.FromAI = true + + self.RadioQueue:Push(RadioEntry) + end + return self +end + --- [Internal] Check available tasks and status -- @param #AWACS self -- @return #AWACS self @@ -1661,7 +1782,7 @@ function AWACS:_CheckTaskQueue() local group = managedgroup.Group local groupcoord = group:GetCoordinate() local zone = target:GetObject() -- Core.Zone#ZONE - self:I({zone}) + self:T({zone}) if group:IsInZone(zone) then self:I("Open Tasks ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) -- made it @@ -1707,7 +1828,7 @@ function AWACS:_CheckTaskQueue() local group = managedgroup.Group local groupcoord = group:GetCoordinate() local zone = target:GetObject() -- Core.Zone#ZONE - self:I({zone}) + self:T({zone}) if group:IsInZone(zone) then self:I("Open Tasks ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) -- made it @@ -1716,11 +1837,11 @@ function AWACS:_CheckTaskQueue() self.AssignedTasks:PullByPointer(_id) -- add group to idle stack if managedgroup.IsAI then - self.TaskedCAPAI:PullByPointer(entry.AssignedGroupID) - self.CAPIdleAI:Push(entry.AssignedGroupID) + -- message AI on station + self:_MessageAIReadyForTasking(managedgroup.GID) elseif managedgroup.IsPlayer then - self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID) - self.CAPIdleHuman:Push(entry.AssignedGroupID) + --self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID) + ---self.CAPIdleHuman:Push(entry.AssignedGroupID) end else -- not there yet @@ -1756,11 +1877,11 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) self:T(self.lid.."_AnnounceContact") -- do we have a group to talk to? local isGroup = false - local ID = 0 + local GID = 0 local grpcallsign = "Unknown 1 1" if Group and Group:IsAlive() then - ID, isGroup = self:_GetManagedGrpID(Group) - self:T("ID="..ID.." CheckedIn = "..tostring(isGroup)) + GID, isGroup = self:_GetManagedGrpID(Group) + self:T("GID="..GID.." CheckedIn = "..tostring(isGroup)) grpcallsign = self:_GetCallSign(Group) or "Unknown 1 1" end local contact = Contact -- Ops.Intelligence#INTEL.Contact @@ -1805,7 +1926,7 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope) RadioEntry.TextScreen = TextScreen RadioEntry.IsNew = IsNew RadioEntry.IsGroup = isGroup - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.Duration = STTS.getSpeechTime(TextTTS,1.2,false)+2 or 16 RadioEntry.ToScreen = true @@ -1857,7 +1978,7 @@ function AWACS:_CheckAICAPOnStation() mission:__Cancel(5) self:_CheckOut(OpsGroup) end - -- Check Alert5 Mission states + -- Check CAP Mission states if onstation > 0 then local missionIDs = self.AICAPMissions:GetIDStackSorted() --self:_CheckInAI(FlightGroup,FlightGroup:GetGroup(),Mission.auftragsnummer) @@ -1901,15 +2022,21 @@ function AWACS:_CheckAICAPOnStation() report:Add(string.format("Mission State %s",mission:GetState())) local OpsGroups = mission:GetOpsGroups() local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP + if OpsGroup then local OpsName = OpsGroup:GetName() or "Unknown" - report:Add(string.format("Mission FG %s",OpsName)) - report:Add(string.format("Mission FG State %s",mission:GetGroupStatus(OpsGroup))) + 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())) + else + report:Add("***** Cannot obtain (yet) this missions OpsGroup!") + end report:Add(string.format("Target Type %s",mission:GetTargetType())) end report:Add("===============") end if self.debug then - self:I(report:Text()) + self:T(report:Text()) end end end @@ -2027,55 +2154,193 @@ function AWACS:onafterStatus(From, Event, To) self.RadioQueue:Push(RadioEntry) self:_StartIntel(awacs) end - end + end + + -------------------------------- + -- AWACS + -------------------------------- + if (awacs and awacs:IsAlive()) then + -- Check on Awacs Mission Status - if self.debug then - local AWmission = self.AwacsMission -- Ops.Auftrag#AUFTRAG - local awstatus = AWmission:GetState() - local AWmissiontime = (timer.getTime() - self.AwacsTimeStamp) - - local AWTOSLeft = UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600) - AWmissiontime),0) -- seconds - - AWTOSLeft = UTILS.Round(AWTOSLeft/60,0) -- minutes - + local AWmission = self.AwacsMission -- Ops.Auftrag#AUFTRAG + local awstatus = AWmission:GetState() + local AWmissiontime = (timer.getTime() - self.AwacsTimeStamp) + + local AWTOSLeft = UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600) - AWmissiontime),0) -- seconds + + AWTOSLeft = UTILS.Round(AWTOSLeft/60,0) -- minutes + + local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) + + local Changedue = "No" + + if not self.ShiftChangeAwacsFlag and (AWTOSLeft <= ChangeTime or AWmission:IsOver()) then + Changedue = "Yes" + self.ShiftChangeAwacsFlag = true + self:__AwacsShiftChange(2) + end + + local report = REPORT:New("AWACS:") + report:Add("====================") + report:Add("AWACS:") + report:Add(string.format("Auftrag Status: %s",awstatus)) + report:Add(string.format("TOS Left: %d min",AWTOSLeft)) + report:Add(string.format("Needs ShiftChange: %s",Changedue)) + + local OpsGroups = AWmission:GetOpsGroups() + local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP + if OpsGroup then + 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())) + else + report:Add("***** Cannot obtain (yet) this missions OpsGroup!") + end + + -- Check for replacement mission - if any + if self.ShiftChangeAwacsFlag and self.ShiftChangeAwacsRequested then -- Ops.Auftrag#AUFTRAG + AWmission = self.EscortMissionReplacement + local esstatus = AWmission:GetState() + local ESmissiontime = (timer.getTime() - self.AwacsTimeStamp) + local ESTOSLeft = UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds + ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) + --local Changedue = "No" + --report:Add("====================") + report:Add("AWACS REPLACEMENT:") + report:Add(string.format("Auftrag Status: %s",esstatus)) + report:Add(string.format("TOS Left: %d min",ESTOSLeft)) + --report:Add(string.format("Needs ShiftChange: %s",Changedue)) + + local OpsGroups = AWmission:GetOpsGroups() + local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP + if OpsGroup then + 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())) + else + report:Add("***** Cannot obtain (yet) this missions OpsGroup!") + end + + if AWmission:IsExecuting() then + -- make the actual change in the queue + self.ShiftChangeAwacsFlag = false + self.ShiftChangeAwacsRequested = false + -- cancel old mission + if self.AwacsMission and self.AwacsMission:IsNotOver() then + self.AwacsMission:Cancel() + end + self.AwacsMission = self.AwacsMissionReplacement + self.AwacsMissionReplacement = nil + self.AwacsTimeStamp = timer.getTime() + report:Add("*** Replacement DONE ***") + end + report:Add("====================") + end + + -------------------------------- + -- ESCORTS + -------------------------------- + + if self.HasEscorts then + local ESmission = self.EscortMission -- Ops.Auftrag#AUFTRAG + local esstatus = ESmission:GetState() + local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp) + local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds + ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes + local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) local Changedue = "No" - if AWTOSLeft <= ChangeTime then Changedue = "Yes" end + if (ESTOSLeft <= ChangeTime and not self.ShiftChangeEscortsFlag) or (ESmission:IsOver() and not self.ShiftChangeEscortsFlag) then + Changedue = "Yes" + self.ShiftChangeEscortsFlag = true -- set this back when new Escorts arrived + self:__EscortShiftChange(2) + end - local report = REPORT:New("AWACS:") report:Add("====================") - report:Add(string.format("Auftrag Status: %s",awstatus)) - report:Add(string.format("TOS Left: %d min",AWTOSLeft)) + report:Add("ESCORTS:") + report:Add(string.format("Auftrag Status: %s",esstatus)) + report:Add(string.format("TOS Left: %d min",ESTOSLeft)) report:Add(string.format("Needs ShiftChange: %s",Changedue)) - if self.HasEscorts then - local ESmission = self.EscortMission -- Ops.Auftrag#AUFTRAG + + local OpsGroups = ESmission:GetOpsGroups() + local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP + if OpsGroup then + 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())) + else + report:Add("***** Cannot obtain (yet) this missions OpsGroup!") + end + + report:Add("====================") + + -- Check for replacement mission - if any + if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG + ESmission = self.EscortMissionReplacement local esstatus = ESmission:GetState() local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp) local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) - local Changedue = "No" - if ESTOSLeft <= ChangeTime then Changedue = "Yes" end - report:Add("====================") - report:Add("ESCORTS:") + --local Changedue = "No" + + --report:Add("====================") + report:Add("ESCORTS REPLACEMENT:") report:Add(string.format("Auftrag Status: %s",esstatus)) report:Add(string.format("TOS Left: %d min",ESTOSLeft)) - report:Add(string.format("Needs ShiftChange: %s",Changedue)) + --report:Add(string.format("Needs ShiftChange: %s",Changedue)) + + local OpsGroups = ESmission:GetOpsGroups() + local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP + if OpsGroup then + 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())) + else + report:Add("***** Cannot obtain (yet) this missions OpsGroup!") + end + + if ESmission:IsExecuting() then + -- make the actual change in the queue + self.ShiftChangeEscortsFlag = false + self.ShiftChangeEscortsRequested = false + -- cancel old mission + if self.EscortMission and self.EscortMission:IsNotOver() then + self.EscortMission:Cancel() + end + self.EscortMission = self.EscortMissionReplacement + self.EscortMissionReplacement = nil + self.EscortsTimeStamp = timer.getTime() + report:Add("*** Replacement DONE ***") + end report:Add("====================") end + end + + if self.debug then self:I(report:Text()) end + -- Check on AUFTRAG status for CAP AI self:_CheckAICAPOnStation() else -- do other stuff + end - -- Check task queue + + -- Check task queue (both) self:_CheckTaskQueue() - -- Do some stats - --self:_LogStatistics() + self:__Status(30) return self end @@ -2096,10 +2361,11 @@ end -- @param #string From -- @param #string Event -- @param #string To +-- @param #number GID Group ID -- @return #AWACS self -function AWACS:onafterAssignAnchor(From, Event, To, ID) - self:T({From, Event, To, "ID = " .. ID}) - self:_AssignAnchorToID(ID) +function AWACS:onafterAssignAnchor(From, Event, To, GID) + self:T({From, Event, To, "GID = " .. GID}) + self:_AssignAnchorToID(GID) return self end @@ -2108,13 +2374,13 @@ end -- @param #string From -- @param #string Event -- @param #string To --- @param #AWACS.ManagedGroup.ID ID +-- @param #AWACS.ManagedGroup.GID Group ID -- @param #number AnchorStackNo -- @param #number Angels -- @return #AWACS self -function AWACS:onafterCheckedOut(From, Event, To, ID, AnchorStackNo, Angels) - self:T({From, Event, To, "ID = " .. ID}) - self:_RemoveIDFromAnchor(ID,AnchorStackNo,Angels) +function AWACS:onafterCheckedOut(From, Event, To, GID, AnchorStackNo, Angels) + self:T({From, Event, To, "GID = " .. GID}) + self:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels) return self end @@ -2123,17 +2389,17 @@ end -- @param #string From -- @param #string Event -- @param #string To --- @param #number ID Managed Group ID +-- @param #number GID Managed Group ID -- @param #AWACS.AnchorData Anchor -- @param #number AnchorStackNo -- @return #AWACS self -function AWACS:onafterAssignedAnchor(From, Event, To, ID, Anchor, AnchorStackNo, AnchorAngels) - self:I({From, Event, To, "ID=" .. ID, "Stack=" .. AnchorStackNo}) +function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo, AnchorAngels) + self:I({From, Event, To, "GID=" .. GID, "Stack=" .. AnchorStackNo}) -- TODO - local managedgroup = self.ManagedGrps[ID] -- #AWACS.ManagedGroup + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup managedgroup.AnchorStackNo = AnchorStackNo managedgroup.AnchorStackAngels = AnchorAngels - self.ManagedGrps[ID] = managedgroup + self.ManagedGrps[GID] = managedgroup local isPlayer = managedgroup.IsPlayer local isAI = managedgroup.IsAI local Group = managedgroup.Group @@ -2141,7 +2407,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, ID, Anchor, AnchorStackNo, local AnchorName = Anchor.AnchorZone:GetName() or "unknown" local AnchorCoordTxt = Anchor.AnchorZoneCoordinateText or "unknown" local Angels = AnchorAngels or 25 - local AnchorSpeed = self.CapSpeedBase or 200 + local AnchorSpeed = self.CapSpeedBase or 220 local AuftragsNr = managedgroup.CurrentTask 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) @@ -2153,14 +2419,14 @@ function AWACS:onafterAssignedAnchor(From, Event, To, ID, Anchor, AnchorStackNo, RadioEntry.IsNew = true RadioEntry.TextTTS = textTTS RadioEntry.TextScreen = textScreen - RadioEntry.GroupID = ID + RadioEntry.GroupID = GID RadioEntry.IsGroup = isPlayer RadioEntry.Duration = STTS.getSpeechTime(textTTS,1.0,false) or 10 RadioEntry.ToScreen = isPlayer self.RadioQueue:Push(RadioEntry) - self:_CreateTaskForGroup(ID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.AnchorZone) + 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 @@ -2172,8 +2438,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, ID, Anchor, AnchorStackNo, self.AICAPMissions:PullByID(AuftragsNr) self.AICAPMissions:Push(AnchorMission,AnchorMission.auftragsnummer) managedgroup.CurrentTask = AnchorMission.auftragsnummer - self.ManagedGrps[ID] = managedgroup - + self.ManagedGrps[GID] = managedgroup end return self @@ -2241,11 +2506,26 @@ end function AWACS:onafterCheckRadioQueue(From,Event,To) self:T({From, Event, To}) -- do we have messages queued? + local nextcall = 10 if self.RadioQueue:IsNotEmpty() then local RadioEntry = self.RadioQueue:Pull() -- #AWACS.RadioEntry + self:T({RadioEntry}) - self.AwacsFG:RadioTransmission(RadioEntry.TextTTS,1,false) + + if not RadioEntry.FromAI then + -- AI AWACS Speaking + self.AwacsFG:RadioTransmission(RadioEntry.TextTTS,1,false) + 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) + end + end + end + if RadioEntry.Duration then nextcall = RadioEntry.Duration end if RadioEntry.ToScreen and RadioEntry.TextScreen then if RadioEntry.GroupID and RadioEntry.GroupID ~= 0 then @@ -2258,10 +2538,65 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) end end end + self:__CheckRadioQueue(nextcall+2) + return self end +--- [Internal] onafterEscortShiftChange +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #AWACS self +function AWACS:onafterEscortShiftChange(From,Event,To) + self:I({From, Event, To}) + -- request new Escorts, check if AWACS-FG still alive first! + if self.AwacsFG and self.ShiftChangeEscortsFlag and not self.ShiftChangeEscortsRequested then + local awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP + if awacs and awacs:IsAlive() then + -- ok we're good to re-request + self.ShiftChangeEscortsRequested = true + self.EscortsTimeStamp = timer.getTime() + self:_StartEscorts(true) + else + -- should not happen + self:E("**** AWACS group dead at onafterEscortShiftChange!") + end + end + return self +end + +--- [Internal] onafterAwacsShiftChange +-- @param #AWACS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #AWACS self +function AWACS:onafterAwacsShiftChange(From,Event,To) + self:I({From, Event, To}) + -- request new Escorts, check if AWACS-FG still alive first! + if self.AwacsFG and self.ShiftChangeAwacsFlag and not self.ShiftChangeAwacsRequested then + + -- ok we're good to re-request + self.ShiftChangeAwacsRequested = true + self.AwacsTimeStamp = timer.getTime() + + -- 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) + local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 + mission:SetTime(nil,timeonstation) + + AwacsAW:AddMission(mission) + + self.AwacsMissionReplacement = mission + + end + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- END AWACS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2291,7 +2626,7 @@ AwacsAW:AddSquadron(Squad_One) AwacsAW:NewPayload("Awacs One One",-1,{AUFTRAG.Type.ORBIT},100) -- Escorts -local Squad_Two = SQUADRON:New("Escorts",10,"Escorts North") +local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North") Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT}) Squad_Two:SetFuelLowRefuel(true) Squad_Two:SetFuelLowThreshold(0.3) From 2c53ebc0e40cce74d1b0afb5e17042e08fd86f3b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 08:32:57 +0200 Subject: [PATCH 14/22] FIFO Added Routines --- Moose Development/Moose/Utilities/FiFo.lua | 162 ++++++++++++++++++++- 1 file changed, 158 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Utilities/FiFo.lua b/Moose Development/Moose/Utilities/FiFo.lua index c2e738468..6d8654931 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.2", + version = "0.0.3", counter = 0, pointer = 0, stackbypointer = {}, @@ -80,7 +80,7 @@ end --- FIFO Push Object to Stack -- @param #FIFO self -- @param #table Object --- @param #string UniqueID (optional) - will default to current pointer + 1 +-- @param #string UniqueID (optional) - will default to current pointer + 1. Note - if you intend to use `FIFO:GetIDStackSorted()` keep the UniqueID numerical! -- @return #FIFO self function FIFO:Push(Object,UniqueID) self:T(self.lid.."Push") @@ -147,7 +147,7 @@ end --- FIFO Read, not Pull, Object from Stack by UniqueID -- @param #FIFO self -- @param #number UniqueID --- @return #table Object or nil if stack is empty or ID does not exist +-- @return #table Object data or nil if stack is empty or ID does not exist 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 @@ -250,7 +250,7 @@ end --- FIFO Get table of UniqueIDs sorted smallest to largest -- @param #FIFO self --- @return #table Table of #FIFO.IDEntry entries +-- @return #table Table with index [1] to [n] of UniqueID entries function FIFO:GetIDStackSorted() self:T(self.lid.."GetIDStackSorted") @@ -271,6 +271,83 @@ function FIFO:GetIDStackSorted() return idstack end +--- FIFO Get table of data entries +-- @param #FIFO self +-- @return #table Raw table indexed [1] to [n] of object entries - might be empty! +function FIFO:GetDataTable() + self:T(self.lid.."GetDataTable") + local datatable = {} + for _,_entry in pairs(self.stackbypointer) do + datatable[#datatable+1] = _entry.data + end + return datatable +end + +--- FIFO Get sorted table of data entries by UniqueIDs (must be numerical UniqueIDs only!) +-- @param #FIFO self +-- @return #table Table indexed [1] to [n] of sorted object entries - might be empty! +function FIFO:GetSortedDataTable() + self:T(self.lid.."GetSortedDataTable") + local datatable = {} + local idtablesorted = self:GetIDStackSorted() + for _,_entry in pairs(idtablesorted) do + datatable[#datatable+1] = self:ReadByID(_entry) + end + return datatable +end + +--- Iterate the FIFO and call an iterator function for the given FIFO data, providing the object for each element of the stack and optional parameters. +-- @param #FIFO self +-- @param #function IteratorFunction The function that will be called. +-- @param #table Arg (Optional) Further Arguments of the IteratorFunction. +-- @param #function Function (Optional) A function returning a #boolean true/false. Only if true, the IteratorFunction is called. +-- @param #table FunctionArguments (Optional) Function arguments. +-- @return #FIFO self +function FIFO:ForEach( IteratorFunction, Arg, Function, FunctionArguments ) + self:T(self.lid.."ForEach") + + local Set = self:GetPointerStack() or {} + Arg = Arg or {} + + local function CoRoutine() + local Count = 0 + for ObjectID, ObjectData in pairs( Set ) do + local Object = ObjectData.data + self:T( {Object} ) + if Function then + if Function( unpack( FunctionArguments or {} ), Object ) == true then + IteratorFunction( Object, unpack( Arg ) ) + end + else + IteratorFunction( Object, unpack( Arg ) ) + end + Count = Count + 1 + end + return true + end + + local co = CoRoutine + + local function Schedule() + + local status, res = co() + self:T( { status, res } ) + + if status == false then + error( res ) + end + if res == false then + return true -- resume next time the loop + end + + return false + end + + Schedule() + + return self +end + --- FIFO Print stacks to dcs.log -- @param #FIFO self -- @return #FIFO self @@ -585,6 +662,83 @@ function LIFO:Flush() return self end +--- LIFO Get table of data entries +-- @param #LIFO self +-- @return #table Raw table indexed [1] to [n] of object entries - might be empty! +function LIFO:GetDataTable() + self:T(self.lid.."GetDataTable") + local datatable = {} + for _,_entry in pairs(self.stackbypointer) do + datatable[#datatable+1] = _entry.data + end + return datatable +end + +--- LIFO Get sorted table of data entries by UniqueIDs (must be numerical UniqueIDs only!) +-- @param #LIFO self +-- @return #table Table indexed [1] to [n] of sorted object entries - might be empty! +function LIFO:GetSortedDataTable() + self:T(self.lid.."GetSortedDataTable") + local datatable = {} + local idtablesorted = self:GetIDStackSorted() + for _,_entry in pairs(idtablesorted) do + datatable[#datatable+1] = self:ReadByID(_entry) + end + return datatable +end + +--- Iterate the LIFO and call an iterator function for the given LIFO data, providing the object for each element of the stack and optional parameters. +-- @param #LIFO self +-- @param #function IteratorFunction The function that will be called. +-- @param #table Arg (Optional) Further Arguments of the IteratorFunction. +-- @param #function Function (Optional) A function returning a #boolean true/false. Only if true, the IteratorFunction is called. +-- @param #table FunctionArguments (Optional) Function arguments. +-- @return #LIFO self +function LIFO:ForEach( IteratorFunction, Arg, Function, FunctionArguments ) + self:T(self.lid.."ForEach") + + local Set = self:GetPointerStack() or {} + Arg = Arg or {} + + local function CoRoutine() + local Count = 0 + for ObjectID, ObjectData in pairs( Set ) do + local Object = ObjectData.data + self:T( {Object} ) + if Function then + if Function( unpack( FunctionArguments or {} ), Object ) == true then + IteratorFunction( Object, unpack( Arg ) ) + end + else + IteratorFunction( Object, unpack( Arg ) ) + end + Count = Count + 1 + end + return true + end + + local co = CoRoutine + + local function Schedule() + + local status, res = co() + self:T( { status, res } ) + + if status == false then + error( res ) + end + if res == false then + return true -- resume next time the loop + end + + return false + end + + Schedule() + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- End LIFO ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 8735dadbca6ec0fd162248c2bd5e7ad01685f39b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 08:33:11 +0200 Subject: [PATCH 15/22] Awacs updates --- Moose Development/Moose/Ops/Awacs.lua | 283 ++++++++++++++++++++------ 1 file changed, 220 insertions(+), 63 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 212784e92..995402c63 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -44,18 +44,19 @@ do -- @field #table ManagedGrps -- @field #number ManagedGrpID -- @field #number ManagedTaskID --- @field Utils.FiFo#FIFO AnchorStacks --- @field Utils.FiFo#FIFO CAPIdleAI --- @field Utils.FiFo#FIFO CAPIdleHuman --- @field Utils.FiFo#FIFO TaskedCAPAI --- @field Utils.FiFo#FIFO TaskedCAPHuman --- @field Utils.FiFo#FIFO OpenTasks --- @field Utils.FiFo#FIFO AssignedTasks --- @field Utils.FiFo#FIFO PictureAO --- @field Utils.FiFo#FIFO PictureEWR --- @field Utils.FiFo#FIFO Contacts --- @field Utils.FiFo#FIFO ContactsAO --- @field Utils.FiFo#FIFO RadioQueue +-- @field Utilities.FiFo#FIFO AnchorStacks +-- @field Utilities.FiFo#FIFO CAPIdleAI +-- @field Utilities.FiFo#FIFO CAPIdleHuman +-- @field Utilities.FiFo#FIFO TaskedCAPAI +-- @field Utilities.FiFo#FIFO TaskedCAPHuman +-- @field Utilities.FiFo#FIFO OpenTasks +-- @field Utilities.FiFo#FIFO AssignedTasks +-- @field Utilities.FiFo#FIFO PictureAO +-- @field Utilities.FiFo#FIFO PictureEWR +-- @field Utilities.FiFo#FIFO Contacts +-- @field Utilities.FiFo#FIFO ContactsAO +-- @field Utilities.FiFo#FIFO RadioQueue +-- @field Utilities.FiFo#FIFO CAPAirwings -- @field #number AwacsTimeOnStation -- @field #number AwacsTimeStamp -- @field #number EscortsTimeOnStation @@ -66,7 +67,7 @@ do -- @field Ops.Auftrag#AUFTRAG EscortMission -- @field Ops.Auftrag#AUFTRAG AwacsMissionReplacement -- @field Ops.Auftrag#AUFTRAG EscortMissionReplacement --- @field Utils.FiFo#FIFO AICAPMissions FIFO for Ops.Auftrag#AUFTRAG for AI CAP +-- @field Utilities.FiFo#FIFO AICAPMissions FIFO for Ops.Auftrag#AUFTRAG for AI CAP -- @field #boolean MenuStrict -- @field #number MaxAIonCAP -- @field #number AIonCAP @@ -100,18 +101,18 @@ AWACS = { ManagedGrps = {}, ManagedGrpID = 0, -- #number ManagedTaskID = 0, -- #number - AnchorStacks = {}, -- Utils.FiFo#FIFO + AnchorStacks = {}, -- Utilities.FiFo#FIFO CAPIdleAI = {}, CAPIdleHuman = {}, TaskedCAPAI = {}, TaskedCAPHuman = {}, - OpenTasks = {}, -- Utils.FiFo#FIFO - AssignedTasks = {}, -- Utils.FiFo#FIFO - PictureAO = {}, -- Utils.FiFo#FIFO - PictureEWR = {}, -- Utils.FiFo#FIFO - Contacts = {}, -- Utils.FiFo#FIFO - ContactsAO = {}, -- Utils.FiFo#FIFO - RadioQueue = {}, -- Utils.FiFo#FIFO + OpenTasks = {}, -- Utilities.FiFo#FIFO + AssignedTasks = {}, -- Utilities.FiFo#FIFO + PictureAO = {}, -- Utilities.FiFo#FIFO + PictureEWR = {}, -- Utilities.FiFo#FIFO + Contacts = {}, -- Utilities.FiFo#FIFO + ContactsAO = {}, -- Utilities.FiFo#FIFO + RadioQueue = {}, -- Utilities.FiFo#FIFO AwacsTimeOnStation = 1, AwacsTimeStamp = 0, EscortsTimeOnStation = 0.5, @@ -121,11 +122,12 @@ AWACS = { MenuStrict = true, MaxAIonCAP = 4, AIonCAP = 0, - AICAPMissions = {}, -- Utils.FiFo#FIFO + AICAPMissions = {}, -- Utilities.FiFo#FIFO ShiftChangeAwacsFlag = false, ShiftChangeEscortsFlag = false, ShiftChangeAwacsRequested = false, ShiftChangeEscortsRequested = false, + CAPAirwings = {}, -- Utilities.FiFo#FIFO } --- @@ -251,8 +253,8 @@ AWACS.TaskStatus = { -- @field #boolean AnchorZone -- @field Core.Point#COORDINATE AnchorZoneCoordinate -- @field #boolean AnchorZoneCoordinateText --- @field Utils.FiFo#FIFO AnchorAssignedID FiFo of #AWACS.AnchorAssignedEntry --- @field Utils.FiFo#FIFO Anchors FiFo of available stacks +-- @field Utilities.FiFo#FIFO AnchorAssignedID FiFo of #AWACS.AnchorAssignedEntry +-- @field Utilities.FiFo#FIFO Anchors FiFo of available stacks --- --@type RadioEntry @@ -281,13 +283,13 @@ AWACS.TaskStatus = { -- TODO - CHIEF / COMMANDER / AIRWING connection? -- TODO - LotATC / IFF -- DONE - ROE --- TODO - Player tasking --- TODO - Stack Management +-- TODO - Player & AI tasking +-- DONE - Anchor Stack Management -- TODO - Reporting -- TODO - Missile launch callout -- TODO - Localization -- DONE - Shift Length AWACS/AI --- TODO - Shift Change, Change on asset RTB or dead or mission done +-- 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 @@ -335,6 +337,12 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ -- base setup self.Name = Name -- #string self.AirWing = AirWing -- Ops.AirWing#AIRWING object + + AirWing:SetUsingOpsAwacs(self) + + self.CAPAirwings = FIFO:New() -- Utilities.FiFo#FIFO + self.CAPAirwings:Push(AirWing,1) + self.AwacsFG = nil --self.AwacsPayload = PayLoad -- Ops.AirWing#AIRWING.Payload self.ModernEra = true -- use of EPLRS @@ -362,7 +370,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.NoHelos = true self.MaxAIonCAP = 4 self.AIonCAP = 0 - self.AICAPMissions = FIFO:New() -- Utils.FiFo#FIFO + self.AICAPMissions = FIFO:New() -- Utilities.FiFo#FIFO local speed = 250 self.SpeedBase = speed @@ -376,7 +384,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.AwacsTimeOnStation = 2 self.AwacsTimeStamp = 0 - self.EscortsTimeOnStation = 0.5 + self.EscortsTimeOnStation = 2 self.EscortsTimeStamp = 0 self.ShiftChangeTime = 0.25 -- 15mins self.ShiftChangeAwacsFlag = false @@ -397,7 +405,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.Culture = "en-US" self.Voice = nil self.Port = 5002 - self.RadioQueue = FIFO:New() -- Utils.FiFo#FIFO + self.RadioQueue = FIFO:New() -- Utilities.FiFo#FIFO self.maxspeakentries = 3 self.CAPGender = "male" @@ -415,7 +423,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.AICAPCAllNumber = 0 -- Anchor stacks init - self.AnchorStacks = FIFO:New() -- Utils.FiFo#FIFO + self.AnchorStacks = FIFO:New() -- Utilities.FiFo#FIFO self.AnchorBaseAngels = 22 self.AnchorStackDistance = 2 self.AnchorMaxStacks = 4 @@ -427,22 +435,22 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self:_CreateAnchorStack() -- Task lists - self.AssignedTasks = FIFO:New() -- Utils.FiFo#FIFO - self.OpenTasks = FIFO:New() -- Utils.FiFo#FIFO + self.AssignedTasks = 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() -- Utils.FiFo#FIFO - self.CAPIdleHuman = FIFO:New() -- Utils.FiFo#FIFO - self.TaskedCAPAI = FIFO:New() -- Utils.FiFo#FIFO - self.TaskedCAPHuman = FIFO:New() -- Utils.FiFo#FIFO + 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 --]] -- Picture, Contacts, Bogeys - self.PictureAO = FIFO:New() -- Utils.FiFo#FIFO - self.PictureEWR = FIFO:New() -- Utils.FiFo#FIFO - self.Contacts = FIFO:New() -- Utils.FiFo#FIFO - self.ContactsAO = FIFO:New() -- Utils.FiFo#FIFO + self.PictureAO = FIFO:New() -- Utilities.FiFo#FIFO + self.PictureEWR = FIFO:New() -- Utilities.FiFo#FIFO + self.Contacts = FIFO:New() -- Utilities.FiFo#FIFO + self.ContactsAO = FIFO:New() -- Utilities.FiFo#FIFO -- SET for Intel Detection self.DetectionSet=SET_GROUP:New() @@ -468,6 +476,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self:AddTransition("*", "CheckRadioQueue", "*") self:AddTransition("*", "EscortShiftChange", "*") self:AddTransition("*", "AwacsShiftChange", "*") + self:AddTransition("*", "FlightOnMission", "*") -- self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -495,6 +504,13 @@ end -- Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- [User] Get AWACS Name +-- @param #AWACS self +-- @return #string Name of this instance +function AWACS:GetName() + return self.Name or "not set" +end + --- [User] Set AWACS flight details -- @param #AWACS self -- @param #number CallSign Defaults to CALLSIGN.AWACS.Magic @@ -742,7 +758,7 @@ function AWACS:_CreatePicture(AO,Callsign,GID) local group = managedgroup.Group -- Wrapper.Group#GROUP local groupcoord = group:GetCoordinate() - local fifo = self.PictureAO -- Utils.FIFO#FIFO + local fifo = self.PictureAO -- Utilities.FiFo#FIFO local maxentries = self.maxspeakentries local counter = 0 @@ -802,7 +818,7 @@ function AWACS:_CreateBogeyDope(Callsign,GID) local group = managedgroup.Group -- Wrapper.Group#GROUP local groupcoord = group:GetCoordinate() - local fifo = self.ContactsAO -- Utils.FIFO#FIFO + local fifo = self.ContactsAO -- Utilities.FiFo#FIFO local maxentries = self.maxspeakentries local counter = 0 @@ -1386,8 +1402,8 @@ function AWACS:_CreateAnchorStack() end local AnchorStackOne = {} -- #AWACS.AnchorData AnchorStackOne.AnchorBaseAngels = self.AnchorBaseAngels - AnchorStackOne.Anchors = FIFO:New() -- Utils.FiFo#FIFO - AnchorStackOne.AnchorAssignedID = FIFO:New() -- Utils.FiFo#FIFO + AnchorStackOne.Anchors = FIFO:New() -- Utilities.FiFo#FIFO + AnchorStackOne.AnchorAssignedID = FIFO:New() -- Utilities.FiFo#FIFO local newname = "" for i=1,self.AnchorMaxStacks do AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) @@ -1441,7 +1457,7 @@ function AWACS:_GetFreeAnchorStack() --return AnchorStackNo, Free local availablestacks = self.AnchorStacks:GetPointerStack() or {} -- #table for _id,_entry in pairs(availablestacks) do - local entry = _entry -- Utils.FIFO#FIFO.IDEntry + local entry = _entry -- Utilities.FiFo#FIFO.IDEntry local data = entry.data -- #AWACS.AnchorData if data.Anchors:IsNotEmpty() then AnchorStackNo = _id @@ -1449,7 +1465,7 @@ function AWACS:_GetFreeAnchorStack() break end end - -- TODO - extension of anchor stacks to max, send AI home + -- TODO - if extension of anchor stacks to max, send AI home if not Free then -- try to create another stack local created, number = self:_CreateAnchorStack() @@ -1531,28 +1547,28 @@ function AWACS:_StartIntel(awacs) -- Callbacks local function NewCluster(Cluster) - self:__NewCluster(2,Cluster) + self:__NewCluster(5,Cluster) end function intel:OnAfterNewCluster(From,Event,To,Cluster) NewCluster(Cluster) end local function NewContact(Contact) - self:__NewContact(2,Contact) + self:__NewContact(5,Contact) end function intel:OnAfterNewContact(From,Event,To,Contact) NewContact(Contact) end local function LostContact(Contact) - self:__LostContact(2,Contact) + self:__LostContact(5,Contact) end function intel:OnAfterLostContact(From,Event,To,Contact) LostContact(Contact) end local function LostCluster(Cluster,Mission) - self:__LostCluster(2,Cluster,Mission) + self:__LostCluster(5,Cluster,Mission) end function intel:OnAfterLostCluster(From,Event,To,Cluster,Mission) LostCluster(Cluster,Mission) @@ -1560,7 +1576,7 @@ function AWACS:_StartIntel(awacs) intel:__Start(2) - self.intel = intel + self.intel = intel -- Ops.Intelligence#INTEL return self end @@ -1769,7 +1785,7 @@ function AWACS:_CheckTaskQueue() self:I("Open Tasks: " .. opentasks) local taskstack = self.OpenTasks:GetPointerStack() for _id,_entry in pairs(taskstack) do - local data = _entry -- Utils.FiFo#FIFO.IDEntry + local data = _entry -- Utilities.FiFo#FIFO.IDEntry local entry = data.data -- #AWACS.ManagedTask local target = entry.Target -- Ops.Target#TARGET local description = entry.ToDo @@ -1815,7 +1831,7 @@ function AWACS:_CheckTaskQueue() self:I("Assigned Tasks: " .. opentasks) local taskstack = self.AssignedTasks:GetPointerStack() for _id,_entry in pairs(taskstack) do - local data = _entry -- Utils.FiFo#FIFO.IDEntry + local data = _entry -- Utilities.FiFo#FIFO.IDEntry local entry = data.data -- #AWACS.ManagedTask local target = entry.Target -- Ops.Target#TARGET local description = entry.ToDo @@ -1866,6 +1882,70 @@ function AWACS:_LogStatistics() return self end +--- [User] Add another AirWing for CAP Flights under management +-- @param #AWACS self +-- @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") + if AirWing then + -- TODO - Test Install callback + -- TODO - add distance to AO as UniqueID + AirWing:SetUsingOpsAwacs(self) + self.CAPAirwings:Push(AirWing) + end + return self +end + +--- Recruit assets for a given TARGET. +-- @param #AWACS self +-- @param #string MissionType Mission Type. +-- @param #number NassetsMin Min number of required assets. +-- @param #number NassetsMax Max number of required assets. +-- @return #boolean If `true` enough assets could be recruited. +-- @return #table Assets that have been recruited from all legions. +-- @return #table Legions that have recruited assets. +function AWACS:RecruitAssets(MissionType, NassetsMin, NassetsMax) + + -- Cohorts. + local Cohorts={} + local AWFiFo = self.CAPAirwings -- Utilities.FiFo#FIFO + local AWStack = AWFiFo:GetPointerStack() + local AirWingList = {} + + for _ID,_AWID in pairs(AWStack) do + local SubAW = self.CAPAirwings:ReadByPointer(_ID) + if SubAW then + table.insert(AirWingList,SubAW) + end + end + + for _,_legion in pairs(AirWingList) do + local legion=_legion --Ops.Legion#LEGION + + -- Check that runway is operational + local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true + + if legion:IsRunning() and Runway then + + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + end + end + + -- Target position. + local TargetVec2=self.OpsZone:GetVec2() + + -- Recruit assets. + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2) + + return recruited, assets, legions +end + --- [Internal] Announce a new contact -- @param #AWACS self -- @param Ops.Intelligence#INTEL.Contact Contact @@ -1965,10 +2045,35 @@ function AWACS:_CheckAICAPOnStation() -- not enough local AnchorStackNo,free = self:_GetFreeAnchorStack() if free then - local mission = AUFTRAG:NewCAP(self.AnchorZone,20000,350,self.AnchorZone:GetCoordinate(),nil,nil,{}) - -- local mission = AUFTRAG:NewNOTHING() - self.AirWing:AddMission(mission) - self.AICAPMissions:Push(mission,mission.auftragsnummer) + -- 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 end elseif onstation > self.MaxAIonCAP then -- too many, send one home @@ -2104,7 +2209,7 @@ function AWACS:onafterStart(From, Event, To) AwacsAW:AddMission(mission) - -- callback functions + --[[ callback functions local function StartSettings(FlightGroup,Mission) self:_StartSettings(FlightGroup,Mission) end @@ -2112,6 +2217,7 @@ function AWACS:onafterStart(From, Event, To) function AwacsAW:OnAfterFlightOnMission(From,Event,To,FlightGroup,Mission) StartSettings(FlightGroup,Mission) end + --]] self.AwacsMission = mission self.AwacsInZone = false -- not yet arrived or gone again @@ -2353,6 +2459,19 @@ end -- @return #AWACS self function AWACS:onafterStop(From, Event, To) self:T({From, Event, To}) + -- unhandle stuff, exit intel + + self.intel:Stop() + + local AWFiFo = self.CAPAirwings -- Utilities.FiFo#FIFO + local AWStack = AWFiFo:GetPointerStack() + for _ID,_AWID in pairs(AWStack) do + local SubAW = self.CAPAirwings:ReadByPointer(_ID) + if SubAW then + SubAW:RemoveUsingOpsAwacs() + end + end + return self end @@ -2539,8 +2658,10 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) end end - self:__CheckRadioQueue(nextcall+2) - + if self:Is("Running") then + -- exit if stopped + self:__CheckRadioQueue(nextcall+2) + end return self end @@ -2597,6 +2718,23 @@ function AWACS:onafterAwacsShiftChange(From,Event,To) return self end +--- On after "FlightOnMission". +-- @param #AWACS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup on mission. +-- @param Ops.Auftrag#AUFTRAG Mission The requested mission. +-- @return #AWACS self +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) + end + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- END AWACS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2635,17 +2773,36 @@ AwacsAW:AddSquadron(Squad_Two) AwacsAW:NewPayload("Escorts",-1,{AUFTRAG.Type.ESCORT},100) -- CAP -local Squad_Three = SQUADRON:New("CAP",10,"CAP North") +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:SetFuelLowRefuel(true) Squad_Three:SetFuelLowThreshold(0.3) Squad_Three:SetTurnoverTime(10,20) AwacsAW:AddSquadron(Squad_Three) -AwacsAW:NewPayload("CAP",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},100) +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:SetRespawnAfterDestroyed(900) +AwacsAW2:Start() + +-- 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:SetFuelLowRefuel(true) +Squad_ThreeOne:SetFuelLowThreshold(0.3) +Squad_ThreeOne:SetTurnoverTime(10,20) +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.Darkstar,1,22,230,61,15) testawacs:SetSRS("E:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010,nil) +testawacs:AddCAPAirWing(AwacsAW2) testawacs:__Start(5) From 1a798886a234f1d42dbef41de03d5d5898208ac1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 08:33:49 +0200 Subject: [PATCH 16/22] AIRWING - added function to callback AWACS "onafterflightonmission" --- Moose Development/Moose/Ops/AirWing.lua | 27 +++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index a67e7ea64..e940de02d 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1164,6 +1164,26 @@ function AIRWING:GetTankerForFlight(flightgroup) return nil end +--- Add the ability to call back an Ops.Awacs#AWACS object with an FSM call "FlightOnMission(FlightGroup, Mission)". +-- @param #AIRWING self +-- @param Ops.Awacs#AWACS ConnectecdAwacs +-- @return #AIRWING self +function AIRWING:SetUsingOpsAwacs(ConnectecdAwacs) + self:I(self.lid .. "Added AWACS Object: "..ConnectecdAwacs:GetName() or "unknown") + self.UseConnectedOpsAwacs = true + self.ConnectedOpsAwacs = ConnectecdAwacs + return self +end + +--- Remove the ability to call back an Ops.Awacs#AWACS object with an FSM call "FlightOnMission(FlightGroup, Mission)". +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:RemoveUsingOpsAwacs() + self:I(self.lid .. "Reomve AWACS Object: "..self.ConnectedOpsAwacs:GetName() or "unknown") + self.UseConnectedOpsAwacs = false + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1173,11 +1193,14 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.FlightGroup#FLIGHTGROUP ArmyGroup Ops army group on mission. +-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup Ops flight group on mission. -- @param Ops.Auftrag#AUFTRAG Mission The requested mission. function AIRWING:onafterFlightOnMission(From, Event, To, FlightGroup, Mission) -- Debug info. - self:T(self.lid..string.format("Group %s on %s mission %s", FlightGroup:GetName(), Mission:GetType(), Mission:GetName())) + self:T(self.lid..string.format("Group %s on %s mission %s", FlightGroup:GetName(), Mission:GetType(), Mission:GetName())) + if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then + self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission) + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From eabc8b585494c99fabf931d608cc8c56f37dff0f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 12:43:24 +0200 Subject: [PATCH 17/22] UTILS - added BearingToCardinal, added ToStringBRAANATO --- Moose Development/Moose/Utilities/Utils.lua | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 5c182524c..d88c524fe 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2397,3 +2397,46 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) end return datatable end + +--- Heading Degrees (0-360) to Cardinal +-- @param #number hgd The heading +-- @return #string Cardinal, e.g. "NORTH" +function Utils.BearingToCardinal(Heading) + if Heading >= 0 and Heading <= 22 then return "North" + elseif Heading >= 23 and Heading <= 66 then return "North-East" + elseif Heading >= 67 and Heading <= 101 then return "East" + elseif Heading >= 102 and Heading <= 146 then return "South-East" + elseif Heading >= 147 and Heading <= 201 then return "South" + elseif Heading >= 202 and Heading <= 246 then return "South-West" + elseif Heading >= 247 and Heading <= 291 then return "West" + elseif Heading >= 292 and Heading <= 338 then return "North-West" + elseif Heading >= 339 then return "North" + end +end + +--- Create a BRAA NATO call string BRAA between two GROUP objects +-- @param Wrapper.Group#GROUP FromGrp GROUP object +-- @param Wrapper.Group#GROUP ToGrp GROUP object +-- @return #string Formatted BRAA NATO call +function Utils.ToStringBRAANATO(FromGrp,ToGrp ) + local BRAANATO = "Merged." + local GroupNumber = FromGrp:GetSize() + local GroupWords = "Singleton" + if GroupNumber == 2 then GroupWords = "Two-Ship" + elseif GroupNumber >= 3 then GroupWords = "Heavy" + end + local grpLeadUnit = ToGrp:GetUnit(1) + local tgtCoord = grpLeadUnit:GetCoordinate() + local currentCoord = FromGrp:GetCoordinate() + local hdg = UTILS.Round(ToGrp:GetHeading()/100,1)*100 + local bearing = UTILS.Round(currentCoord:HeadingTo(tgtCoord),0) + local rangeMetres = tgtCoord:Get2DDistance(currentCoord) + local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) + local aspect = tgtCoord:ToStringAspect(currentCoord) + 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) + end + return BRAANATO +end \ No newline at end of file From 5a63c51316a00b883f40487c28abe068c7920bb6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 12:45:33 +0200 Subject: [PATCH 18/22] Utils - docu spelling --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index d88c524fe..9a374657f 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2399,7 +2399,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) end --- Heading Degrees (0-360) to Cardinal --- @param #number hgd The heading +-- @param #number Heading The heading -- @return #string Cardinal, e.g. "NORTH" function Utils.BearingToCardinal(Heading) if Heading >= 0 and Heading <= 22 then return "North" From 77dd40fb4ae2b04f7b1fb32107f62eedde13a708 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 14:00:34 +0200 Subject: [PATCH 19/22] SET - clarified return value of SET_GROUP:GetAliveSET() (table, not SET) UTILS - BRAANATO ZONE - added examples to ZONE_RADIUS:Scan() --- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Core/Zone.lua | 8 ++++---- Moose Development/Moose/Utilities/Utils.lua | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 0127bd185..e5c77f044 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1029,7 +1029,7 @@ do -- SET_GROUP --- Gets the Set. -- @param #SET_GROUP self - -- @return #SET_GROUP self + -- @return #table Table of objects function SET_GROUP:GetAliveSet() self:F2() diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 6b6bd46e1..e7359808f 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -810,7 +810,7 @@ end --- Scan the zone for the presence of units of the given ObjectCategories. --- Note that after a zone has been scanned, the zone can be evaluated by: +-- Note that **only after** a zone has been scanned, the zone can be evaluated by: -- -- * @{ZONE_RADIUS.IsAllInZoneOfCoalition}(): Scan the presence of units in the zone of a coalition. -- * @{ZONE_RADIUS.IsAllInZoneOfOtherCoalition}(): Scan the presence of units in the zone of an other coalition. @@ -819,10 +819,10 @@ end -- * @{ZONE_RADIUS.IsNoneInZone}(): Scan if the zone is empty. -- @{#ZONE_RADIUS. -- @param #ZONE_RADIUS self --- @param ObjectCategories An array of categories of the objects to find in the zone. --- @param UnitCategories An array of unit categories of the objects to find in the zone. +-- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}` +-- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}` -- @usage --- self.Zone:Scan() +-- self.Zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) -- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 9a374657f..3fcf20930 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2418,7 +2418,7 @@ end -- @param Wrapper.Group#GROUP FromGrp GROUP object -- @param Wrapper.Group#GROUP ToGrp GROUP object -- @return #string Formatted BRAA NATO call -function Utils.ToStringBRAANATO(FromGrp,ToGrp ) +function Utils.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO = "Merged." local GroupNumber = FromGrp:GetSize() local GroupWords = "Singleton" From 610e33e4a46f614ee0adb3e3e0e7a77e419cc99f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 18:54:51 +0200 Subject: [PATCH 20/22] COORDINATE - added ToStringBRAANATO (works hopfully ;)) --- Moose Development/Moose/Core/Point.lua | 44 +++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index ae40ec4ca..a3f59152f 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -194,7 +194,12 @@ do -- COORDINATE -- ## 9) Coordinate text generation -- -- * @{#COORDINATE.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance. - -- * @{#COORDINATE.ToStringLL}(): Generates a Latutude & Longutude text. + -- * @{#COORDINATE.ToStringBRA}(): Generates a Bearing, Range & Altitude text. + -- * @{#COORDINATE.ToStringBRAANATO}(): Generates a Generates a Bearing, Range, Aspect & Altitude text in NATOPS. + -- * @{#COORDINATE.ToStringLL}(): Generates a Latutide & Longitude text. + -- * @{#COORDINATE.ToStringLLDMS}(): Generates a Lat, Lon, Degree, Minute, Second text. + -- * @{#COORDINATE.ToStringLLDDM}(): Generates a Lat, Lon, Degree, decimal Minute text. + -- * @{#COORDINATE.ToStringMGRS}(): Generates a MGRS grid coordinate text. -- -- ## 10) Drawings on F10 map -- @@ -2756,7 +2761,44 @@ do -- COORDINATE local Altitude = self:GetAltitudeText() return "BRA, " .. self:GetBRAText( AngleRadians, Distance, Settings, Language ) end + + --- Create a BRAA NATO call string to this COORDINATE from the FromCOORDINATE. + -- @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) + -- @return #string The BRAA text. + function COORDINATE:ToStringBRAANATO(FromCoordinate,Spades) + + -- Thanks to @Pikey + local BRAANATO = "Merged." + local currentCoord = FromCoordinate + local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + + local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 ) + + local rangeMetres = self:Get2DDistance(currentCoord) + local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) + + local aspect = self:ToStringAspect(currentCoord) + + local alt = UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)--*1000 + + 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) + if Spades then + BRAANATO = BRAANATO..", Spades." + else + BRAANATO = BRAANATO.."." + end + end + + return BRAANATO + end + --- Return a BULLS string out of the BULLS of the coalition to the COORDINATE. -- @param #COORDINATE self -- @param DCS#coalition.side Coalition The coalition. From 9169bfc608990cded6254d1d5b33fdcebf0b656c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 19:04:37 +0200 Subject: [PATCH 21/22] COORDINATE - Added Precision to GetDistanceText as an option --- Moose Development/Moose/Core/Point.lua | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index a3f59152f..2e96efb67 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1117,25 +1117,28 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #number Distance The distance in meters. -- @param Core.Settings#SETTINGS Settings + -- @param #string Language (optional) "EN" or "RU" + -- @param #number Precision (optional) round to this many decimal places -- @return #string The distance text expressed in the units of measurement. - function COORDINATE:GetDistanceText( Distance, Settings, Language ) + function COORDINATE:GetDistanceText( Distance, Settings, Language, Precision ) local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local Language = Language or "EN" - + local Precision = Precision or 0 + local DistanceText if Settings:IsMetric() then if Language == "EN" then - DistanceText = " for " .. UTILS.Round( Distance / 1000, 2 ) .. " km" + DistanceText = " for " .. UTILS.Round( Distance / 1000, Precision ) .. " km" elseif Language == "RU" then - DistanceText = " за " .. UTILS.Round( Distance / 1000, 2 ) .. " километров" + DistanceText = " за " .. UTILS.Round( Distance / 1000, Precision ) .. " километров" end else if Language == "EN" then - DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " miles" + DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), Precision ) .. " miles" elseif Language == "RU" then - DistanceText = " за " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " миль" + DistanceText = " за " .. UTILS.Round( UTILS.MetersToNM( Distance ), Precision ) .. " миль" end end @@ -1213,7 +1216,7 @@ do -- COORDINATE local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) - local DistanceText = self:GetDistanceText( Distance, Settings, Language ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 ) local BRText = BearingText .. DistanceText @@ -1231,7 +1234,7 @@ do -- COORDINATE local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) - local DistanceText = self:GetDistanceText( Distance, Settings, Language ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 ) local AltitudeText = self:GetAltitudeText( Settings, Language ) local BRAText = BearingText .. DistanceText .. AltitudeText -- When the POINT is a VEC2, there will be no altitude shown. From e6cc8757ac9021ab8bb165f27f93cc5256d10342 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 20 Apr 2022 19:14:19 +0200 Subject: [PATCH 22/22] Utils typo --- Moose Development/Moose/Utilities/Utils.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 3fcf20930..56d6e63c8 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2401,7 +2401,7 @@ end --- Heading Degrees (0-360) to Cardinal -- @param #number Heading The heading -- @return #string Cardinal, e.g. "NORTH" -function Utils.BearingToCardinal(Heading) +function UTILS.BearingToCardinal(Heading) if Heading >= 0 and Heading <= 22 then return "North" elseif Heading >= 23 and Heading <= 66 then return "North-East" elseif Heading >= 67 and Heading <= 101 then return "East" @@ -2418,7 +2418,7 @@ end -- @param Wrapper.Group#GROUP FromGrp GROUP object -- @param Wrapper.Group#GROUP ToGrp GROUP object -- @return #string Formatted BRAA NATO call -function Utils.ToStringBRAANATO(FromGrp,ToGrp) +function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO = "Merged." local GroupNumber = FromGrp:GetSize() local GroupWords = "Singleton" @@ -2434,7 +2434,7 @@ function Utils.ToStringBRAANATO(FromGrp,ToGrp) local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) local aspect = tgtCoord:ToStringAspect(currentCoord) local alt = UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0)--*1000 - local track = Utils.BearingToCardinal(hdg) + 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) end