Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2023-07-22 10:16:40 +02:00
commit a7857670d3
21 changed files with 1621 additions and 417 deletions

View File

@ -0,0 +1,785 @@
--- **Core** - Client Menu Management.
--
-- **Main Features:**
--
-- * For complex, non-static menu structures
-- * Lightweigt implementation as alternative to MENU
-- * Separation of menu tree creation from menu on the clients's side
-- * Works with a SET_CLIENT set of clients
-- * Allow manipulation of the shadow tree in various ways
-- * Push to all or only one client
-- * Change entries' menu text
-- * Option to make an entry usable once only across all clients
-- * Auto appends GROUP and CLIENT objects to menu calls
--
-- ===
--
-- ### Author: **applevangelist**
--
-- ===
--
-- @module Core.ClientMenu
-- @image Core_Menu.JPG
-- last change: July 2023
-- TODO
----------------------------------------------------------------------------------------------------------------
--
-- CLIENTMENU
--
----------------------------------------------------------------------------------------------------------------
---
-- @type CLIENTMENU
-- @field #string ClassName Class Name
-- @field #string lid Lid for log entries
-- @field #string version Version string
-- @field #string name Name
-- @field #table path
-- @field #table parentpath
-- @field #CLIENTMENU Parent
-- @field Wrapper.Client#CLIENT client
-- @field #number GroupID Group ID
-- @field #number ID Entry ID
-- @field Wrapper.Group#GROUP group
-- @field #string UUID Unique ID based on path+name
-- @field #string Function
-- @field #table Functionargs
-- @field #table Children
-- @field #boolean Once
-- @field #boolean Generic
-- @field #boolean debug
-- @field #CLIENTMENUMANAGER Controller
-- @extends Core.Base#BASE
---
-- @field #CLIENTMENU
CLIENTMENU = {
ClassName = "CLIENTMENUE",
lid = "",
version = "0.1.0",
name = nil,
path = nil,
group = nil,
client = nil,
GroupID = nil,
Children = {},
Once = false,
Generic = false,
debug = false,
Controller = nil,
}
---
-- @field #CLIENTMENU_ID
CLIENTMENU_ID = 0
--- Create an new CLIENTMENU object.
-- @param #CLIENTMENU self
-- @param Wrapper.Client#CLIENT Client The client for whom this entry is.
-- @param #string Text Text of the F10 menu entry.
-- @param #CLIENTMENU Parent The parent menu entry.
-- @param #string Function (optional) Function to call when the entry is used.
-- @param ... (optional) Arguments for the Function, comma separated
-- @return #CLIENTMENU self
function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New()) -- #CLIENTMENU
CLIENTMENU_ID = CLIENTMENU_ID + 1
self.ID = CLIENTMENU_ID
if Client then
self.group = Client:GetGroup()
self.client = Client
self.GroupID = self.group:GetID()
else
self.Generic = true
end
self.name = Text or "unknown entry"
if Parent then
if Parent:IsInstanceOf("MENU_BASE") then
self.parentpath = Parent.MenuPath
else
self.parentpath = Parent:GetPath()
Parent:AddChild(self)
end
end
self.Parent = Parent
self.Function = Function
self.Functionargs = arg or {}
table.insert(self.Functionargs,self.group)
table.insert(self.Functionargs,self.client)
if self.Functionargs and self.debug then
self:T({"Functionargs",self.Functionargs})
end
if not self.Generic then
if Function ~= nil then
local ErrorHandler = function( errmsg )
env.info( "MOOSE Error in CLIENTMENU COMMAND function: " .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
end
return errmsg
end
self.CallHandler = function()
local function MenuFunction()
return self.Function( unpack( self.Functionargs ) )
end
local Status, Result = xpcall( MenuFunction, ErrorHandler)
if self.Once == true then
self:Clear()
end
end
self.path = missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath, self.CallHandler)
else
self.path = missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath)
end
else
if self.parentpath then
self.path = UTILS.DeepCopy(self.parentpath)
else
self.path = {}
end
self.path[#self.path+1] = Text
end
self.UUID = table.concat(self.path,";")
self:T({self.UUID})
self.Once = false
-- Log id.
self.lid=string.format("CLIENTMENU %s | %s | ", self.ID, self.name)
self:T(self.lid.."Created")
return self
end
--- Create a UUID
-- @param #CLIENTMENU self
-- @param #CLIENTMENU Parent The parent object if any
-- @param #string Text The menu entry text
-- @return #string UUID
function CLIENTMENU:CreateUUID(Parent,Text)
local path = {}
if Parent and Parent.path then
path = Parent.path
end
path[#path+1] = Text
local UUID = table.concat(path,";")
return UUID
end
--- Set the CLIENTMENUMANAGER for this entry.
-- @param #CLIENTMENU self
-- @param #CLIENTMENUMANAGER Controller The controlling object.
-- @return #CLIENTMENU self
function CLIENTMENU:SetController(Controller)
self.Controller = Controller
return self
end
--- The entry will be deleted after being used used - for menu entries with functions only.
-- @param #CLIENTMENU self
-- @return #CLIENTMENU self
function CLIENTMENU:SetOnce()
self:T(self.lid.."SetOnce")
self.Once = true
return self
end
--- Remove the entry from the F10 menu.
-- @param #CLIENTMENU self
-- @return #CLIENTMENU self
function CLIENTMENU:RemoveF10()
self:T(self.lid.."RemoveF10")
if self.GroupID then
--self:I(self.lid.."Removing "..table.concat(self.path,";"))
missionCommands.removeItemForGroup(self.GroupID , self.path )
end
return self
end
--- Get the menu path table.
-- @param #CLIENTMENU self
-- @return #table Path
function CLIENTMENU:GetPath()
self:T(self.lid.."GetPath")
return self.path
end
--- Get the UUID.
-- @param #CLIENTMENU self
-- @return #string UUID
function CLIENTMENU:GetUUID()
self:T(self.lid.."GetUUID")
return self.UUID
end
--- Link a child entry.
-- @param #CLIENTMENU self
-- @param #CLIENTMENU Child The entry to link as a child.
-- @return #CLIENTMENU self
function CLIENTMENU:AddChild(Child)
self:T(self.lid.."AddChild "..Child.ID)
table.insert(self.Children,Child.ID,Child)
return self
end
--- Remove a child entry.
-- @param #CLIENTMENU self
-- @param #CLIENTMENU Child The entry to remove from the children.
-- @return #CLIENTMENU self
function CLIENTMENU:RemoveChild(Child)
self:T(self.lid.."RemoveChild "..Child.ID)
table.remove(self.Children,Child.ID)
return self
end
--- Remove all subentries (children) from this entry.
-- @param #CLIENTMENU self
-- @return #CLIENTMENU self
function CLIENTMENU:RemoveSubEntries()
self:T(self.lid.."RemoveSubEntries")
self:T({self.Children})
for _id,_entry in pairs(self.Children) do
self:T("Removing ".._id)
if _entry then
_entry:RemoveSubEntries()
_entry:RemoveF10()
if _entry.Parent then
_entry.Parent:RemoveChild(self)
end
--if self.Controller then
--self.Controller:_RemoveByID(_entry.ID)
--end
--_entry = nil
end
end
return self
end
--- Remove this entry and all subentries (children) from this entry.
-- @param #CLIENTMENU self
-- @return #CLIENTMENU self
function CLIENTMENU:Clear()
self:T(self.lid.."Clear")
for _id,_entry in pairs(self.Children) do
if _entry then
_entry:RemoveSubEntries()
_entry = nil
end
end
self:RemoveF10()
if self.Parent then
self.Parent:RemoveChild(self)
end
--if self.Controller then
--self.Controller:_RemoveByID(self.ID)
--end
return self
end
-- TODO
----------------------------------------------------------------------------------------------------------------
--
-- CLIENTMENUMANAGER
--
----------------------------------------------------------------------------------------------------------------
--- Class CLIENTMENUMANAGER
-- @type CLIENTMENUMANAGER
-- @field #string ClassName Class Name
-- @field #string lid Lid for log entries
-- @field #string version Version string
-- @field #string name Name
-- @field Core.Set#SET_CLIENT clientset The set of clients this menu manager is for
-- @field #table flattree
-- @field #table rootentries
-- @field #table menutree
-- @field #number entrycount
-- @field #boolean debug
-- @extends Core.Base#BASE
--- *As a child my family's menu consisted of two choices: take it, or leave it.*
--
-- ===
--
-- ## CLIENTMENU and CLIENTMENUMANAGER
--
-- Manage menu structures for a SET_CLIENT of clients.
--
-- ## Concept
--
-- Separate creation of a menu tree structure from pushing it to each client. Create a shadow "reference" menu structure tree for your client pilot's in a mission.
-- This can then be propagated to all clients. Manipulate the entries in the structure with removing, clearing or changing single entries, create replacement sub-structures
-- for entries etc, push to one or all clients.
--
-- Many functions can either change the tree for one client or for all clients.
--
-- ## Create a base reference tree and send to all clients
--
-- local clientset = SET_CLIENT:New():FilterStart()
--
-- local menumgr = CLIENTMENUMANAGER:New(clientset,"Dayshift")
-- local mymenu = menumgr:NewEntry("Top")
-- local mymenu_lv1a = menumgr:NewEntry("Level 1 a",mymenu)
-- local mymenu_lv1b = menumgr:NewEntry("Level 1 b",mymenu)
-- -- next one is a command menu entry, which can only be used once
-- local mymenu_lv1c = menumgr:NewEntry("Action Level 1 c",mymenu, testfunction, "testtext"):SetOnce()
--
-- local mymenu_lv2a = menumgr:NewEntry("Go here",mymenu_lv1a)
-- local mymenu_lv2b = menumgr:NewEntry("Level 2 ab",mymenu_lv1a)
-- local mymenu_lv2c = menumgr:NewEntry("Level 2 ac",mymenu_lv1a)
--
-- local mymenu_lv2ba = menumgr:NewEntry("Level 2 ba",mymenu_lv1b)
-- local mymenu_lv2bb = menumgr:NewEntry("Level 2 bb",mymenu_lv1b)
-- local mymenu_lv2bc = menumgr:NewEntry("Level 2 bc",mymenu_lv1b)
--
-- local mymenu_lv3a = menumgr:NewEntry("Level 3 aaa",mymenu_lv2a)
-- local mymenu_lv3b = menumgr:NewEntry("Level 3 aab",mymenu_lv2a)
-- local mymenu_lv3c = menumgr:NewEntry("Level 3 aac",mymenu_lv2a)
--
-- menumgr:Propagate()
--
-- ## Remove a single entry's subtree
--
-- menumgr:RemoveSubEntries(mymenu_lv3a)
--
-- ## Remove a single entry and also it's subtree
--
-- menumgr:DeleteEntry(mymenu_lv3a)
--
-- ## Add a single entry
--
-- local baimenu = menumgr:NewEntry("BAI",mymenu_lv1b)
--
-- menumgr:AddEntry(baimenu)
--
-- ## Add an entry with a function
--
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, Argument1, Argument1)
--
-- Now, the class will **automatically append the call with GROUP and CLIENT objects**, as this is can only be done when pushing the entry to the clients. So, the actual function implementation needs to look like this:
--
-- function TestFunction( Argument1, Argument2, Group, Client)
--
-- **Caveat is**, that you need to ensure your arguments are not **nil** or **false**, as LUA will optimize those away. You would end up having Group and Client in wrong places in the function call. Hence,
-- if you need/ want to send **nil** or **false**, send a place holder instead and ensure your function can handle this, e.g.
--
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, "nil", Argument1)
--
-- ## Change the text of a leaf entry in the menu tree
--
-- menumgr:ChangeEntryTextForAll(mymenu_lv1b,"Attack")
--
-- ## Reset a single clients menu tree
--
-- menumgr:ResetMenu(client)
--
-- ## Reset all and clear the reference tree
--
-- menumgr:ResetMenuComplete()
--
-- @field #CLIENTMENUMANAGER
CLIENTMENUMANAGER = {
ClassName = "CLIENTMENUMANAGER",
lid = "",
version = "0.1.0",
name = nil,
clientset = nil,
menutree = {},
flattree = {},
playertree = {},
entrycount = 0,
rootentries = {},
debug = true,
}
--- Create a new ClientManager instance.
-- @param #CLIENTMENUMANAGER self
-- @param Core.Set#SET_CLIENT ClientSet The set of clients to manage.
-- @param #string Alias The name of this manager.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:New(ClientSet, Alias)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, BASE:New()) -- #CLIENTMENUMANAGER
self.clientset = ClientSet
self.name = Alias or "Nightshift"
-- Log id.
self.lid=string.format("CLIENTMENUMANAGER %s | %s | ", self.version, self.name)
if self.debug then
self:T(self.lid.."Created")
end
return self
end
--- Create a new entry in the generic structure.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text of the F10 menu entry.
-- @param #CLIENTMENU Parent The parent menu entry.
-- @param #string Function (optional) Function to call when the entry is used.
-- @param ... (optional) Arguments for the Function, comma separated.
-- @return #CLIENTMENU Entry
function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...)
self:T(self.lid.."NewEntry "..Text or "None")
self.entrycount = self.entrycount + 1
local entry = CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg))
if not Parent then
self.rootentries[self.entrycount] = entry
end
local depth = #entry.path
if not self.menutree[depth] then self.menutree[depth] = {} end
table.insert(self.menutree[depth],entry.UUID)
self.flattree[entry.UUID] = entry
return entry
end
--- Check matching entry in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #string UUID UUID of the menu entry.
-- @return #boolean Exists
function CLIENTMENUMANAGER:EntryUUIDExists(UUID)
local exists = self.flattree[UUID] and true or false
return exists
end
--- Find matching entry in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #string UUID UUID of the menu entry.
-- @return #CLIENTMENU Entry The #CLIENTMENU object found or nil.
function CLIENTMENUMANAGER:FindEntryByUUID(UUID)
self:T(self.lid.."FindEntryByUUID "..UUID or "None")
local entry = nil
for _gid,_entry in pairs(self.flattree) do
local Entry = _entry -- #CLIENTMENU
if Entry and Entry.UUID == UUID then
entry = Entry
end
end
return entry
end
--- Find matching entries by text in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text or partial text of the menu entry to find.
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
-- @return #table Table of matching #CLIENTMENU objects
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindUUIDsByText(Text,Parent)
self:T(self.lid.."FindUUIDsByText "..Text or "None")
local matches = {}
local entries = {}
local n = 0
for _uuid,_entry in pairs(self.flattree) do
local Entry = _entry -- #CLIENTMENU
if Parent then
if Entry and string.find(Entry.name,Text) and string.find(Entry.UUID,Parent.UUID) then
table.insert(matches,_uuid)
table.insert(entries,Entry )
n=n+1
end
else
if Entry and string.find(Entry.name,Text) then
table.insert(matches,_uuid)
table.insert(entries,Entry )
n=n+1
end
end
end
return matches, entries, n
end
--- Find matching entries in the generic structure by the menu text.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text or partial text of the F10 menu entry.
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
-- @return #table Table of matching #CLIENTMENU objects.
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindEntriesByText(Text,Parent)
self:T(self.lid.."FindEntriesByText "..Text or "None")
local matches, objects, number = self:FindUUIDsByText(Text, Parent)
return objects, number
end
--- Find matching entries under a parent in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Parent Find entries under this parent entry.
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
-- @return #table Table of matching #CLIENTMENU objects
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindUUIDsByParent(Parent)
self:T(self.lid.."FindUUIDsByParent")
local matches = {}
local entries = {}
local n = 0
for _uuid,_entry in pairs(self.flattree) do
local Entry = _entry -- #CLIENTMENU
if Parent then
if Entry and string.find(Entry.UUID,Parent.UUID) then
table.insert(matches,_uuid)
table.insert(entries,Entry )
n=n+1
end
end
end
return matches, entries, n
end
--- Find matching entries in the generic structure under a parent.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Parent Find entries under this parent entry.
-- @return #table Table of matching #CLIENTMENU objects.
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindEntriesByParent(Parent)
self:T(self.lid.."FindEntriesByParent")
local matches, objects, number = self:FindUUIDsByParent(Parent)
return objects, number
end
--- Alter the text of a leaf entry in the generic structure and push to one specific client's F10 menu.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The menu entry.
-- @param #string Text New Text of the F10 menu entry.
-- @param Wrapper.Client#CLIENT Client (optional) The client for whom to alter the entry, if nil done for all clients.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ChangeEntryText(Entry, Text, Client)
self:T(self.lid.."ChangeEntryText "..Text or "None")
local newentry = CLIENTMENU:NewEntry(nil,Text,Entry.Parent,Entry.Function,unpack(Entry.Functionargs))
self:DeleteF10Entry(Entry,Client)
self:DeleteGenericEntry(Entry)
if not Entry.Parent then
self.rootentries[self.entrycount] = newentry
end
local depth = #newentry.path
if not self.menutree[depth] then self.menutree[depth] = {} end
table.insert(self.menutree[depth],newentry.UUID)
self.flattree[newentry.UUID] = newentry
self:AddEntry(newentry,Client)
return self
end
--- Push the complete menu structure to each of the clients in the set - refresh the menu tree of the clients.
-- @param #CLIENTMENUMANAGER self
-- @param Wrapper.Client#CLIENT Client (optional) If given, propagate only for this client.
-- @return #CLIENTMENU Entry
function CLIENTMENUMANAGER:Propagate(Client)
self:T(self.lid.."Propagate")
self:T(Client)
local Set = self.clientset.Set
if Client then
Set = {Client}
end
self:ResetMenu(Client)
for _,_client in pairs(Set) do
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
local playername = client:GetPlayerName()
if not self.playertree[playername] then
self.playertree[playername] = {}
end
for level,branch in pairs (self.menutree) do
self:T("Building branch:" .. level)
for _,leaf in pairs(branch) do
self:T("Building leaf:" .. leaf)
local entry = self:FindEntryByUUID(leaf)
if entry then
self:T("Found generic entry:" .. entry.UUID)
local parent = nil
if entry.Parent and entry.Parent.UUID then
parent = self.playertree[playername][entry.Parent.UUID] or self:FindEntryByUUID(entry.Parent.UUID)
end
self.playertree[playername][entry.UUID] = CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs))
self.playertree[playername][entry.UUID].Once = entry.Once
else
self:T("NO generic entry for:" .. leaf)
end
end
end
end
end
return self
end
--- Push a single previously created entry into the menu structure of all clients.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to add.
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:AddEntry(Entry,Client)
self:T(self.lid.."AddEntry")
local Set = self.clientset.Set
if Client then
Set = {Client}
end
for _,_client in pairs(Set) do
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
local playername = client:GetPlayerName()
if Entry then
self:T("Adding generic entry:" .. Entry.UUID)
local parent = nil
if not self.playertree[playername] then
self.playertree[playername] = {}
end
if Entry.Parent and Entry.Parent.UUID then
parent = self.playertree[playername][Entry.Parent.UUID] or self:FindEntryByUUID(Entry.Parent.UUID)
end
self.playertree[playername][Entry.UUID] = CLIENTMENU:NewEntry(client,Entry.name,parent,Entry.Function,unpack(Entry.Functionargs))
self.playertree[playername][Entry.UUID].Once = Entry.Once
else
self:T("NO generic entry given")
end
end
end
return self
end
--- Blank out the menu - remove **all root entries** and all entries below from the client's F10 menus, leaving the generic structure untouched.
-- @param #CLIENTMENUMANAGER self
-- @param Wrapper.Client#CLIENT Client (optional) If given, remove only for this client.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ResetMenu(Client)
self:T(self.lid.."ResetMenu")
for _,_entry in pairs(self.rootentries) do
--local RootEntry = self.structure.generic[_entry]
if _entry then
self:DeleteF10Entry(_entry,Client)
end
end
return self
end
--- Blank out the menu - remove **all root entries** and all entries below from all clients' F10 menus, and **delete** the generic structure.
-- @param #CLIENTMENUMANAGER self
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ResetMenuComplete()
self:T(self.lid.."ResetMenuComplete")
for _,_entry in pairs(self.rootentries) do
--local RootEntry = self.structure.generic[_entry]
if _entry then
self:DeleteF10Entry(_entry)
end
end
self.playertree = nil
self.playertree = {}
self.rootentries = nil
self.rootentries = {}
self.menutree = nil
self.menutree = {}
return self
end
--- Remove the entry and all entries below the given entry from the client's F10 menus.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to remove
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client)
self:T(self.lid.."DeleteF10Entry")
local Set = self.clientset.Set
if Client then
Set = {Client}
end
for _,_client in pairs(Set) do
if _client and _client:IsAlive() then
local playername = _client:GetPlayerName()
if self.playertree[playername] then
local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
if centry then
--self:I("Match for "..Entry.UUID)
centry:Clear()
end
end
end
end
return self
end
--- Remove the entry and all entries below the given entry from the generic tree.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to remove
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:DeleteGenericEntry(Entry)
self:T(self.lid.."DeleteGenericEntry")
if Entry.Children and #Entry.Children > 0 then
self:RemoveGenericSubEntries(Entry)
end
local depth = #Entry.path
local uuid = Entry.UUID
local tbl = UTILS.DeepCopy(self.menutree)
if tbl[depth] then
for i=depth,#tbl do
--self:I("Level = "..i)
for _id,_uuid in pairs(tbl[i]) do
self:T(_uuid)
if string.find(_uuid,uuid) or _uuid == uuid then
--self:I("Match for ".._uuid)
self.menutree[i][_id] = nil
self.flattree[_uuid] = nil
end
end
end
end
return self
end
--- Remove all entries below the given entry from the generic tree.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:RemoveGenericSubEntries(Entry)
self:T(self.lid.."RemoveGenericSubEntries")
local depth = #Entry.path + 1
local uuid = Entry.UUID
local tbl = UTILS.DeepCopy(self.menutree)
if tbl[depth] then
for i=depth,#tbl do
self:T("Level = "..i)
for _id,_uuid in pairs(tbl[i]) do
self:T(_uuid)
if string.find(_uuid,uuid) then
self:T("Match for ".._uuid)
self.menutree[i][_id] = nil
self.flattree[_uuid] = nil
end
end
end
end
return self
end
--- Remove all entries below the given entry from the client's F10 menus.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:RemoveF10SubEntries(Entry,Client)
self:T(self.lid.."RemoveSubEntries")
local Set = self.clientset.Set
if Client then
Set = {Client}
end
for _,_client in pairs(Set) do
if _client and _client:IsAlive() then
local playername = _client:GetPlayerName()
if self.playertree[playername] then
local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
centry:RemoveSubEntries()
end
end
end
return self
end
----------------------------------------------------------------------------------------------------------------
--
-- End ClientMenu
--
----------------------------------------------------------------------------------------------------------------

View File

@ -2945,7 +2945,12 @@ do -- COORDINATE
alttext = "very low" alttext = "very low"
end end
local track = UTILS.BearingToCardinal(bearing) or "North" -- corrected Track to be direction of travel of bogey (self in this case)
local track = "Maneuver"
if self.Heading then
track = UTILS.BearingToCardinal(self.Heading) or "North"
end
if rangeNM > 3 then if rangeNM > 3 then
if SSML then -- google says "oh" instead of zero, be aware if SSML then -- google says "oh" instead of zero, be aware

View File

@ -163,6 +163,16 @@
-- --
-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a battalion in an array. -- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a battalion in an array.
-- --
-- ### Group initial position - if wanted different from template position, for use with e.g. @{#SPAWN.SpawnScheduled}().
--
-- * @{#SPAWN.InitPositionCoordinate}(): Set initial position of group via a COORDINATE.
-- * @{#SPAWN.InitPositionVec2}(): Set initial position of group via a VEC2.
--
-- ### Set the positions of a group's units to absolute positions, or relative positions to unit No. 1
--
-- * @{#SPAWN.InitSetUnitRelativePositions}(): Spawn the UNITs of this group with individual relative positions to unit #1 and individual headings.
-- * @{#SPAWN.InitSetUnitAbsolutePositions}(): Spawn the UNITs of this group with individual absolute positions and individual headings.
--
-- ### Position randomization -- ### Position randomization
-- --
-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. -- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
@ -268,7 +278,7 @@ SPAWN = {
-- @type SPAWN.Takeoff -- @type SPAWN.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff
--- @field #SPAWN.Takeoff Takeoff -- @field #SPAWN.Takeoff Takeoff
SPAWN.Takeoff = { SPAWN.Takeoff = {
Air = 1, Air = 1,
Runway = 2, Runway = 2,
@ -276,7 +286,7 @@ SPAWN.Takeoff = {
Cold = 4, Cold = 4,
} }
--- @type SPAWN.SpawnZoneTable -- @type SPAWN.SpawnZoneTable
-- @list <Core.Zone#ZONE_BASE> SpawnZone -- @list <Core.Zone#ZONE_BASE> SpawnZone
--- Creates the main object to spawn a @{Wrapper.Group} defined in the DCS ME. --- Creates the main object to spawn a @{Wrapper.Group} defined in the DCS ME.
@ -1047,7 +1057,7 @@ end
--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. --- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
-- @param #SPAWN self -- @param #SPAWN self
-- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects. -- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects.
-- @return #SPAWN -- @return #SPAWN self
-- @usage -- @usage
-- --
-- -- Create a zone table of the 2 zones. -- -- Create a zone table of the 2 zones.
@ -1077,6 +1087,31 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable )
return self return self
end end
--- This method sets a spawn position for the group that is different from the location of the template.
-- @param #SPAWN self
-- @param Core.Point#COORDINATE Coordinate The position to spawn from
-- @return #SPAWN self
function SPAWN:InitPositionCoordinate(Coordinate)
self:T( { self.SpawnTemplatePrefix, Coordinate:GetVec2()} )
self:InitPositionVec2(Coordinate:GetVec2())
return self
end
--- This method sets a spawn position for the group that is different from the location of the template.
-- @param #SPAWN self
-- @param DCS#Vec2 Vec2 The position to spawn from
-- @return #SPAWN self
function SPAWN:InitPositionVec2(Vec2)
self:T( { self.SpawnTemplatePrefix, Vec2} )
self.SpawnInitPosition = Vec2
self.SpawnFromNewPosition = true
self:I("MaxGroups:"..self.SpawnMaxGroups)
for SpawnGroupID = 1, self.SpawnMaxGroups do
self:_SetInitialPosition( SpawnGroupID )
end
return self
end
--- For planes and helicopters, when these groups go home and land on their home airbases and FARPs, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- For planes and helicopters, when these groups go home and land on their home airbases and FARPs, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment.
-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. -- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed.
-- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... -- This will enable a spawned group to be re-spawned after it lands, until it is destroyed...
@ -1377,6 +1412,11 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
if self:_GetSpawnIndex( SpawnIndex ) then if self:_GetSpawnIndex( SpawnIndex ) then
if self.SpawnFromNewPosition then
self:_SetInitialPosition( SpawnIndex )
end
if self.SpawnGroups[self.SpawnIndex].Visible then if self.SpawnGroups[self.SpawnIndex].Visible then
self.SpawnGroups[self.SpawnIndex].Group:Activate() self.SpawnGroups[self.SpawnIndex].Group:Activate()
else else
@ -1614,7 +1654,7 @@ end
-- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. -- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups.
-- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. -- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn.
-- The variation is a number between 0 and 1, representing the % of variation to be applied on the time interval. -- The variation is a number between 0 and 1, representing the % of variation to be applied on the time interval.
-- @param #boolen WithDelay Do not spawn the **first** group immediately, but delay the spawn as per the calculation below. -- @param #boolean WithDelay Do not spawn the **first** group immediately, but delay the spawn as per the calculation below.
-- Effectively the same as @{InitDelayOn}(). -- Effectively the same as @{InitDelayOn}().
-- @return #SPAWN self -- @return #SPAWN self
-- @usage -- @usage
@ -3128,6 +3168,10 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix )
local SpawnTemplate = nil local SpawnTemplate = nil
if _DATABASE.Templates.Groups[SpawnTemplatePrefix] == nil then
error( 'No Template exists for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
end
local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template
self:F( { Template = Template } ) self:F( { Template = Template } )
@ -3296,6 +3340,57 @@ function SPAWN:_RandomizeTemplate( SpawnIndex )
return self return self
end end
--- Private method that sets the DCS#Vec2 where the Group will be spawned.
-- @param #SPAWN self
-- @param #number SpawnIndex
-- @return #SPAWN self
function SPAWN:_SetInitialPosition( SpawnIndex )
self:T( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } )
if self.SpawnFromNewPosition then
self:T( "Preparing Spawn at Vec2 ", self.SpawnInitPosition )
local SpawnVec2 = self.SpawnInitPosition
self:T( { SpawnVec2 = SpawnVec2 } )
local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
SpawnTemplate.route = SpawnTemplate.route or {}
SpawnTemplate.route.points = SpawnTemplate.route.points or {}
SpawnTemplate.route.points[1] = SpawnTemplate.route.points[1] or {}
SpawnTemplate.route.points[1].x = SpawnTemplate.route.points[1].x or 0
SpawnTemplate.route.points[1].y = SpawnTemplate.route.points[1].y or 0
self:T( { Route = SpawnTemplate.route } )
for UnitID = 1, #SpawnTemplate.units do
local UnitTemplate = SpawnTemplate.units[UnitID]
self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y )
local SX = UnitTemplate.x
local SY = UnitTemplate.y
local BX = SpawnTemplate.route.points[1].x
local BY = SpawnTemplate.route.points[1].y
local TX = SpawnVec2.x + (SX - BX)
local TY = SpawnVec2.y + (SY - BY)
UnitTemplate.x = TX
UnitTemplate.y = TY
-- TODO: Manage altitude based on landheight...
-- SpawnTemplate.units[UnitID].alt = SpawnVec2:
self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y )
end
SpawnTemplate.route.points[1].x = SpawnVec2.x
SpawnTemplate.route.points[1].y = SpawnVec2.y
SpawnTemplate.x = SpawnVec2.x
SpawnTemplate.y = SpawnVec2.y
end
return self
end
--- Private method that randomizes the @{Core.Zone}s where the Group will be spawned. --- Private method that randomizes the @{Core.Zone}s where the Group will be spawned.
-- @param #SPAWN self -- @param #SPAWN self
-- @param #number SpawnIndex -- @param #number SpawnIndex
@ -3415,7 +3510,7 @@ end
-- TODO Need to delete this... _DATABASE does this now ... -- TODO Need to delete this... _DATABASE does this now ...
--- @param #SPAWN self -- @param #SPAWN self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function SPAWN:_OnBirth( EventData ) function SPAWN:_OnBirth( EventData )
self:F( self.SpawnTemplatePrefix ) self:F( self.SpawnTemplatePrefix )
@ -3435,7 +3530,7 @@ function SPAWN:_OnBirth( EventData )
end end
--- @param #SPAWN self -- @param #SPAWN self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function SPAWN:_OnDeadOrCrash( EventData ) function SPAWN:_OnDeadOrCrash( EventData )
self:F( self.SpawnTemplatePrefix ) self:F( self.SpawnTemplatePrefix )

View File

@ -33,7 +33,8 @@
do do
--- @type SPOT ---
-- @type SPOT
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@ -228,7 +229,8 @@ do
-- @param #number LaserCode Laser code. -- @param #number LaserCode Laser code.
-- @param #number Duration Duration of lasing in seconds. -- @param #number Duration Duration of lasing in seconds.
function SPOT:onafterLaseOn( From, Event, To, Target, LaserCode, Duration ) function SPOT:onafterLaseOn( From, Event, To, Target, LaserCode, Duration )
self:F( { "LaseOn", Target, LaserCode, Duration } ) self:T({From, Event, To})
self:T2( { "LaseOn", Target, LaserCode, Duration } )
local function StopLase( self ) local function StopLase( self )
self:LaseOff() self:LaseOff()
@ -256,6 +258,8 @@ do
self:HandleEvent( EVENTS.Dead ) self:HandleEvent( EVENTS.Dead )
self:__Lasing( -1 ) self:__Lasing( -1 )
return self
end end
@ -268,7 +272,7 @@ do
-- @param #number LaserCode Laser code. -- @param #number LaserCode Laser code.
-- @param #number Duration Duration of lasing in seconds. -- @param #number Duration Duration of lasing in seconds.
function SPOT:onafterLaseOnCoordinate(From, Event, To, Coordinate, LaserCode, Duration) function SPOT:onafterLaseOnCoordinate(From, Event, To, Coordinate, LaserCode, Duration)
self:F( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } ) self:T2( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } )
local function StopLase( self ) local function StopLase( self )
self:LaseOff() self:LaseOff()
@ -290,12 +294,14 @@ do
end end
self:__Lasing(-1) self:__Lasing(-1)
return self
end end
--- @param #SPOT self ---
-- @param #SPOT self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function SPOT:OnEventDead(EventData) function SPOT:OnEventDead(EventData)
self:F( { Dead = EventData.IniDCSUnitName, Target = self.Target } ) self:T2( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
if self.Target then if self.Target then
if EventData.IniDCSUnitName == self.TargetName then if EventData.IniDCSUnitName == self.TargetName then
self:F( {"Target dead ", self.TargetName } ) self:F( {"Target dead ", self.TargetName } )
@ -309,18 +315,24 @@ do
self:LaseOff() self:LaseOff()
end end
end end
return self
end end
--- @param #SPOT self ---
-- @param #SPOT self
-- @param From -- @param From
-- @param Event -- @param Event
-- @param To -- @param To
function SPOT:onafterLasing( From, Event, To ) function SPOT:onafterLasing( From, Event, To )
self:T({From, Event, To})
if self.Lasing then
if self.Target and self.Target:IsAlive() then if self.Target and self.Target:IsAlive() then
self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3() ) self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3() )
self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() ) self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() )
self:__Lasing( -0.2 )
self:__Lasing(0.2)
elseif self.TargetCoord then elseif self.TargetCoord then
-- Wiggle the IR spot a bit. -- Wiggle the IR spot a bit.
@ -330,21 +342,24 @@ do
self.SpotIR:setPoint(irvec3) self.SpotIR:setPoint(irvec3)
self.SpotLaser:setPoint(lsvec3) self.SpotLaser:setPoint(lsvec3)
self:__Lasing(-0.25) self:__Lasing(0.2)
else else
self:F( { "Target is not alive", self.Target:IsAlive() } ) self:F( { "Target is not alive", self.Target:IsAlive() } )
end end
end
return self
end end
--- @param #SPOT self ---
-- @param #SPOT self
-- @param From -- @param From
-- @param Event -- @param Event
-- @param To -- @param To
-- @return #SPOT -- @return #SPOT
function SPOT:onafterLaseOff( From, Event, To ) function SPOT:onafterLaseOff( From, Event, To )
self:T({From, Event, To})
self:F( {"Stopped lasing for ", self.Target and self.Target:GetName() or "coord", SpotIR = self.SportIR, SpotLaser = self.SpotLaser } ) self:T2( {"Stopped lasing for ", self.Target and self.Target:GetName() or "coord", SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
self.Lasing = false self.Lasing = false

View File

@ -22,7 +22,7 @@
-- === -- ===
-- --
-- ### Author: **Applevangelist** -- ### Author: **Applevangelist**
-- Last Update February 2022 -- Last Update July 2023
-- --
-- === -- ===
-- @module Functional.AICSAR -- @module Functional.AICSAR
@ -191,7 +191,7 @@
-- @field #AICSAR -- @field #AICSAR
AICSAR = { AICSAR = {
ClassName = "AICSAR", ClassName = "AICSAR",
version = "0.1.14", version = "0.1.15",
lid = "", lid = "",
coalition = coalition.side.BLUE, coalition = coalition.side.BLUE,
template = "", template = "",
@ -397,6 +397,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued
self:AddTransition("*", "PilotKIA", "*") -- Pilot dead self:AddTransition("*", "PilotKIA", "*") -- Pilot dead
self:AddTransition("*", "HeloDown", "*") -- Helo dead self:AddTransition("*", "HeloDown", "*") -- Helo dead
self:AddTransition("*", "HeloOnDuty", "*") -- Helo spawnd
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler) self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler)
@ -473,6 +474,14 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
--- On after "HeloOnDuty" event.
-- @function [parent=#AICSAR] OnAfterHeloOnDuty
-- @param #AICSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Group#GROUP Helo Helo group object
--- On after "HeloDown" event. --- On after "HeloDown" event.
-- @function [parent=#AICSAR] OnAfterHeloDown -- @function [parent=#AICSAR] OnAfterHeloDown
-- @param #AICSAR self -- @param #AICSAR self
@ -853,6 +862,11 @@ function AICSAR:_GetFlight()
local newhelo = SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000)) local newhelo = SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000))
:InitDelayOff() :InitDelayOff()
:InitUnControlled(true) :InitUnControlled(true)
:OnSpawnGroup(
function(Group)
self:__HeloOnDuty(1,Group)
end
)
:Spawn() :Spawn()
local nhelo=FLIGHTGROUP:New(newhelo) local nhelo=FLIGHTGROUP:New(newhelo)

View File

@ -17,7 +17,7 @@
-- @module Functional.AmmoTruck -- @module Functional.AmmoTruck
-- @image Artillery.JPG -- @image Artillery.JPG
-- --
-- Date: Nov 2022 -- Last update: July 2023
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **AMMOTRUCK** class, extends Core.FSM#FSM --- **AMMOTRUCK** class, extends Core.FSM#FSM
@ -40,6 +40,7 @@
-- @field #number unloadtime Unload time in seconds -- @field #number unloadtime Unload time in seconds
-- @field #number waitingtime Max waiting time in seconds -- @field #number waitingtime Max waiting time in seconds
-- @field #boolean routeonroad Route truck on road if true (default) -- @field #boolean routeonroad Route truck on road if true (default)
-- @field #number reloads Number of reloads a single truck can do before he must return home
-- @extends Core.FSM#FSM -- @extends Core.FSM#FSM
--- *Amateurs talk about tactics, but professionals study logistics.* - General Robert H Barrow, USMC --- *Amateurs talk about tactics, but professionals study logistics.* - General Robert H Barrow, USMC
@ -73,9 +74,10 @@
-- ammotruck.remunidist = 20000 -- 20km - send trucks max this far from home -- ammotruck.remunidist = 20000 -- 20km - send trucks max this far from home
-- ammotruck.unloadtime = 600 -- 10 minutes - min time to unload ammunition -- ammotruck.unloadtime = 600 -- 10 minutes - min time to unload ammunition
-- ammotruck.waitingtime = 1800 -- 30 mintes - wait max this long until remunition is done -- ammotruck.waitingtime = 1800 -- 30 mintes - wait max this long until remunition is done
-- ammotruck.monitor = -60 - 1 minute - AMMOTRUCK checks on things every 1 minute -- ammotruck.monitor = -60 -- 1 minute - AMMOTRUCK checks run every one minute
-- ammotruck.routeonroad = true - Trucks will **try** to drive on roads -- ammotruck.routeonroad = true -- Trucks will **try** to drive on roads
-- ammotruck.usearmygroup = false - if true, will make use of ARMYGROUP in the background (if used in DEV branch) -- ammotruck.usearmygroup = false -- If true, will make use of ARMYGROUP in the background (if used in DEV branch)
-- ammotruck.reloads = 5 -- Maxn re-arms a truck can do before he needs to go home and restock. Set to -1 for unlimited
-- --
-- ## 3 FSM Events to shape mission -- ## 3 FSM Events to shape mission
-- --
@ -113,7 +115,7 @@
AMMOTRUCK = { AMMOTRUCK = {
ClassName = "AMMOTRUCK", ClassName = "AMMOTRUCK",
lid = "", lid = "",
version = "0.0.10", version = "0.0.12",
alias = "", alias = "",
debug = false, debug = false,
trucklist = {}, trucklist = {},
@ -128,7 +130,8 @@ AMMOTRUCK = {
monitor = -60, monitor = -60,
unloadtime = 600, unloadtime = 600,
waitingtime = 1800, waitingtime = 1800,
routeonroad = true routeonroad = true,
reloads = 5,
} }
--- ---
@ -156,6 +159,7 @@ AMMOTRUCK.State = {
--@field #string targetname --@field #string targetname
--@field Wrapper.Group#GROUP targetgroup --@field Wrapper.Group#GROUP targetgroup
--@field Core.Point#COORDINATE targetcoordinate --@field Core.Point#COORDINATE targetcoordinate
--@field #number reloads
--- ---
-- @param #AMMOTRUCK self -- @param #AMMOTRUCK self
@ -369,6 +373,7 @@ function AMMOTRUCK:CheckReturningTrucks(dataset)
truck.statusquo = AMMOTRUCK.State.IDLE truck.statusquo = AMMOTRUCK.State.IDLE
truck.timestamp = timer.getAbsTime() truck.timestamp = timer.getAbsTime()
truck.coordinate = coord truck.coordinate = coord
truck.reloads = self.reloads or 5
self:__TruckHome(1,truck) self:__TruckHome(1,truck)
end end
end end
@ -540,6 +545,7 @@ function AMMOTRUCK:CheckTrucksAlive()
newtruck.statusquo = AMMOTRUCK.State.IDLE newtruck.statusquo = AMMOTRUCK.State.IDLE
newtruck.timestamp = timer.getAbsTime() newtruck.timestamp = timer.getAbsTime()
newtruck.coordinate = truck:GetCoordinate() newtruck.coordinate = truck:GetCoordinate()
newtruck.reloads = self.reloads or 5
self.trucklist[name] = newtruck self.trucklist[name] = newtruck
end end
end end
@ -626,9 +632,11 @@ function AMMOTRUCK:onafterMonitor(From, Event, To)
unloadingtrucks[#unloadingtrucks+1] = data unloadingtrucks[#unloadingtrucks+1] = data
elseif data.statusquo == AMMOTRUCK.State.RETURNING then elseif data.statusquo == AMMOTRUCK.State.RETURNING then
returningtrucks[#returningtrucks+1] = data returningtrucks[#returningtrucks+1] = data
if data.reloads > 0 or data.reloads == -1 then
idletrucks[#idletrucks+1] = data idletrucks[#idletrucks+1] = data
found = true found = true
end end
end
else else
self.truckset[data.name] = nil self.truckset[data.name] = nil
end end
@ -637,7 +645,7 @@ function AMMOTRUCK:onafterMonitor(From, Event, To)
local n=0 local n=0
if found and remunition then if found and remunition then
-- match -- match
local match = false --local match = false
for _,_truckdata in pairs(idletrucks) do for _,_truckdata in pairs(idletrucks) do
local truckdata = _truckdata -- #AMMOTRUCK.data local truckdata = _truckdata -- #AMMOTRUCK.data
local truckcoord = truckdata.group:GetCoordinate() -- Core.Point#COORDINATE local truckcoord = truckdata.group:GetCoordinate() -- Core.Point#COORDINATE
@ -750,6 +758,12 @@ end
end end
local scheduler = SCHEDULER:New(nil,destroyammo,{ammo},self.waitingtime) local scheduler = SCHEDULER:New(nil,destroyammo,{ammo},self.waitingtime)
-- one reload less
if truck.reloads ~= -1 then
truck.reloads = truck.reloads - 1
end
return self
end end
--- ---

View File

@ -2354,6 +2354,7 @@ do -- DETECTION_TYPES
if not DetectedItem then if not DetectedItem then
DetectedItem = self:AddDetectedItem( "TYPE", DetectedTypeName ) DetectedItem = self:AddDetectedItem( "TYPE", DetectedTypeName )
DetectedItem.TypeName = DetectedTypeName DetectedItem.TypeName = DetectedTypeName
DetectedItem.Name = DetectedUnitName -- fix by @Nocke
end end
DetectedItem.Set:AddUnit( DetectedUnit ) DetectedItem.Set:AddUnit( DetectedUnit )

View File

@ -22,7 +22,7 @@
-- @module Functional.Mantis -- @module Functional.Mantis
-- @image Functional.Mantis.jpg -- @image Functional.Mantis.jpg
-- --
-- Last Update: Oct 2022 -- Last Update: July 2023
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE --- **MANTIS** class, extends Core.Base#BASE
@ -104,9 +104,13 @@
-- * Silkworm (though strictly speaking this is a surface to ship missile) -- * Silkworm (though strictly speaking this is a surface to ship missile)
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19 -- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19
-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2 -- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2
--
-- * From SMA: RBS98M, RBS70, RBS90, RBS90M, RBS103A, RBS103B, RBS103AM, RBS103BM, Lvkv9040M -- * From SMA: RBS98M, RBS70, RBS90, RBS90M, RBS103A, RBS103B, RBS103AM, RBS103BM, Lvkv9040M
-- **NOTE** If you are using the Swedish Military Assets (SMA), please note that the **group name** for RBS-SAM types also needs to contain the keyword "SMA" -- **NOTE** If you are using the Swedish Military Assets (SMA), please note that the **group name** for RBS-SAM types also needs to contain the keyword "SMA"
-- --
-- * From CH: 2S38, PantsirS1, PantsirS2, PGL-625, HQ-17A, M903PAC2, M903PAC3, TorM2, TorM2K, TorM2M, NASAMS3-AMRAAMER, NASAMS3-AIM9X2, C-RAM, PGZ-09, S350-9M100, S350-9M96D
-- **NOTE** If you are using the Military Assets by Currenthill (CH), please note that the **group name** for CH-SAM types also needs to contain the keyword "CHM"
--
-- Following the example started above, an SA-6 site group name should start with "Red SAM SA-6" then, or a blue Patriot installation with e.g. "Blue SAM Patriot". -- Following the example started above, an SA-6 site group name should start with "Red SAM SA-6" then, or a blue Patriot installation with e.g. "Blue SAM Patriot".
-- **NOTE** If you are using the High-Digit-Sam Mod, please note that the **group name** for the following SAM types also needs to contain the keyword "HDS": -- **NOTE** If you are using the High-Digit-Sam Mod, please note that the **group name** for the following SAM types also needs to contain the keyword "HDS":
-- --
@ -369,6 +373,7 @@ MANTIS.SamData = {
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"},
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"},
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="Igla" }
} }
--- SAM data HDS --- SAM data HDS
@ -415,6 +420,35 @@ MANTIS.SamDataSMA = {
["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" }, ["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" },
} }
--- SAM data CH
-- @type MANTIS.SamDataCH
-- @field #number Range Max firing range in km
-- @field #number Blindspot no-firing range (green circle)
-- @field #number Height Max firing height in km
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
-- @field #string Radar Radar typename on unit level (used as key)
MANTIS.SamDataCH = {
-- units from CH (Military Assets by Currenthill)
-- https://www.currenthill.com/
-- group name MUST contain CHM to ID launcher type correctly!
["2S38 CH"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" },
["PantsirS1 CH"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" },
["PantsirS2 CH"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" },
["PGL-625 CH"] = { Range=10, Blindspot=0.5, Height=5, Type="Short", Radar="PGL_625" },
["HQ-17A CH"] = { Range=20, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" },
["M903PAC2 CH"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" },
["M903PAC3 CH"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
["TorM2 CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" },
["TorM2K CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" },
["TorM2M CH"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" },
["NASAMS3-AMRAAMER CH"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" },
["NASAMS3-AIM9X2 CH"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" },
["C-RAM CH"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_Centurion_C_RAM" },
["PGZ-09 CH"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" },
["S350-9M100 CH"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" },
["S350-9M96D CH"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" },
}
----------------------------------------------------------------------- -----------------------------------------------------------------------
-- MANTIS System -- MANTIS System
----------------------------------------------------------------------- -----------------------------------------------------------------------
@ -578,7 +612,7 @@ do
-- TODO Version -- TODO Version
-- @field #string version -- @field #string version
self.version="0.8.9" self.version="0.8.11"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions --- --- FSM Functions ---
@ -1299,11 +1333,12 @@ do
-- @param #string grpname Name of the group -- @param #string grpname Name of the group
-- @param #boolean mod HDS mod flag -- @param #boolean mod HDS mod flag
-- @param #boolean sma SMA mod flag -- @param #boolean sma SMA mod flag
-- @param #boolean chm CH mod flag
-- @return #number range Max firing range -- @return #number range Max firing range
-- @return #number height Max firing height -- @return #number height Max firing height
-- @return #string type Long, medium or short range -- @return #string type Long, medium or short range
-- @return #number blind "blind" spot -- @return #number blind "blind" spot
function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma) function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma,chm)
self:T(self.lid.."_GetSAMRangeFromUnits") self:T(self.lid.."_GetSAMRangeFromUnits")
local found = false local found = false
local range = self.checkradius local range = self.checkradius
@ -1318,8 +1353,10 @@ do
SAMData = self.SamDataHDS SAMData = self.SamDataHDS
elseif sma then elseif sma then
SAMData = self.SamDataSMA SAMData = self.SamDataSMA
elseif chm then
SAMData = self.SamDataCH
end end
--self:I("Looking to auto-match for "..grpname) --self:T("Looking to auto-match for "..grpname)
for _,_unit in pairs(units) do for _,_unit in pairs(units) do
local unit = _unit -- Wrapper.Unit#UNIT local unit = _unit -- Wrapper.Unit#UNIT
local type = string.lower(unit:GetTypeName()) local type = string.lower(unit:GetTypeName())
@ -1364,10 +1401,13 @@ do
local found = false local found = false
local HDSmod = false local HDSmod = false
local SMAMod = false local SMAMod = false
local CHMod = false
if string.find(grpname,"HDS",1,true) then if string.find(grpname,"HDS",1,true) then
HDSmod = true HDSmod = true
elseif string.find(grpname,"SMA",1,true) then elseif string.find(grpname,"SMA",1,true) then
SMAMod = true SMAMod = true
elseif string.find(grpname,"CHM",1,true) then
CHMod = true
end end
if self.automode then if self.automode then
for idx,entry in pairs(self.SamData) do for idx,entry in pairs(self.SamData) do
@ -1386,8 +1426,8 @@ do
end end
end end
-- secondary filter if not found -- secondary filter if not found
if (not found and self.automode) or HDSmod or SMAMod then if (not found and self.automode) or HDSmod or SMAMod or CHMod then
range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod) range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
elseif not found then elseif not found then
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end end

View File

@ -1767,9 +1767,9 @@ function SCORING:SecondsToClock( sSeconds )
-- return nil; -- return nil;
return "00:00:00"; return "00:00:00";
else else
nHours = string.format( "%02.f", math.floor( nSeconds / 3600 ) ); local nHours = string.format( "%02.f", math.floor( nSeconds / 3600 ) );
nMins = string.format( "%02.f", math.floor( nSeconds / 60 - (nHours * 60) ) ); local nMins = string.format( "%02.f", math.floor( nSeconds / 60 - (nHours * 60) ) );
nSecs = string.format( "%02.f", math.floor( nSeconds - nHours * 3600 - nMins * 60 ) ); local nSecs = string.format( "%02.f", math.floor( nSeconds - nHours * 3600 - nMins * 60 ) );
return nHours .. ":" .. nMins .. ":" .. nSecs return nHours .. ":" .. nMins .. ":" .. nSecs
end end
end end

View File

@ -12,6 +12,7 @@ __Moose.Include( 'Scripts/Moose/Core/Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' ) __Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' ) __Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
__Moose.Include( 'Scripts/Moose/Core/Condition.lua' ) __Moose.Include( 'Scripts/Moose/Core/Condition.lua' )
__Moose.Include( 'Scripts/Moose/Core/ClientMenu.lua')
__Moose.Include( 'Scripts/Moose/Core/Database.lua' ) __Moose.Include( 'Scripts/Moose/Core/Database.lua' )
__Moose.Include( 'Scripts/Moose/Core/Event.lua' ) __Moose.Include( 'Scripts/Moose/Core/Event.lua' )
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )

View File

@ -1,3 +1,4 @@
---@diagnostic disable: cast-local-type
--- **Ops** - Automatic Terminal Information Service (ATIS). --- **Ops** - Automatic Terminal Information Service (ATIS).
-- --
-- === -- ===
@ -608,15 +609,16 @@ _ATIS = {}
--- ATIS class version. --- ATIS class version.
-- @field #string version -- @field #string version
ATIS.version = "0.9.14" ATIS.version = "0.9.15"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add new Normandy airfields.
-- TODO: Zulu time --> Zulu in output.
-- TODO: Correct fog for elevation. -- TODO: Correct fog for elevation.
-- DONE: Zulu time --> Zulu in output.
-- DONE: Fix for AB not having a runway - Helopost like Naqoura
-- DONE: Add new Normandy airfields.
-- DONE: Use new AIRBASE system to set start/landing runway -- DONE: Use new AIRBASE system to set start/landing runway
-- DONE: SetILS doesn't work -- DONE: SetILS doesn't work
-- DONE: Visibility reported twice over SRS -- DONE: Visibility reported twice over SRS
@ -2140,15 +2142,19 @@ function ATIS:onafterBroadcast( From, Event, To )
end end
alltext = alltext .. ";\n" .. subtitle alltext = alltext .. ";\n" .. subtitle
local _RUNACT
if not self.ATISforFARPs then if not self.ATISforFARPs then
-- Active runway. -- Active runway.
if runwayLanding then
local subtitle=string.format("Active runway %s", runwayLanding) local subtitle=string.format("Active runway %s", runwayLanding)
if rwyLandingLeft==true then if rwyLandingLeft==true then
subtitle=subtitle.." Left" subtitle=subtitle.." Left"
elseif rwyLandingLeft==false then elseif rwyLandingLeft==false then
subtitle=subtitle.." Right" subtitle=subtitle.." Right"
end end
local _RUNACT = subtitle end
_RUNACT = subtitle
if not self.useSRS then if not self.useSRS then
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runwayLanding) self.radioqueue:Number2Transmission(runwayLanding)
@ -2509,8 +2515,11 @@ function ATIS:GetActiveRunway(Takeoff)
else else
runway=self.airbase:GetActiveRunwayLanding() runway=self.airbase:GetActiveRunwayLanding()
end end
if runway then -- some ABs have NO runways, e.g. Syria Naqoura
return runway.name, runway.isLeft return runway.name, runway.isLeft
else
return nil, nil
end
end end
--- Get runway from user supplied magnetic heading. --- Get runway from user supplied magnetic heading.

View File

@ -11585,7 +11585,13 @@ function AIRBOSS:GetHeadingIntoWind( magnetic, coord )
end end
-- Get direction the wind is blowing from. This is where we want to go. -- Get direction the wind is blowing from. This is where we want to go.
local windfrom, vwind = self:GetWind( nil, nil, coord ) + adjustDegreesForWindSpeed(vwind) local windfrom, vwind = self:GetWind( nil, nil, coord )
--self:I("windfrom="..windfrom.." vwind="..vwind)
vwind = vwind + adjustDegreesForWindSpeed(vwind)
--self:I("windfrom="..windfrom.." (c)vwind="..vwind)
-- Actually, we want the runway in the wind. -- Actually, we want the runway in the wind.
local intowind = windfrom - self.carrierparam.rwyangle local intowind = windfrom - self.carrierparam.rwyangle
@ -17348,7 +17354,7 @@ function AIRBOSS:_DisplayCarrierInfo( _unitname )
state = "Deck closed" state = "Deck closed"
end end
if self.turning then if self.turning then
state = state .. " (turning currently)" state = state .. " (currently turning)"
end end
-- Message text. -- Message text.

View File

@ -1,3 +1,4 @@
---@diagnostic disable: undefined-global
--- **Ops** - Auftrag (mission) for Ops. --- **Ops** - Auftrag (mission) for Ops.
-- --
-- ## Main Features: -- ## Main Features:
@ -259,6 +260,10 @@
-- --
-- Not implemented yet. -- Not implemented yet.
-- --
-- ## Ground Escort
--
-- An escort mission can be created with the @{#AUFTRAG.NewGROUNDESCORT}() function.
--
-- ## Intercept -- ## Intercept
-- --
-- An intercept mission can be created with the @{#AUFTRAG.NewINTERCEPT}() function. -- An intercept mission can be created with the @{#AUFTRAG.NewINTERCEPT}() function.
@ -406,6 +411,7 @@ _AUFTRAGSNR=0
-- @field #string FAC Forward AirController mission. -- @field #string FAC Forward AirController mission.
-- @field #string FACA Forward AirController airborne mission. -- @field #string FACA Forward AirController airborne mission.
-- @field #string FERRY Ferry mission. -- @field #string FERRY Ferry mission.
-- @field #string GROUNDESCORT Ground escort mission.
-- @field #string INTERCEPT Intercept mission. -- @field #string INTERCEPT Intercept mission.
-- @field #string ORBIT Orbit mission. -- @field #string ORBIT Orbit mission.
-- @field #string GCICAP Similar to CAP but no auto engage targets. -- @field #string GCICAP Similar to CAP but no auto engage targets.
@ -450,6 +456,7 @@ AUFTRAG.Type={
FAC="FAC", FAC="FAC",
FACA="FAC-A", FACA="FAC-A",
FERRY="Ferry Flight", FERRY="Ferry Flight",
GROUNDESCORT="Ground Escort",
INTERCEPT="Intercept", INTERCEPT="Intercept",
ORBIT="Orbit", ORBIT="Orbit",
GCICAP="Ground Controlled CAP", GCICAP="Ground Controlled CAP",
@ -477,7 +484,7 @@ AUFTRAG.Type={
RELOCATECOHORT="Relocate Cohort", RELOCATECOHORT="Relocate Cohort",
AIRDEFENSE="Air Defence", AIRDEFENSE="Air Defence",
EWR="Early Warning Radar", EWR="Early Warning Radar",
RECOVERYTANKER="Recovery Tanker", --RECOVERYTANKER="Recovery Tanker",
REARMING="Rearming", REARMING="Rearming",
CAPTUREZONE="Capture Zone", CAPTUREZONE="Capture Zone",
NOTHING="Nothing", NOTHING="Nothing",
@ -1740,6 +1747,43 @@ function AUFTRAG:NewBOMBCARPET(Target, Altitude, CarpetLength)
return mission return mission
end end
--- **[AIR/HELO]** Create a GROUNDESCORT (or FOLLOW) mission. Helo will escort a **ground** group and automatically engage certain target types.
-- @param #AUFTRAG self
-- @param Wrapper.Group#GROUP EscortGroup The ground group to escort.
-- @param #number OrbitDistance Orbit to/from the lead unit this many NM. Defaults to 1.5 NM.
-- @param #table TargetTypes Types of targets to engage automatically. Default is {"Ground vehicles"}, i.e. all enemy ground units. Use an empty set {} for a simple "FOLLOW" mission.
-- @return #AUFTRAG self
function AUFTRAG:NewGROUNDESCORT(EscortGroup, OrbitDistance, TargetTypes)
local mission=AUFTRAG:New(AUFTRAG.Type.GROUNDESCORT)
-- If only a string is passed we set a variable and check later if the group exists.
if type(EscortGroup)=="string" then
mission.escortGroupName=EscortGroup
mission:_TargetFromObject()
else
mission:_TargetFromObject(EscortGroup)
end
-- DCS task parameters:
mission.orbitDistance=OrbitDistance and UTILS.NMToMeters(OrbitDistance) or UTILS.NMToMeters(1.5)
--mission.engageMaxDistance=EngageMaxDistance and UTILS.NMToMeters(EngageMaxDistance) or UTILS.NMToMeters(5)
mission.engageTargetTypes=TargetTypes or {"Ground vehicles"}
-- Mission options:
mission.missionTask=ENUMS.MissionTask.GROUNDESCORT
mission.missionFraction=0.1
mission.missionAltitude=100
mission.optionROE=ENUMS.ROE.OpenFire -- TODO: what's the best ROE here? Make dependent on ESCORT or FOLLOW!
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.HELICOPTER}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
--- **[AIR]** Create an ESCORT (or FOLLOW) mission. Flight will escort another group and automatically engage certain target types. --- **[AIR]** Create an ESCORT (or FOLLOW) mission. Flight will escort another group and automatically engage certain target types.
-- @param #AUFTRAG self -- @param #AUFTRAG self
@ -5843,10 +5887,20 @@ function AUFTRAG:GetDCSMissionTask()
-- ESCORT Mission -- -- ESCORT Mission --
-------------------- --------------------
local DCStask=CONTROLLABLE.TaskEscort(nil, self.engageTarget:GetObject(), self.escortVec3, LastWaypointIndex, self.engageMaxDistance, self.engageTargetTypes) local DCStask=CONTROLLABLE.TaskEscort(nil, self.engageTarget:GetObject(), self.escortVec3, nil, self.engageMaxDistance, self.engageTargetTypes)
table.insert(DCStasks, DCStask) table.insert(DCStasks, DCStask)
elseif self.type==AUFTRAG.Type.GROUNDESCORT then
--------------------
-- GROUNDESCORT Mission --
--------------------
local DCSTask=CONTROLLABLE.TaskGroundEscort(nil,self.engageTarget:GetObject(),nil,self.orbitDistance,self.engageTargetTypes)
table.insert(DCStasks, DCSTask)
elseif self.type==AUFTRAG.Type.FACA then elseif self.type==AUFTRAG.Type.FACA then
------------------ ------------------
@ -6528,6 +6582,8 @@ function AUFTRAG:GetMissionTaskforMissionType(MissionType)
mtask=ENUMS.MissionTask.AFAC mtask=ENUMS.MissionTask.AFAC
elseif MissionType==AUFTRAG.Type.FERRY then elseif MissionType==AUFTRAG.Type.FERRY then
mtask=ENUMS.MissionTask.NOTHING mtask=ENUMS.MissionTask.NOTHING
elseif MissionType==AUFTRAG.Type.GROUNDESCORT then
mtask=ENUMS.MissionTask.GROUNDESCORT
elseif MissionType==AUFTRAG.Type.INTERCEPT then elseif MissionType==AUFTRAG.Type.INTERCEPT then
mtask=ENUMS.MissionTask.INTERCEPT mtask=ENUMS.MissionTask.INTERCEPT
elseif MissionType==AUFTRAG.Type.RECON then elseif MissionType==AUFTRAG.Type.RECON then

View File

@ -499,7 +499,7 @@ do
-- @field #AWACS -- @field #AWACS
AWACS = { AWACS = {
ClassName = "AWACS", -- #string ClassName = "AWACS", -- #string
version = "0.2.54", -- #string version = "0.2.55", -- #string
lid = "", -- #string lid = "", -- #string
coalition = coalition.side.BLUE, -- #number coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string coalitiontxt = "blue", -- #string
@ -783,8 +783,8 @@ AWACS.Messages = {
-- @field #string AwacsStateMission -- @field #string AwacsStateMission
-- @field #string AwacsStateFG -- @field #string AwacsStateFG
-- @field #boolean AwacsShiftChange -- @field #boolean AwacsShiftChange
-- @field #string EscortsStateMission -- @field #table EscortsStateMission
-- @field #string EscortsStateFG -- @field #table EscortsStateFG
-- @field #boolean EscortsShiftChange -- @field #boolean EscortsShiftChange
-- @field #number AICAPMax -- @field #number AICAPMax
-- @field #number AICAPCurrent -- @field #number AICAPCurrent
@ -1162,8 +1162,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
MonitoringData.AwacsStateFG = "unknown" MonitoringData.AwacsStateFG = "unknown"
MonitoringData.AwacsStateMission = "unknown" MonitoringData.AwacsStateMission = "unknown"
MonitoringData.EscortsShiftChange = false MonitoringData.EscortsShiftChange = false
MonitoringData.EscortsStateFG= "unknown" MonitoringData.EscortsStateFG = {}
MonitoringData.EscortsStateMission = "unknown" MonitoringData.EscortsStateMission = {}
self.MonitoringOn = false -- #boolean self.MonitoringOn = false -- #boolean
self.MonitoringData = MonitoringData self.MonitoringData = MonitoringData
@ -2027,9 +2027,9 @@ function AWACS:_StartEscorts(Shiftchange)
self.CatchAllMissions[#self.CatchAllMissions+1] = escort self.CatchAllMissions[#self.CatchAllMissions+1] = escort
if Shiftchange then if Shiftchange then
self.EscortMissionReplacement[i] = mission self.EscortMissionReplacement[i] = escort
else else
self.EscortMission[i] = mission self.EscortMission[i] = escort
end end
end end
@ -3597,10 +3597,13 @@ function AWACS:_SetClientMenus()
local tasking = MENU_GROUP:New(cgrp,"Tasking",basemenu) local tasking = MENU_GROUP:New(cgrp,"Tasking",basemenu)
local showtask = MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp) local showtask = MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp)
local commit
local unable
local abort
if self.PlayerCapAssignment then if self.PlayerCapAssignment then
local commit = MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp) commit = MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp)
local unable = MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp) unable = MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp)
local abort = MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp) abort = MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp)
--local judy = MENU_GROUP_COMMAND:New(cgrp,"Judy",tasking,self._Judy,self,cgrp) --local judy = MENU_GROUP_COMMAND:New(cgrp,"Judy",tasking,self._Judy,self,cgrp)
end end
@ -4933,8 +4936,8 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo
end end
end end
string.gsub(BRAText,"BRAA","brah") BRAText = string.gsub(BRAText,"BRAA","brah")
string.gsub(BRAText,"BRA","brah") BRAText = string.gsub(BRAText,"BRA","brah")
local prio = IsNew or IsBogeyDope local prio = IsNew or IsBogeyDope
self:_NewRadioEntry(BRAText,TextScreen,GID,isGroup,true,IsNew,false,prio) self:_NewRadioEntry(BRAText,TextScreen,GID,isGroup,true,IsNew,false,prio)
@ -5547,7 +5550,7 @@ end
-- @param #string Event -- @param #string Event
-- @param #string To -- @param #string To
-- @return #AWACS self -- @return #AWACS self
function AWACS:onbeforeStart(From,Event,to) function AWACS:onbeforeStart(From,Event,To)
self:T({From, Event, To}) self:T({From, Event, To})
if self.IncludeHelicopters then if self.IncludeHelicopters then
self.clientset:FilterCategories("helicopter") self.clientset:FilterCategories("helicopter")

View File

@ -204,7 +204,7 @@ CTLD_CARGO = {
-- @param #CTLD_CARGO self -- @param #CTLD_CARGO self
-- @param #boolean loaded -- @param #boolean loaded
function CTLD_CARGO:Isloaded() function CTLD_CARGO:Isloaded()
if self.HasBeenMoved and not self.WasDropped() then if self.HasBeenMoved and not self:WasDropped() then
return true return true
else else
return false return false
@ -1221,7 +1221,7 @@ CTLD.UnitTypes = {
--- CTLD class version. --- CTLD class version.
-- @field #string version -- @field #string version
CTLD.version="1.0.39" CTLD.version="1.0.40"
--- Instantiate a new CTLD. --- Instantiate a new CTLD.
-- @param #CTLD self -- @param #CTLD self
@ -1390,6 +1390,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- sub categories -- sub categories
self.usesubcats = false self.usesubcats = false
self.subcats = {} self.subcats = {}
self.subcatsTroop = {}
-- disallow building in loadzones -- disallow building in loadzones
self.nobuildinloadzones = true self.nobuildinloadzones = true
@ -2279,6 +2280,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop)
if not drop then if not drop then
inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then if not inzone then
---@diagnostic disable-next-line: cast-local-type
inzone, ship, zone, distance, width = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) inzone, ship, zone, distance, width = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end end
else else
@ -3468,6 +3470,12 @@ function CTLD:_RefreshF10Menus()
self.subcats[entry.Subcategory] = entry.Subcategory self.subcats[entry.Subcategory] = entry.Subcategory
end end
end end
for _id,_cargo in pairs(self.Cargo_Troops) do
local entry = _cargo -- #CTLD_CARGO
if not self.subcatsTroop[entry.Subcategory] then
self.subcatsTroop[entry.Subcategory] = entry.Subcategory
end
end
end end
-- build unit menus -- build unit menus
@ -3506,11 +3514,24 @@ function CTLD:_RefreshF10Menus()
-- sub menu troops management -- sub menu troops management
if cantroops then if cantroops then
local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops)
if self.usesubcats then
local subcatmenus = {}
for _name,_entry in pairs(self.subcatsTroop) do
subcatmenus[_name] = MENU_GROUP:New(_group,_name,troopsmenu)
end
for _,_entry in pairs(self.Cargo_Troops) do
local entry = _entry -- #CTLD_CARGO
local subcat = entry.Subcategory
menucount = menucount + 1
menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry)
end
else
for _,_entry in pairs(self.Cargo_Troops) do for _,_entry in pairs(self.Cargo_Troops) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
menucount = menucount + 1 menucount = menucount + 1
menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry)
end end
end
local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh()
local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh()
end end
@ -3603,7 +3624,8 @@ end
-- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). -- @param #number NoTroops Size of the group in number of Units across combined templates (for loading).
-- @param #number PerTroopMass Mass in kg of each soldier -- @param #number PerTroopMass Mass in kg of each soldier
-- @param #number Stock Number of groups in stock. Nil for unlimited. -- @param #number Stock Number of groups in stock. Nil for unlimited.
function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock) -- @param #string SubCategory Name of sub-category (optional).
function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,SubCategory)
self:T(self.lid .. " AddTroopsCargo") self:T(self.lid .. " AddTroopsCargo")
self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock})
if not self:_CheckTemplates(Templates) then if not self:_CheckTemplates(Templates) then
@ -3612,7 +3634,7 @@ function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock)
end end
self.CargoCounter = self.CargoCounter + 1 self.CargoCounter = self.CargoCounter + 1
-- Troops are directly loadable -- Troops are directly loadable
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock) local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock, SubCategory)
table.insert(self.Cargo_Troops,cargo) table.insert(self.Cargo_Troops,cargo)
return self return self
end end

View File

@ -522,7 +522,7 @@ function PLAYERTASK:MarkTargetOnF10Map(Text,Coalition,ReadOnly)
-- Marker exists, delete one first -- Marker exists, delete one first
self.TargetMarker:Remove() self.TargetMarker:Remove()
end end
local text = Text or "Target of "..self.lid local text = Text or ("Target of "..self.lid)
self.TargetMarker = MARKER:New(coordinate,text) self.TargetMarker = MARKER:New(coordinate,text)
if ReadOnly then if ReadOnly then
self.TargetMarker:ReadOnly() self.TargetMarker:ReadOnly()
@ -954,6 +954,7 @@ do
-- @field Utilities.FiFo#FIFO TasksPerPlayer -- @field Utilities.FiFo#FIFO TasksPerPlayer
-- @field Utilities.FiFo#FIFO PrecisionTasks -- @field Utilities.FiFo#FIFO PrecisionTasks
-- @field Core.Set#SET_CLIENT ClientSet -- @field Core.Set#SET_CLIENT ClientSet
-- @field Core.Set#SET_CLIENT ActiveClientSet
-- @field #string ClientFilter -- @field #string ClientFilter
-- @field #string Name -- @field #string Name
-- @field #string Type -- @field #string Type
@ -988,6 +989,7 @@ do
-- @field #table PlayerJoinMenu -- @field #table PlayerJoinMenu
-- @field #table PlayerInfoMenu -- @field #table PlayerInfoMenu
-- @field #boolean noflaresmokemenu -- @field #boolean noflaresmokemenu
-- @field #boolean illumenu
-- @field #boolean TransmitOnlyWithPlayers -- @field #boolean TransmitOnlyWithPlayers
-- @field #boolean buddylasing -- @field #boolean buddylasing
-- @field Ops.PlayerRecce#PLAYERRECCE PlayerRecce -- @field Ops.PlayerRecce#PLAYERRECCE PlayerRecce
@ -999,6 +1001,13 @@ do
-- @field #table PlayerMenuTag -- @field #table PlayerMenuTag
-- @field #boolean UseTypeNames -- @field #boolean UseTypeNames
-- @field Functional.Scoring#SCORING Scoring -- @field Functional.Scoring#SCORING Scoring
-- @field Core.ClientMenu#CLIENTMENUMANAGER JoinTaskMenuTemplate
-- @field Core.ClientMenu#CLIENTMENU JoinMenu
-- @field Core.ClientMenu#CLIENTMENU JoinTopMenu
-- @field Core.ClientMenu#CLIENTMENU JoinInfoMenu
-- @field Core.ClientMenu#CLIENTMENUMANAGER ActiveTaskMenuTemplate
-- @field Core.ClientMenu#CLIENTMENU ActiveTopMenu
-- @field Core.ClientMenu#CLIENTMENU ActiveInfoMenu
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- ---
@ -1155,6 +1164,7 @@ do
-- MENUMARK = "Mark on map", -- MENUMARK = "Mark on map",
-- MENUSMOKE = "Smoke", -- MENUSMOKE = "Smoke",
-- MENUFLARE = "Flare", -- MENUFLARE = "Flare",
-- MENUILLU = "Illuminate",
-- MENUABORT = "Abort", -- MENUABORT = "Abort",
-- MENUJOIN = "Join Task", -- MENUJOIN = "Join Task",
-- MENUTASKINFO = Task Info", -- MENUTASKINFO = Task Info",
@ -1315,6 +1325,7 @@ PLAYERTASKCONTROLLER = {
PlayerInfoMenu = {}, PlayerInfoMenu = {},
PlayerMenuTag = {}, PlayerMenuTag = {},
noflaresmokemenu = false, noflaresmokemenu = false,
illumenu = false,
TransmitOnlyWithPlayers = true, TransmitOnlyWithPlayers = true,
buddylasing = false, buddylasing = false,
PlayerRecce = nil, PlayerRecce = nil,
@ -1408,6 +1419,7 @@ PLAYERTASKCONTROLLER.Messages = {
MENUMARK = "Mark on map", MENUMARK = "Mark on map",
MENUSMOKE = "Smoke", MENUSMOKE = "Smoke",
MENUFLARE = "Flare", MENUFLARE = "Flare",
MENUILLU = "Illuminate",
MENUABORT = "Abort", MENUABORT = "Abort",
MENUJOIN = "Join Task", MENUJOIN = "Join Task",
MENUTASKINFO = "Task Info", MENUTASKINFO = "Task Info",
@ -1487,6 +1499,7 @@ PLAYERTASKCONTROLLER.Messages = {
MENUMARK = "Kartenmarkierung", MENUMARK = "Kartenmarkierung",
MENUSMOKE = "Rauchgranate", MENUSMOKE = "Rauchgranate",
MENUFLARE = "Leuchtgranate", MENUFLARE = "Leuchtgranate",
MENUILLU = "Feldbeleuchtung",
MENUABORT = "Abbrechen", MENUABORT = "Abbrechen",
MENUJOIN = "Auftrag annehmen", MENUJOIN = "Auftrag annehmen",
MENUTASKINFO = "Auftrag Briefing", MENUTASKINFO = "Auftrag Briefing",
@ -1592,25 +1605,28 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
self.CallsignTranslations = nil self.CallsignTranslations = nil
self.noflaresmokemenu = false self.noflaresmokemenu = false
self.illumenu = false
self.ShowMagnetic = true self.ShowMagnetic = true
self.UseTypeNames = false self.UseTypeNames = false
local IsClientSet = false self.IsClientSet = false
if ClientFilter and type(ClientFilter) == "table" and ClientFilter.ClassName and ClientFilter.ClassName == "SET_CLIENT" then if ClientFilter and type(ClientFilter) == "table" and ClientFilter.ClassName and ClientFilter.ClassName == "SET_CLIENT" then
-- we have a predefined SET_CLIENT -- we have a predefined SET_CLIENT
self.ClientSet = ClientFilter self.ClientSet = ClientFilter
IsClientSet = true self.IsClientSet = true
end end
if ClientFilter and not IsClientSet then if ClientFilter and not self.IsClientSet then
self.ClientSet = SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterPrefixes(ClientFilter):FilterStart() self.ClientSet = SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterPrefixes(ClientFilter):FilterStart()
elseif not IsClientSet then elseif not self.IsClientSet then
self.ClientSet = SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterStart() self.ClientSet = SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterStart()
end end
self.ActiveClientSet = SET_CLIENT:New()
self.lid=string.format("PlayerTaskController %s %s | ", self.Name, tostring(self.Type)) self.lid=string.format("PlayerTaskController %s %s | ", self.Name, tostring(self.Type))
self:_InitLocalization() self:_InitLocalization()
@ -1849,6 +1865,24 @@ function PLAYERTASKCONTROLLER:SetEnableSmokeFlareTask()
return self return self
end end
--- [User] Show menu entries to illuminate targets. Needs smoke/flare enabled.
-- @param #PLAYERTASKCONTROLLER self
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:SetEnableIlluminateTask()
self:T(self.lid.."SetEnableSmokeFlareTask")
self.illumenu = true
return self
end
--- [User] Do not show menu entries to illuminate targets.
-- @param #PLAYERTASKCONTROLLER self
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:SetDisableIlluminateTask()
self:T(self.lid.."SetDisableIlluminateTask")
self.illumenu = false
return self
end
--- [User] Show info text on screen with a coordinate info in any case (OFF by default) --- [User] Show info text on screen with a coordinate info in any case (OFF by default)
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @param #boolean OnOff Switch on = true or off = false -- @param #boolean OnOff Switch on = true or off = false
@ -2141,6 +2175,7 @@ end
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_EventHandler(EventData) function PLAYERTASKCONTROLLER:_EventHandler(EventData)
self:T(self.lid.."_EventHandler: "..EventData.id) self:T(self.lid.."_EventHandler: "..EventData.id)
self:T(self.lid.."_EventHandler: "..EventData.IniPlayerName)
if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then
if EventData.IniPlayerName then if EventData.IniPlayerName then
self:T(self.lid.."Event for player: "..EventData.IniPlayerName) self:T(self.lid.."Event for player: "..EventData.IniPlayerName)
@ -2164,11 +2199,14 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData)
self:T(self.lid..text) self:T(self.lid..text)
end end
elseif EventData.id == EVENTS.PlayerEnterAircraft and EventData.IniCoalition == self.Coalition then elseif EventData.id == EVENTS.PlayerEnterAircraft and EventData.IniCoalition == self.Coalition then
if EventData.IniPlayerName and EventData.IniGroup and self.UseSRS then if EventData.IniPlayerName and EventData.IniGroup then
if self.ClientSet:IsNotInSet(CLIENT:FindByName(EventData.IniUnitName)) then if self.IsClientSet and self.ClientSet:IsNotInSet(CLIENT:FindByName(EventData.IniUnitName)) then
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
return self return self
end end
self:T(self.lid.."Event for player: "..EventData.IniPlayerName) self:T(self.lid.."Event for player: "..EventData.IniPlayerName)
if self.UseSRS then
local frequency = self.Frequency local frequency = self.Frequency
local freqtext = "" local freqtext = ""
if type(frequency) == "table" then if type(frequency) == "table" then
@ -2195,10 +2233,11 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData)
--local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext)
local text = string.format(switchtext,playername,self.MenuName or self.Name,freqtext) local text = string.format(switchtext,playername,self.MenuName or self.Name,freqtext)
self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation) self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation)
end
if EventData.IniPlayerName then if EventData.IniPlayerName then
self.PlayerMenu[EventData.IniPlayerName] = nil self.PlayerMenu[EventData.IniPlayerName] = nil
--self:_BuildMenus(CLIENT:FindByName(EventData.IniUnitName)) local player = CLIENT:FindByName(EventData.IniUnitName)
self:_BuildMenus(CLIENT:FindByPlayerName(EventData.IniPlayerName)) self:_SwitchMenuForClient(player,"Info")
end end
end end
end end
@ -2308,7 +2347,7 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType()
self:T(self.lid.."_GetTasksPerType") self:T(self.lid.."_GetTasksPerType")
local tasktypes = self:_GetAvailableTaskTypes() local tasktypes = self:_GetAvailableTaskTypes()
--self:T({tasktypes}) --self:I({tasktypes})
-- Sort tasks per threat level first -- Sort tasks per threat level first
local datatable = self.TaskQueue:GetDataTable() local datatable = self.TaskQueue:GetDataTable()
@ -2327,11 +2366,23 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType()
local threat=_data.threat local threat=_data.threat
local task = _data.task -- Ops.PlayerTask#PLAYERTASK local task = _data.task -- Ops.PlayerTask#PLAYERTASK
local type = task.Type local type = task.Type
local name = task.Target:GetName()
--self:I(name)
if not task:IsDone() then if not task:IsDone() then
--self:I(name)
table.insert(tasktypes[type],task) table.insert(tasktypes[type],task)
end end
end end
--[[
for _type,_data in pairs(tasktypes) do
self:I("Task Type: ".._type)
for _id,_task in pairs(_data) do
self:I("Task Name: ".._task.Target:GetName())
end
end
--]]
return tasktypes return tasktypes
end end
@ -2443,7 +2494,7 @@ function PLAYERTASKCONTROLLER:_CheckTaskQueue()
local client = _client --Wrapper.Client#CLIENT local client = _client --Wrapper.Client#CLIENT
local group = client:GetGroup() local group = client:GetGroup()
for _,task in pairs(nexttasks) do for _,task in pairs(nexttasks) do
self:_JoinTask(group,client,task,true) self:_JoinTask(task,true,group,client)
end end
end end
end end
@ -2462,7 +2513,7 @@ end
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() function PLAYERTASKCONTROLLER:_CheckPrecisionTasks()
self:T(self.lid.."_CheckTaskQueue") self:T(self.lid.."_CheckPrecisionTasks")
if self.PrecisionTasks:Count() > 0 and self.precisionbombing then if self.PrecisionTasks:Count() > 0 and self.precisionbombing then
if not self.LasingDrone or self.LasingDrone:IsDead() then if not self.LasingDrone or self.LasingDrone:IsDead() then
-- we need a new drone -- we need a new drone
@ -2965,15 +3016,20 @@ end
--- [Internal] Join a player to a task --- [Internal] Join a player to a task
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @param Wrapper.Group#GROUP Group
-- @param Wrapper.Client#CLIENT Client
-- @param Ops.PlayerTask#PLAYERTASK Task -- @param Ops.PlayerTask#PLAYERTASK Task
-- @param #boolean Force Assign task even if client already has one -- @param #boolean Force Assign task even if client already has one
-- @param Wrapper.Group#GROUP Group
-- @param Wrapper.Client#CLIENT Client
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task, Force) function PLAYERTASKCONTROLLER:_JoinTask(Task, Force, Group, Client)
self:T({Force, Group, Client})
self:T(self.lid.."_JoinTask") self:T(self.lid.."_JoinTask")
local force = false
if type(Force) == "boolean" then
force = Force
end
local playername, ttsplayername = self:_GetPlayerName(Client) local playername, ttsplayername = self:_GetPlayerName(Client)
if self.TasksPerPlayer:HasUniqueID(playername) and not Force then if self.TasksPerPlayer:HasUniqueID(playername) and not force then
-- Player already has a task -- Player already has a task
if not self.NoScreenOutput then if not self.NoScreenOutput then
local text = self.gettext:GetEntry("HAVEACTIVETASK",self.locale) local text = self.gettext:GetEntry("HAVEACTIVETASK",self.locale)
@ -3004,7 +3060,7 @@ function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task, Force)
self.TasksPerPlayer:Push(Task,playername) self.TasksPerPlayer:Push(Task,playername)
self:__PlayerJoinedTask(1, Group, Client, Task) self:__PlayerJoinedTask(1, Group, Client, Task)
-- clear menu -- clear menu
self:_BuildMenus(Client,true) self:_SwitchMenuForClient(Client,"Active",1)
end end
if Task.Type == AUFTRAG.Type.PRECISIONBOMBING then if Task.Type == AUFTRAG.Type.PRECISIONBOMBING then
if not self.PrecisionTasks:HasUniqueID(Task.PlayerTaskNr) then if not self.PrecisionTasks:HasUniqueID(Task.PlayerTaskNr) then
@ -3065,19 +3121,23 @@ end
--- [Internal] Show active task info --- [Internal] Show active task info
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @param Ops.PlayerTask#PLAYERTASK Task
-- @param Wrapper.Group#GROUP Group -- @param Wrapper.Group#GROUP Group
-- @param Wrapper.Client#CLIENT Client -- @param Wrapper.Client#CLIENT Client
-- @param Ops.PlayerTask#PLAYERTASK Task
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
self:T(self.lid.."_ActiveTaskInfo") self:T(self.lid.."_ActiveTaskInfo")
local playername, ttsplayername = self:_GetPlayerName(Client) local playername, ttsplayername = self:_GetPlayerName(Client)
local text = "" local text = ""
local textTTS = "" local textTTS = ""
if self.TasksPerPlayer:HasUniqueID(playername) or Task then local task = nil
if type(Task) ~= "string" then
task = Task
end
if self.TasksPerPlayer:HasUniqueID(playername) or task then
-- NODO: Show multiple? -- NODO: Show multiple?
-- Details -- Details
local task = Task or self.TasksPerPlayer:ReadByID(playername) -- Ops.PlayerTask#PLAYERTASK local task = task or self.TasksPerPlayer:ReadByID(playername) -- Ops.PlayerTask#PLAYERTASK
local tname = self.gettext:GetEntry("TASKNAME",self.locale) local tname = self.gettext:GetEntry("TASKNAME",self.locale)
local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale) local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale)
local taskname = string.format(tname,task.Type,task.PlayerTaskNr) local taskname = string.format(tname,task.Type,task.PlayerTaskNr)
@ -3389,280 +3449,287 @@ function PLAYERTASKCONTROLLER:_AbortTask(Group, Client)
if not self.NoScreenOutput then if not self.NoScreenOutput then
local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client)
end end
self:_BuildMenus(Client,true) self:_SwitchMenuForClient(Client,"Info",1)
return self return self
end end
--- [Internal] Build Task Info Menu
-- @param #PLAYERTASKCONTROLLER self
-- @param Wrapper.Group#GROUP group
-- @param Wrapper.Client#CLIENT client
-- @param #string playername
-- @param Core.Menu#MENU_BASE topmenu
-- @param #table tasktypes
-- @param #table taskpertype
-- @param #string newtag
-- @return #table taskinfomenu
function PLAYERTASKCONTROLLER:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype,newtag)
self:T(self.lid.."_BuildTaskInfoMenu")
local taskinfomenu = nil
if self.taskinfomenu then
local menutaskinfo = self.gettext:GetEntry("MENUTASKINFO",self.locale)
local taskinfomenu = MENU_GROUP:New(group,menutaskinfo,topmenu):SetTag(newtag)
local ittypes = {}
local itaskmenu = {}
local tnow = timer.getTime()
for _tasktype,_data in pairs(tasktypes) do -- TODO - New Menu Manager
ittypes[_tasktype] = MENU_GROUP:New(group,_tasktype,taskinfomenu):SetTag(newtag) --- [Internal] _UpdateJoinMenuTemplate
local tasks = taskpertype[_tasktype] or {} -- @param #PLAYERTASKCONTROLLER self
local n = 0 -- @return #PLAYERTASKCONTROLLER self
for _,_task in pairs(tasks) do function PLAYERTASKCONTROLLER:_UpdateJoinMenuTemplate()
_task = _task -- Ops.PlayerTask#PLAYERTASK self:T("_UpdateJoinMenuTemplate")
local pilotcount = _task:CountClients() if self.TaskQueue:Count() > 0 then
local newtext = "]" local taskpertype = self:_GetTasksPerType()
-- marker for new tasks local JoinMenu = self.JoinMenu -- Core.ClientMenu#CLIENTMENU
if tnow - _task.timestamp < 60 then --self:I(JoinMenu.UUID)
newtext = "*]" local controller = self.JoinTaskMenuTemplate -- Core.ClientMenu#CLIENTMENUMANAGER
local actcontroller = self.ActiveTaskMenuTemplate -- Core.ClientMenu#CLIENTMENUMANAGER
local actinfomenu = self.ActiveInfoMenu
--local entrynumbers = {}
--local existingentries = {}
local maxn = self.menuitemlimit
-- Generate task type menu items
for _type,_ in pairs(taskpertype) do
local found = controller:FindEntriesByText(_type)
--self:I({found})
if #found == 0 then
local newentry = controller:NewEntry(_type,JoinMenu)
controller:AddEntry(newentry)
if self.JoinInfoMenu then
local newentry = controller:NewEntry(_type,self.JoinInfoMenu)
controller:AddEntry(newentry)
end end
if actinfomenu then
local newentry = actcontroller:NewEntry(_type,self.ActiveInfoMenu)
actcontroller:AddEntry(newentry)
end
end
end
local typelist = self:_GetAvailableTaskTypes()
-- Slot in Tasks
for _tasktype,_data in pairs(typelist) do
self:T("**** Building for TaskType: ".._tasktype)
--local tasks = taskpertype[_tasktype] or {}
for _,_task in pairs(taskpertype[_tasktype]) do
_task = _task -- Ops.PlayerTask#PLAYERTASK
self:T("**** Building for Task: ".._task.Target:GetName())
if _task.InMenu then
self:T("**** Task already in Menu ".._task.Target:GetName())
else
--local pilotcount = _task:CountClients()
--local newtext = "]"
--local tnow = timer.getTime()
-- marker for new tasks
--if tnow - _task.timestamp < 60 then
--newtext = "*]"
--end
local menutaskno = self.gettext:GetEntry("MENUTASKNO",self.locale) local menutaskno = self.gettext:GetEntry("MENUTASKNO",self.locale)
local text = string.format("%s %03d [%d%s",menutaskno,_task.PlayerTaskNr,pilotcount,newtext) --local text = string.format("%s %03d [%d%s",menutaskno,_task.PlayerTaskNr,pilotcount,newtext)
local text = string.format("%s %03d",menutaskno,_task.PlayerTaskNr)
if self.UseGroupNames then if self.UseGroupNames then
local name = _task.Target:GetName() local name = _task.Target:GetName()
if name ~= "Unknown" then if name ~= "Unknown" then
text = string.format("%s (%03d) [%d%s",name,_task.PlayerTaskNr,pilotcount,newtext) --text = string.format("%s (%03d) [%d%s",name,_task.PlayerTaskNr,pilotcount,newtext)
text = string.format("%s (%03d)",name,_task.PlayerTaskNr)
end end
end end
if self.UseTypeNames then --local taskentry = MENU_GROUP_COMMAND:New(group,text,ttypes[_tasktype],self._JoinTask,self,group,client,_task):SetTag(newtag)
if _task.TypeName then local parenttable, number = controller:FindEntriesByText(_tasktype,JoinMenu)
--local name = self.gettext:GetEntry(_task.TypeName,self.locale) if number > 0 then
text = string.format("%s (%03d) [%d%s",_task.TypeName,_task.PlayerTaskNr,pilotcount,newtext) local Parent = parenttable[1]
--self:T(self.lid.."Menu text = "..text) local matches, count = controller:FindEntriesByParent(Parent)
self:T("***** Join Menu ".._tasktype.. " # of entries: "..count)
if count < self.menuitemlimit then
local taskentry = controller:NewEntry(text,Parent,self._JoinTask,self,_task,"false")
controller:AddEntry(taskentry)
_task.InMenu = true
if not _task.UUIDS then _task.UUIDS = {} end
table.insert(_task.UUIDS,taskentry.UUID)
end
end
if self.JoinInfoMenu then
local parenttable, number = controller:FindEntriesByText(_tasktype,self.JoinInfoMenu)
if number > 0 then
local Parent = parenttable[1]
local matches, count = controller:FindEntriesByParent(Parent)
self:T("***** Join Info Menu ".._tasktype.. " # of entries: "..count)
if count < self.menuitemlimit then
local taskentry = controller:NewEntry(text,Parent,self._ActiveTaskInfo,self,_task)
controller:AddEntry(taskentry)
_task.InMenu = true
if not _task.UUIDS then _task.UUIDS = {} end
table.insert(_task.UUIDS,taskentry.UUID)
end
end
end
if actinfomenu then
local parenttable, number = actcontroller:FindEntriesByText(_tasktype,self.ActiveInfoMenu)
if number > 0 then
local Parent = parenttable[1]
local matches, count = actcontroller:FindEntriesByParent(Parent)
self:T("***** Active Info Menu ".._tasktype.. " # of entries: "..count)
if count < self.menuitemlimit then
local taskentry = actcontroller:NewEntry(text,Parent,self._ActiveTaskInfo,self,_task)
actcontroller:AddEntry(taskentry)
_task.InMenu = true
if not _task.AUUIDS then _task.AUUIDS = {} end
table.insert(_task.AUUIDS,taskentry.UUID)
end end
end end
local taskentry = MENU_GROUP_COMMAND:New(group,text,ittypes[_tasktype],self._ActiveTaskInfo,self,group,client,_task):SetTag(newtag)
--taskentry:SetTag(playername)
itaskmenu[#itaskmenu+1] = taskentry
-- keep max items limit
n = n + 1
if n >= self.menuitemlimit then
break
end end
end end
end end
end end
return taskinfomenu
end end
--- [Internal] Build client menus
-- @param #PLAYERTASKCONTROLLER self
-- @param Wrapper.Client#CLIENT Client (optional) build for this client name only
-- @param #boolean enforced
-- @param #boolean fromsuccess
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
self:T(self.lid.."_BuildMenus")
if self.MenuBuildLocked and (timer.getAbsTime() - self.MenuBuildLocked < 2) then
self:ScheduleOnce(2,self._BuildMenus,self,Client,enforced,fromsuccess)
return self return self
else
self.MenuBuildLocked = timer.getAbsTime()
end end
local clients = self.ClientSet:GetAliveSet() --- [Internal] _RemoveMenuEntriesForTask
local joinorabort = false -- @param #PLAYERTASKCONTROLLER self
local timedbuild = false -- @param #PLAYERTASK Task
-- @param Wrapper.Client#CLIENT Client
if Client then -- @return #PLAYERTASKCONTROLLER self
-- client + enforced -- join task or abort function PLAYERTASKCONTROLLER:_RemoveMenuEntriesForTask(Task,Client)
clients = {Client} self:T("_RemoveMenuEntriesForTask")
enforced = true --self:I("Task name: "..Task.Target:GetName())
joinorabort = true --self:I("Client: "..Client:GetPlayerName())
if Task then
if Task.UUIDS and self.JoinTaskMenuTemplate then
--self:I("***** JoinTaskMenuTemplate")
UTILS.PrintTableToLog(Task.UUIDS)
local controller = self.JoinTaskMenuTemplate
for _,_uuid in pairs(Task.UUIDS) do
local Entry = controller:FindEntryByUUID(_uuid)
if Entry then
controller:DeleteF10Entry(Entry,Client)
controller:DeleteGenericEntry(Entry)
UTILS.PrintTableToLog(controller.menutree)
end
end
end end
local tasktypes = self:_GetAvailableTaskTypes() if Task.AUUIDS and self.ActiveTaskMenuTemplate then
local taskpertype = self:_GetTasksPerType() --self:I("***** ActiveTaskMenuTemplate")
UTILS.PrintTableToLog(Task.AUUIDS)
for _,_uuid in pairs(Task.AUUIDS) do
local controller = self.ActiveTaskMenuTemplate
local Entry = controller:FindEntryByUUID(_uuid)
if Entry then
controller:DeleteF10Entry(Entry,Client)
controller:DeleteGenericEntry(Entry)
UTILS.PrintTableToLog(controller.menutree)
end
end
end
for _,_client in pairs(clients) do Task.UUIDS = nil
if _client and _client:IsAlive() then Task.AUUIDS = nil
local client = _client -- Wrapper.Client#CLIENT end
local group = client:GetGroup() return self
local unknown = self.gettext:GetEntry("UNKNOWN",self.locale) end
local playername = client:GetPlayerName() or unknown
local oldtag = self.PlayerMenuTag[playername] --- [Internal] _CreateJoinMenuTemplate
local newtag = playername..timer.getAbsTime() -- @param #PLAYERTASKCONTROLLER self
self.PlayerMenuTag[playername] = newtag -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_CreateJoinMenuTemplate()
self:T("_CreateActiveTaskMenuTemplate")
if group and client then local menujoin = self.gettext:GetEntry("MENUJOIN",self.locale)
--- local menunotasks = self.gettext:GetEntry("MENUNOTASKS",self.locale)
-- TOPMENU local flashtext = self.gettext:GetEntry("FLASHMENU",self.locale)
---
local JoinTaskMenuTemplate = CLIENTMENUMANAGER:New(self.ClientSet,"JoinTask")
if not self.JoinTopMenu then
local taskings = self.gettext:GetEntry("MENUTASKING",self.locale) local taskings = self.gettext:GetEntry("MENUTASKING",self.locale)
local longname = self.Name..taskings..self.Type local longname = self.Name..taskings..self.Type
local menuname = self.MenuName or longname local menuname = self.MenuName or longname
local playerhastask = false self.JoinTopMenu = JoinTaskMenuTemplate:NewEntry(menuname,self.MenuParent)
if self:_CheckPlayerHasTask(playername) and not fromsuccess then playerhastask = true end
local topmenu = nil
--local oldmenu = nil
local rebuilddone = false
self:T("Playerhastask = "..tostring(playerhastask).." Enforced = "..tostring(enforced).." Join or Abort = "..tostring(joinorabort))
-- Cases to rebuild menu
-- 1) new player
-- 2) player joined a task, joinorabort = true
-- 3) player left a task, joinorabort = true
-- 4) player has no task, but number of tasks changed, and last build > 30 secs ago
if self.PlayerMenu[playername] then
-- NOT a new player
-- 2)+3) Join or abort?
if joinorabort then
self.PlayerMenu[playername]:RemoveSubMenus()
self.PlayerMenu[playername] = MENU_GROUP:New(group,menuname,self.MenuParent)
self.PlayerMenu[playername]:SetTag(newtag)
topmenu = self.PlayerMenu[playername]
elseif (not playerhastask) or enforced then
-- 4) last build > 30 secs?
local T0 = timer.getAbsTime()
local TDiff = T0-self.PlayerMenu[playername].PTTimeStamp
self:T("TDiff = "..string.format("%.2d",TDiff))
if TDiff >= self.holdmenutime then
--self.PlayerMenu[playername]:RemoveSubMenus()
--oldmenu = self.PlayerMenu[playername]
--self.PlayerMenu[playername] = nil
--self.PlayerMenu[playername]:RemoveSubMenus()
--self.PlayerMenu[playername] = MENU_GROUP:New(group,menuname,self.MenuParent)
--self.PlayerMenu[playername]:SetTag(newtag)
--self.PlayerMenu[playername].PTTimeStamp = timer.getAbsTime()
--timedbuild = true
end
topmenu = self.PlayerMenu[playername]
end
else
-- 1) new player#
topmenu = MENU_GROUP:New(group,menuname,self.MenuParent)
self.PlayerMenu[playername] = topmenu
self.PlayerMenu[playername]:SetTag(newtag)
self.PlayerMenu[playername].PTTimeStamp = timer.getAbsTime()
enforced = true
end end
--- if self.AllowFlash then
-- ACTIVE TASK MENU JoinTaskMenuTemplate:NewEntry(flashtext,self.JoinTopMenu,self._SwitchFlashing,self)
--- end
if playerhastask and enforced then
self:T("Building Active Task Menus for "..playername) self.JoinMenu = JoinTaskMenuTemplate:NewEntry(menujoin,self.JoinTopMenu)
rebuilddone = true
if self.taskinfomenu then
local menutaskinfo = self.gettext:GetEntry("MENUTASKINFO",self.locale)
self.JoinInfoMenu = JoinTaskMenuTemplate:NewEntry(menutaskinfo,self.JoinTopMenu)
end
if self.TaskQueue:Count() == 0 then
JoinTaskMenuTemplate:NewEntry(menunotasks,self.JoinMenu)
end
self.JoinTaskMenuTemplate = JoinTaskMenuTemplate
return self
end
--- [Internal] _CreateActiveTaskMenuTemplate
-- @param #PLAYERTASKCONTROLLER self
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_CreateActiveTaskMenuTemplate()
self:T("_CreateActiveTaskMenuTemplate")
local menuactive = self.gettext:GetEntry("MENUACTIVE",self.locale) local menuactive = self.gettext:GetEntry("MENUACTIVE",self.locale)
local menuinfo = self.gettext:GetEntry("MENUINFO",self.locale) local menuinfo = self.gettext:GetEntry("MENUINFO",self.locale)
local menumark = self.gettext:GetEntry("MENUMARK",self.locale) local menumark = self.gettext:GetEntry("MENUMARK",self.locale)
local menusmoke = self.gettext:GetEntry("MENUSMOKE",self.locale) local menusmoke = self.gettext:GetEntry("MENUSMOKE",self.locale)
local menuflare = self.gettext:GetEntry("MENUFLARE",self.locale) local menuflare = self.gettext:GetEntry("MENUFLARE",self.locale)
local menuillu = self.gettext:GetEntry("MENUILLU",self.locale)
local menuabort = self.gettext:GetEntry("MENUABORT",self.locale) local menuabort = self.gettext:GetEntry("MENUABORT",self.locale)
local active = MENU_GROUP:New(group,menuactive,topmenu):SetTag(newtag) local ActiveTaskMenuTemplate = CLIENTMENUMANAGER:New(self.ActiveClientSet,"ActiveTask")
local info = MENU_GROUP_COMMAND:New(group,menuinfo,active,self._ActiveTaskInfo,self,group,client):SetTag(newtag)
local mark = MENU_GROUP_COMMAND:New(group,menumark,active,self._MarkTask,self,group,client):SetTag(newtag)
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
if self.noflaresmokemenu ~= true then
-- no smoking/flaring here if A2A or designer has set noflaresmokemenu to true
local smoke = MENU_GROUP_COMMAND:New(group,menusmoke,active,self._SmokeTask,self,group,client):SetTag(newtag)
local flare = MENU_GROUP_COMMAND:New(group,menuflare,active,self._FlareTask,self,group,client):SetTag(newtag)
local IsNight = client:GetCoordinate():IsNight()
if IsNight then
local light = MENU_GROUP_COMMAND:New(group,menuflare,active,self._IlluminateTask,self,group,client):SetTag(newtag)
end
end
end
local abort = MENU_GROUP_COMMAND:New(group,menuabort,active,self._AbortTask,self,group,client):SetTag(newtag)
if self.activehasinfomenu and self.taskinfomenu then
self:T("Building Active-Info Menus for "..playername)
if self.PlayerInfoMenu[playername] then
self.PlayerInfoMenu[playername]:RemoveSubMenus(nil,oldtag)
end
self.PlayerInfoMenu[playername] = self:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype,newtag)
end
elseif (self.TaskQueue:Count() > 0 and enforced) or (not playerhastask and (timedbuild or joinorabort)) then
self:T("Building Join Menus for "..playername)
rebuilddone = true
---
-- JOIN TASK MENU
---
local menujoin = self.gettext:GetEntry("MENUJOIN",self.locale)
if self.PlayerJoinMenu[playername] then if not self.ActiveTopMenu then
self.PlayerJoinMenu[playername]:RemoveSubMenus(nil,oldtag) local taskings = self.gettext:GetEntry("MENUTASKING",self.locale)
local longname = self.Name..taskings..self.Type
local menuname = self.MenuName or longname
self.ActiveTopMenu = ActiveTaskMenuTemplate:NewEntry(menuname,self.MenuParent)
end end
local joinmenu = MENU_GROUP:New(group,menujoin,topmenu):SetTag(newtag) if self.AllowFlash then
self.PlayerJoinMenu[playername] = joinmenu
local ttypes = {}
local taskmenu = {}
for _tasktype,_data in pairs(tasktypes) do
ttypes[_tasktype] = MENU_GROUP:New(group,_tasktype,joinmenu):SetTag(newtag)
local tasks = taskpertype[_tasktype] or {}
local n = 0
for _,_task in pairs(tasks) do
_task = _task -- Ops.PlayerTask#PLAYERTASK
local pilotcount = _task:CountClients()
local newtext = "]"
local tnow = timer.getTime()
-- marker for new tasks
if tnow - _task.timestamp < 60 then
newtext = "*]"
end
local menutaskno = self.gettext:GetEntry("MENUTASKNO",self.locale)
local text = string.format("%s %03d [%d%s",menutaskno,_task.PlayerTaskNr,pilotcount,newtext)
if self.UseGroupNames then
local name = _task.Target:GetName()
if name ~= "Unknown" then
text = string.format("%s (%03d) [%d%s",name,_task.PlayerTaskNr,pilotcount,newtext)
end
end
local taskentry = MENU_GROUP_COMMAND:New(group,text,ttypes[_tasktype],self._JoinTask,self,group,client,_task):SetTag(newtag)
--taskentry:SetTag(playername)
taskmenu[#taskmenu+1] = taskentry
n = n + 1
if n >= self.menuitemlimit then
break
end
end
end
if self.taskinfomenu then
self:T("Building Join-Info Menus for "..playername)
if self.PlayerInfoMenu[playername] then
self.PlayerInfoMenu[playername]:RemoveSubMenus(nil,oldtag)
end
self.PlayerInfoMenu[playername] = self:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype,newtag)
end
end
if self.AllowFlash and topmenu ~= nil then
local flashtext = self.gettext:GetEntry("FLASHMENU",self.locale) local flashtext = self.gettext:GetEntry("FLASHMENU",self.locale)
local flashmenu = MENU_GROUP_COMMAND:New(group,flashtext,topmenu,self._SwitchFlashing,self,group,client):SetTag(newtag) ActiveTaskMenuTemplate:NewEntry(flashtext,self.ActiveTopMenu,self._SwitchFlashing,self)
end end
if self.TaskQueue:Count() == 0 then
self:T("No open tasks info") local active = ActiveTaskMenuTemplate:NewEntry(menuactive,self.ActiveTopMenu)
local menunotasks = self.gettext:GetEntry("MENUNOTASKS",self.locale) ActiveTaskMenuTemplate:NewEntry(menuinfo,active,self._ActiveTaskInfo,self,"NONE")
local joinmenu = MENU_GROUP:New(group,menunotasks,self.PlayerMenu[playername]):SetTag(newtag) ActiveTaskMenuTemplate:NewEntry(menumark,active,self._MarkTask,self)
rebuilddone = true
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and self.noflaresmokemenu ~= true then
ActiveTaskMenuTemplate:NewEntry(menusmoke,active,self._SmokeTask,self)
ActiveTaskMenuTemplate:NewEntry(menuflare,active,self._FlareTask,self)
if self.illumenu then
ActiveTaskMenuTemplate:NewEntry(menuillu,active,self._IlluminateTask,self)
end end
---
-- REFRESH MENU
---
if rebuilddone then
--self.PlayerMenu[playername]:RemoveSubMenus(nil,oldtag)
--self.PlayerMenu[playername]:Set()
self.PlayerMenu[playername]:Refresh()
end end
ActiveTaskMenuTemplate:NewEntry(menuabort,active,self._AbortTask,self)
self.ActiveTaskMenuTemplate = ActiveTaskMenuTemplate
if self.taskinfomenu and self.activehasinfomenu then
local menutaskinfo = self.gettext:GetEntry("MENUTASKINFO",self.locale)
self.ActiveInfoMenu = ActiveTaskMenuTemplate:NewEntry(menutaskinfo,self.ActiveTopMenu)
end end
return self
end end
--- [Internal] _SwitchMenuForClient
-- @param #PLAYERTASKCONTROLLER self
-- @param Wrapper.Client#CLIENT Client The client
-- @param #string MenuType
-- @param #number Delay
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_SwitchMenuForClient(Client,MenuType,Delay)
self:T(self.lid.."_SwitchMenuForClient")
if Delay then
self:ScheduleOnce(Delay,self._SwitchMenuForClient,self,Client,MenuType)
return self
end
if MenuType == "Info" then
self.ClientSet:AddClientsByName(Client:GetName())
self.ActiveClientSet:Remove(Client:GetName(),true)
self.ActiveTaskMenuTemplate:ResetMenu(Client)
self.JoinTaskMenuTemplate:ResetMenu(Client)
self.JoinTaskMenuTemplate:Propagate(Client)
elseif MenuType == "Active" then
self.ActiveClientSet:AddClientsByName(Client:GetName())
self.ClientSet:Remove(Client:GetName(),true)
self.ActiveTaskMenuTemplate:ResetMenu(Client)
self.JoinTaskMenuTemplate:ResetMenu(Client)
self.ActiveTaskMenuTemplate:Propagate(Client)
else
self:E(self.lid .."Unknown menu type in _SwitchMenuForClient:"..tostring(MenuType))
end end
self.MenuBuildLocked = false
return self return self
end end
@ -3810,8 +3877,8 @@ end
-- @param Core.Menu#MENU_MISSION Menu -- @param Core.Menu#MENU_MISSION Menu
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:SetParentMenu(Menu) function PLAYERTASKCONTROLLER:SetParentMenu(Menu)
self:T(self.lid.."SetParentName") self:T(self.lid.."SetParentMenu")
self.MenuParent = Menu --self.MenuParent = Menu
return self return self
end end
@ -3965,6 +4032,20 @@ end
-- TODO: FSM Functions PLAYERTASKCONTROLLER -- TODO: FSM Functions PLAYERTASKCONTROLLER
------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------
--- [Internal] On after start call
-- @param #PLAYERTASKCONTROLLER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:onafterStart(From, Event, To)
self:T({From, Event, To})
self:T(self.lid.."onafterStart")
self:_CreateJoinMenuTemplate()
self:_CreateActiveTaskMenuTemplate()
return self
end
--- [Internal] On after Status call --- [Internal] On after Status call
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @param #string From -- @param #string From
@ -3994,7 +4075,7 @@ function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To)
end end
end end
self:_BuildMenus(nil,enforcedmenu) self:_UpdateJoinMenuTemplate()
if self.verbose then if self.verbose then
local text = string.format("%s | New Targets: %02d | Active Tasks: %02d | Active Players: %02d | Assigned Tasks: %02d",self.MenuName, targetcount,taskcount,playercount,assignedtasks) local text = string.format("%s | New Targets: %02d | Active Tasks: %02d | Active Players: %02d | Assigned Tasks: %02d",self.MenuName, targetcount,taskcount,playercount,assignedtasks)
@ -4041,6 +4122,16 @@ function PLAYERTASKCONTROLLER:onafterTaskCancelled(From, Event, To, Task)
taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType)) taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end end
local clients=Task:GetClientObjects()
for _,client in pairs(clients) do
self:_RemoveMenuEntriesForTask(Task,client)
--self:_SwitchMenuForClient(client,"Info")
end
for _,client in pairs(clients) do
--self:_RemoveMenuEntriesForTask(Task,client)
self:_SwitchMenuForClient(client,"Info",5)
end
return self return self
end end
@ -4065,9 +4156,15 @@ function PLAYERTASKCONTROLLER:onafterTaskSuccess(From, Event, To, Task)
taskname = string.format(succtxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType)) taskname = string.format(succtxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end end
local clients=Task:GetClientObjects() local clients=Task:GetClientObjects()
for _,client in pairs(clients) do for _,client in pairs(clients) do
self:_BuildMenus(client,true,true) self:_RemoveMenuEntriesForTask(Task,client)
--self:_SwitchMenuForClient(client,"Info")
end
for _,client in pairs(clients) do
-- self:_RemoveMenuEntriesForTask(Task,client)
self:_SwitchMenuForClient(client,"Info",5)
end end
return self return self
end end

View File

@ -236,6 +236,7 @@ ENUMS.WeaponType.Any={
-- @field #string ESCORT Escort another group. -- @field #string ESCORT Escort another group.
-- @field #string FIGHTERSWEEP Fighter sweep. -- @field #string FIGHTERSWEEP Fighter sweep.
-- @field #string GROUNDATTACK Ground attack. -- @field #string GROUNDATTACK Ground attack.
-- @field #string GROUNDESCORT Ground escort another group.
-- @field #string INTERCEPT Intercept. -- @field #string INTERCEPT Intercept.
-- @field #string PINPOINTSTRIKE Pinpoint strike. -- @field #string PINPOINTSTRIKE Pinpoint strike.
-- @field #string RECONNAISSANCE Reconnaissance mission. -- @field #string RECONNAISSANCE Reconnaissance mission.
@ -251,6 +252,7 @@ ENUMS.MissionTask={
CAP="CAP", CAP="CAP",
CAS="CAS", CAS="CAS",
ESCORT="Escort", ESCORT="Escort",
GROUNDESCORT="Ground escort",
FIGHTERSWEEP="Fighter Sweep", FIGHTERSWEEP="Fighter Sweep",
GROUNDATTACK="Ground Attack", GROUNDATTACK="Ground Attack",
INTERCEPT="Intercept", INTERCEPT="Intercept",

View File

@ -299,14 +299,14 @@ end
-- @param #table tbl Input table. -- @param #table tbl Input table.
UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function
lookup_table = {} local lookup_table = {}
local function _Serialize( tbl ) local function _Serialize( tbl )
if type(tbl) == 'table' then --function only works for tables! if type(tbl) == 'table' then --function only works for tables!
if lookup_table[tbl] then if lookup_table[tbl] then
return lookup_table[object] return lookup_table[tbl]
end end
local tbl_str = {} local tbl_str = {}

View File

@ -12,7 +12,7 @@
-- @image Wrapper_Airbase.JPG -- @image Wrapper_Airbase.JPG
--- @type AIRBASE -- @type AIRBASE
-- @field #string ClassName Name of the class, i.e. "AIRBASE". -- @field #string ClassName Name of the class, i.e. "AIRBASE".
-- @field #table CategoryName Names of airbase categories. -- @field #table CategoryName Names of airbase categories.
-- @field #string AirbaseName Name of the airbase. -- @field #string AirbaseName Name of the airbase.
@ -1501,16 +1501,16 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Get the aircraft size, i.e. it's longest side of x,z. -- Get the aircraft size, i.e. it's longest side of x,z.
local aircraft = nil -- fix local problem below local aircraft = nil -- fix local problem below
local _aircraftsize, ax,ay,az -- SU27 dimensions as default
local _aircraftsize = 23
local ax = 23 -- l
local ay = 7 -- h
local az = 17 -- w
if group and group.ClassName == "GROUP" then if group and group.ClassName == "GROUP" then
aircraft=group:GetUnit(1) aircraft=group:GetUnit(1)
if aircraft then
_aircraftsize, ax,ay,az=aircraft:GetObjectSize() _aircraftsize, ax,ay,az=aircraft:GetObjectSize()
else end
-- SU27 dimensions
_aircraftsize = 23
ax = 23 -- length
ay = 7 -- height
az = 17 -- width
end end

View File

@ -52,6 +52,7 @@
-- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. -- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable.
-- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. -- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location.
-- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. -- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable.
-- * @{#CONTROLLABLE.TaskGroundEscort}: (AIR/HELO) Escort a ground controllable.
-- * @{#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. -- * @{#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction.
-- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire some or all ammunition at a VEC2 point. -- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire some or all ammunition at a VEC2 point.
-- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. -- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable.
@ -1480,15 +1481,53 @@ function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex )
return DCSTask return DCSTask
end end
--- (AIR/HELO) Escort a ground controllable.
-- The unit / controllable will follow lead unit of the other controllable, additional units of both controllables will continue following their leaders.
-- The unit / controllable will also protect that controllable from threats of specified types.
-- @param #CONTROLLABLE self
-- @param #CONTROLLABLE FollowControllable The controllable to be escorted.
-- @param #number LastWaypointIndex (optional) Detach waypoint of another controllable. Once reached the unit / controllable Escort task is finished.
-- @param #number OrbitDistance (optional) Maximum distance helo will orbit around the ground unit in meters. Defaults to 2000 meters.
-- @param DCS#AttributeNameArray TargetTypes (optional) Array of AttributeName that is contains threat categories allowed to engage. Default {"Ground vehicles"}. See [https://wiki.hoggit.us/view/DCS_enum_attributes](https://wiki.hoggit.us/view/DCS_enum_attributes)
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskGroundEscort( FollowControllable, LastWaypointIndex, OrbitDistance, TargetTypes )
-- Escort = {
-- id = 'GroundEscort',
-- params = {
-- groupId = Group.ID, -- must
-- engagementDistMax = Distance, -- Must. With his task it does not appear to actually define the range AI are allowed to attack at, rather it defines the size length of the orbit. The helicopters will fly up to this set distance before returning to the escorted group.
-- lastWptIndexFlag = boolean, -- optional
-- lastWptIndex = number, -- optional
-- targetTypes = array of AttributeName, -- must
-- lastWptIndexFlagChangedManually = boolean, -- must be true
-- }
-- }
local DCSTask = {
id = 'GroundEscort',
params = {
groupId = FollowControllable and FollowControllable:GetID() or nil,
engagementDistMax = OrbitDistance or 2000,
lastWptIndexFlag = LastWaypointIndex and true or false,
lastWptIndex = LastWaypointIndex,
targetTypes = TargetTypes or {"Ground vehicles"},
lastWptIndexFlagChangedManually = true,
},
}
return DCSTask
end
--- (AIR) Escort another airborne controllable. --- (AIR) Escort another airborne controllable.
-- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders.
-- The unit / controllable will also protect that controllable from threats of specified types. -- The unit / controllable will also protect that controllable from threats of specified types.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #CONTROLLABLE FollowControllable The controllable to be escorted. -- @param #CONTROLLABLE FollowControllable The controllable to be escorted.
-- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. -- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around.
-- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. -- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Escort task is finished.
-- @param #number EngagementDistance Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. -- @param #number EngagementDistance Maximal distance from escorted controllable to threat in meters. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax.
-- @param DCS#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. Default {"Air"}. -- @param DCS#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. Default {"Air"}. See https://wiki.hoggit.us/view/DCS_enum_attributes
-- @return DCS#Task The DCS task structure. -- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes )
@ -1504,8 +1543,7 @@ function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, E
-- } -- }
-- } -- }
local DCSTask local DCSTask = {
DCSTask = {
id = 'Escort', id = 'Escort',
params = { params = {
groupId = FollowControllable and FollowControllable:GetID() or nil, groupId = FollowControllable and FollowControllable:GetID() or nil,

View File

@ -32,6 +32,7 @@ Core/Spot.lua
Core/TextAndSound.lua Core/TextAndSound.lua
Core/Condition.lua Core/Condition.lua
Core/Pathline.lua Core/Pathline.lua
Core/ClientMenu.lua
Wrapper/Object.lua Wrapper/Object.lua
Wrapper/Identifiable.lua Wrapper/Identifiable.lua