From a3805118a02ab516aff1004e1ceb8759a89b8e47 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 16 May 2024 09:54:19 +0200 Subject: [PATCH 01/15] #SPAWN - Fix for KeepUnitNames --- Moose Development/Moose/Core/Spawn.lua | 51 +++++++++++++++----------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 12dc0ad4c..75664cee9 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -292,9 +292,10 @@ SPAWN = { --- Enumerator for spawns at airbases -- @type SPAWN.Takeoff --- @extends Wrapper.Group#GROUP.Takeoff - --- @field #SPAWN.Takeoff Takeoff +-- @field #number Air Take off happens in air. +-- @field #number Runway Spawn on runway. Does not work in MP! +-- @field #number Hot Spawn at parking with engines on. +-- @field #number Cold Spawn at parking with engines off. SPAWN.Takeoff = { Air = 1, Runway = 2, @@ -619,12 +620,14 @@ end -- and any spaces before and after the resulting name are removed. -- IMPORTANT! This method MUST be the first used after :New !!! -- @param #SPAWN self --- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided to make new unit names. +-- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided create new unit names. -- @return #SPAWN self function SPAWN:InitKeepUnitNames( KeepUnitNames ) self:F() - self.SpawnInitKeepUnitNames = KeepUnitNames or true + self.SpawnInitKeepUnitNames = false + + if KeepUnitNames == true then self.SpawnInitKeepUnitNames = true end return self end @@ -1609,8 +1612,8 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) numTries = numTries + 1 inZone = SpawnZone:IsVec2InZone(RandomVec2) - self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!") - self:I(SpawnZone) + --self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!") + --self:I(SpawnZone) end end if (not inZone) then @@ -3276,7 +3279,7 @@ end --- Get the index from a given group. -- The function will search the name of the group for a #, and will return the number behind the #-mark. function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) - self:F2( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) + self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 ) local Index = tonumber( IndexString ) @@ -3288,7 +3291,7 @@ end --- Return the last maximum index that can be used. function SPAWN:_GetLastIndex() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) return self.SpawnMaxGroups end @@ -3436,24 +3439,28 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 end if self.SpawnInitKeepUnitNames == false then - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) + for UnitID = 1, #SpawnTemplate.units do + if not string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc + SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) + end SpawnTemplate.units[UnitID].unitId = nil end else for UnitID = 1, #SpawnTemplate.units do - local SpawnInitKeepUnitIFF = false - if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc - SpawnInitKeepUnitIFF = true - end - local UnitPrefix, Rest - if SpawnInitKeepUnitIFF == false then - UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) - self:T( { UnitPrefix, Rest } ) - else - UnitPrefix=SpawnTemplate.units[UnitID].name + local SpawnInitKeepUnitIFF = false + if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc + SpawnInitKeepUnitIFF = true end - SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) + local UnitPrefix, Rest + if SpawnInitKeepUnitIFF == false then + UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) + SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) + self:T( { UnitPrefix, Rest } ) + --else + --UnitPrefix=SpawnTemplate.units[UnitID].name + end + --SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) + SpawnTemplate.units[UnitID].unitId = nil end end From 07a76ced889fd9ecd08ab8477db69e73254ec843 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 16 May 2024 17:56:14 +0200 Subject: [PATCH 02/15] #MANTIS - added an option to do a friendly check in firing range before activitaing a SAM --- Moose Development/Moose/Functional/Mantis.lua | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 997d355cd..a648696f9 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Feb 2024 +-- Last Update: May 2024 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -58,6 +58,7 @@ -- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range -- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius. +-- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range. -- @extends Core.Base#BASE @@ -187,29 +188,34 @@ -- -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when -- -- it is inside any AcceptZone. Then RejectZones are checked, which enforces both borders, but also overlaps of -- -- Accept- and RejectZones. Last, if it is inside a conflict zone, it is accepted. --- `mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones)` +-- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones) -- -- -- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target: -- -- -- parameters are numbers. Defaults are 1,2,2,6 respectively --- `mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic)` +-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic) -- -- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range" -- -- ### 2.1.4 Advanced features -- -- -- switch off auto mode **before** you start MANTIS. --- `mybluemantis.automode = false` +-- mybluemantis.automode = false -- -- -- switch off auto shorad **before** you start MANTIS. --- `mybluemantis.autoshorad = false` +-- mybluemantis.autoshorad = false -- -- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. -- -- also see engagerange below. --- ` self.radiusscale[MANTIS.SamType.LONG] = 1.1` --- ` self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2` --- ` self.radiusscale[MANTIS.SamType.SHORT] = 1.3` +-- self.radiusscale[MANTIS.SamType.LONG] = 1.1 +-- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 +-- self.radiusscale[MANTIS.SamType.SHORT] = 1.3 +-- +-- ### 2.1.5 Friendlies check in firing range +-- +-- -- For some scenarios, like Cold War, it might be useful not to activate SAMs if friendly aircraft are around to avoid death by friendly fire. +-- mybluemantis.checkforfriendlies = true -- -- # 3. Default settings [both modes unless stated otherwise] -- @@ -321,6 +327,7 @@ MANTIS = { automode = true, autoshorad = true, ShoradGroupSet = nil, + checkforfriendlies = false, } --- Advanced state enumerator @@ -1222,10 +1229,10 @@ do function MANTIS:_PreFilterHeight(height) self:T(self.lid.."_PreFilterHeight") local set = {} - local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK + local dlink = self.Detection -- Ops.Intel#INTEL_DLINK local detectedgroups = dlink:GetContactTable() for _,_contact in pairs(detectedgroups) do - local contact = _contact -- Ops.Intelligence#INTEL.Contact + local contact = _contact -- Ops.Intel#INTEL.Contact local grp = contact.group -- Wrapper.Group#GROUP if grp:IsAlive() then if grp:GetHeight(true) < height then @@ -1255,6 +1262,10 @@ do -- DEBUG set = self:_PreFilterHeight(height) end + local friendlyset -- Core.Set#SET_GROUP + if self.checkforfriendlies == true then + friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterOnce() + end for _,_coord in pairs (set) do local coord = _coord -- get current coord to check -- output for cross-check @@ -1279,8 +1290,16 @@ do local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) self:T(self.lid..text) end + -- friendlies around? + local nofriendlies = true + if self.checkforfriendlies == true then + local closestfriend, distance = friendlyset:GetClosestGroup(samcoordinate) + if closestfriend and distance and distance < rad then + nofriendlies = false + end + end -- end output to cross-check - if targetdistance <= rad and zonecheck then + if targetdistance <= rad and zonecheck == true and nofriendlies == true then return true, targetdistance end end @@ -1777,7 +1796,7 @@ do -- @return #MANTIS self function MANTIS:_CheckDLinkState() self:T(self.lid .. "_CheckDLinkState") - local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK + local dlink = self.Detection -- Ops.Intel#INTEL_DLINK local TS = timer.getAbsTime() if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then self.DLink = false From 3a0d2a5c51084901939d05c4e7675bf870f1030a Mon Sep 17 00:00:00 2001 From: kaltokri Date: Sat, 18 May 2024 16:57:28 +0200 Subject: [PATCH 03/15] Added link in Core.Menu to the demo missions. --- Moose Development/Moose/Core/Menu.lua | 368 +++++++++++++------------- 1 file changed, 186 insertions(+), 182 deletions(-) diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index fc9a75394..f47476dc0 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -1,9 +1,9 @@ --- **Core** - Manage hierarchical menu structures and commands for players within a mission. --- +-- -- === --- --- ### Features: --- +-- +-- ## Features: +-- -- * Setup mission sub menus. -- * Setup mission command menus. -- * Setup coalition sub menus. @@ -21,35 +21,39 @@ -- * Update the parameters and the receiving methods, without updating the menu within DCS! -- * Provide a great performance boost in menu management. -- * Provide a great tool to manage menus in your code. --- --- DCS Menus can be managed using the MENU classes. --- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to +-- +-- DCS Menus can be managed using the MENU classes. +-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to -- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing --- menus is not a easy feat if you have complex menu hierarchies defined. +-- menus is not a easy feat if you have complex menu hierarchies defined. -- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. --- On top, MOOSE implements **variable parameter** passing for command menus. --- +-- On top, MOOSE implements **variable parameter** passing for command menus. +-- -- There are basically two different MENU class types that you need to use: --- +-- -- ### To manage **main menus**, the classes begin with **MENU_**: --- +-- -- * @{Core.Menu#MENU_MISSION}: Manages main menus for whole mission file. -- * @{Core.Menu#MENU_COALITION}: Manages main menus for whole coalition. -- * @{Core.Menu#MENU_GROUP}: Manages main menus for GROUPs. --- +-- -- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: --- +-- -- * @{Core.Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. -- * @{Core.Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. -- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- +-- -- === ---- +-- +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Menu) +-- +-- === +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Menu -- @image Core_Menu.JPG @@ -65,18 +69,18 @@ MENU_INDEX.Group = {} function MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or "" - if ParentMenu then + if ParentMenu then if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then local GroupName = ParentMenu.Group:GetName() if not self.Group[GroupName].Menus[Path] then - BASE:E( { Path = Path, GroupName = GroupName } ) + BASE:E( { Path = Path, GroupName = GroupName } ) error( "Parent path not found in menu index for group menu" ) return nil end elseif ParentMenu:IsInstanceOf( "MENU_COALITION" ) or ParentMenu:IsInstanceOf( "MENU_COALITION_COMMAND" ) then local Coalition = ParentMenu.Coalition if not self.Coalition[Coalition].Menus[Path] then - BASE:E( { Path = Path, Coalition = Coalition } ) + BASE:E( { Path = Path, Coalition = Coalition } ) error( "Parent path not found in menu index for coalition menu" ) return nil end @@ -88,7 +92,7 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText ) end end end - + Path = Path .. "@" .. MenuText return Path end @@ -149,19 +153,19 @@ function MENU_INDEX:ClearGroupMenu( Group, Path ) end function MENU_INDEX:Refresh( Group ) for MenuID, Menu in pairs( self.MenuMission.Menus ) do - Menu:Refresh() - end + Menu:Refresh() + end for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do - Menu:Refresh() - end + Menu:Refresh() + end for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do - Menu:Refresh() - end + Menu:Refresh() + end local GroupName = Group:GetName() for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do - Menu:Refresh() - end - + Menu:Refresh() + end + return self end @@ -177,19 +181,19 @@ do -- MENU_BASE MenuText = "", MenuParentPath = nil, } - + --- Constructor -- @param #MENU_BASE -- @return #MENU_BASE function MENU_BASE:New( MenuText, ParentMenu ) - + local MenuParentPath = {} if ParentMenu ~= nil then MenuParentPath = ParentMenu.MenuPath end local self = BASE:Inherit( self, BASE:New() ) - - self.MenuPath = nil + + self.MenuPath = nil self.MenuText = MenuText self.ParentMenu = ParentMenu self.MenuParentPath = MenuParentPath @@ -198,12 +202,12 @@ do -- MENU_BASE self.MenuCount = 0 self.MenuStamp = timer.getTime() self.MenuRemoveParent = false - + if self.ParentMenu then self.ParentMenu.Menus = self.ParentMenu.Menus or {} self.ParentMenu.Menus[MenuText] = self end - + return self end function MENU_BASE:SetParentMenu( MenuText, Menu ) @@ -231,7 +235,7 @@ do -- MENU_BASE self.MenuRemoveParent = RemoveParent return self end - + --- Gets a @{Menu} from a parent @{Menu} -- @param #MENU_BASE self -- @param #string MenuText The text of the child menu. @@ -239,7 +243,7 @@ do -- MENU_BASE function MENU_BASE:GetMenu( MenuText ) return self.Menus[MenuText] end - + --- Sets a menu stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @param MenuStamp @@ -248,16 +252,16 @@ do -- MENU_BASE self.MenuStamp = MenuStamp return self end - - + + --- Gets a menu stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @return MenuStamp function MENU_BASE:GetStamp() return timer.getTime() end - - + + --- Sets a time stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @param MenuStamp @@ -266,7 +270,7 @@ do -- MENU_BASE self.MenuStamp = MenuStamp return self end - + --- Sets a tag for later selection of menu refresh. -- @param #MENU_BASE self -- @param #string MenuTag A Tag or Key that will filter only menu items set with this key. @@ -275,16 +279,16 @@ do -- MENU_BASE self.MenuTag = MenuTag return self end - + end do -- MENU_COMMAND_BASE --- @type MENU_COMMAND_BASE -- @field #function MenuCallHandler -- @extends Core.Menu#MENU_BASE - - --- Defines the main MENU class where other MENU COMMAND_ + + --- Defines the main MENU class where other MENU COMMAND_ -- classes are derived from, in order to set commands. - -- + -- -- @field #MENU_COMMAND_BASE MENU_COMMAND_BASE = { ClassName = "MENU_COMMAND_BASE", @@ -292,12 +296,12 @@ do -- MENU_COMMAND_BASE CommandMenuArgument = nil, MenuCallHandler = nil, } - + --- Constructor -- @param #MENU_COMMAND_BASE -- @return #MENU_COMMAND_BASE function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) - + local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE -- When a menu function goes into error, DCS displays an obscure menu message. -- This error handler catches the menu error and displays the full call stack. @@ -308,20 +312,20 @@ do -- MENU_COMMAND_BASE end return errmsg end - + self:SetCommandMenuFunction( CommandMenuFunction ) self:SetCommandMenuArguments( CommandMenuArguments ) self.MenuCallHandler = function() - local function MenuFunction() + local function MenuFunction() return self.CommandMenuFunction( unpack( self.CommandMenuArguments ) ) end local Status, Result = xpcall( MenuFunction, ErrorHandler ) end - + return self end - - --- This sets the new command function of a menu, + + --- This sets the new command function of a menu, -- so that if a menu is regenerated, or if command function changes, -- that the function set for the menu is loosely coupled with the menu itself!!! -- If the function changes, no new menu needs to be generated if the menu text is the same!!! @@ -331,7 +335,7 @@ do -- MENU_COMMAND_BASE self.CommandMenuFunction = CommandMenuFunction return self end - --- This sets the new command arguments of a menu, + --- This sets the new command arguments of a menu, -- so that if a menu is regenerated, or if command arguments change, -- that the arguments set for the menu are loosely coupled with the menu itself!!! -- If the arguments change, no new menu needs to be generated if the menu text is the same!!! @@ -346,38 +350,38 @@ end do -- MENU_MISSION --- @type MENU_MISSION -- @extends Core.Menu#MENU_BASE - --- Manages the main menus for a complete mission. - -- + --- Manages the main menus for a complete mission. + -- -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- @field #MENU_MISSION MENU_MISSION = { ClassName = "MENU_MISSION", } - + --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. -- @param #MENU_MISSION self -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_MISSION function MENU_MISSION:New( MenuText, ParentMenu ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu then return MissionMenu else local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) MENU_INDEX:SetMissionMenu( Path, self ) - + self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath ) self:SetParentMenu( self.MenuText, self ) return self end - + end - + --- Refreshes a radio item for a mission -- @param #MENU_MISSION self -- @return #MENU_MISSION @@ -388,28 +392,28 @@ do -- MENU_MISSION end return self end - + --- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept! -- @param #MENU_MISSION self -- @return #MENU_MISSION function MENU_MISSION:RemoveSubMenus() - + for MenuID, Menu in pairs( self.Menus or {} ) do Menu:Remove() end - + self.Menus = nil - + end - + --- Removes the main menu and the sub menus recursively of this MENU_MISSION. -- @param #MENU_MISSION self -- @return #nil function MENU_MISSION:Remove( MenuStamp, MenuTag ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu == self then self:RemoveSubMenus() if not MenuStamp or self.MenuStamp ~= MenuStamp then @@ -426,26 +430,26 @@ do -- MENU_MISSION else BASE:E( { "Cannot Remove MENU_MISSION", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText } ) end - + return self end end do -- MENU_MISSION_COMMAND - + --- @type MENU_MISSION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for a complete mission, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for a complete mission, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. - -- + -- -- @field #MENU_MISSION_COMMAND MENU_MISSION_COMMAND = { ClassName = "MENU_MISSION_COMMAND", } - + --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. -- @param #MENU_MISSION_COMMAND self -- @param #string MenuText The text for the menu. @@ -454,10 +458,10 @@ do -- MENU_MISSION_COMMAND -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. -- @return #MENU_MISSION_COMMAND self function MENU_MISSION_COMMAND:New( MenuText, ParentMenu, CommandMenuFunction, ... ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu then MissionMenu:SetCommandMenuFunction( CommandMenuFunction ) MissionMenu:SetCommandMenuArguments( arg ) @@ -465,7 +469,7 @@ do -- MENU_MISSION_COMMAND else local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) MENU_INDEX:SetMissionMenu( Path, self ) - + self.MenuPath = missionCommands.addCommand( MenuText, self.MenuParentPath, self.MenuCallHandler ) self:SetParentMenu( self.MenuText, self ) return self @@ -481,15 +485,15 @@ do -- MENU_MISSION_COMMAND end return self end - + --- Removes a radio command item for a coalition -- @param #MENU_MISSION_COMMAND self -- @return #nil function MENU_MISSION_COMMAND:Remove() - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then @@ -505,19 +509,19 @@ do -- MENU_MISSION_COMMAND else BASE:E( { "Cannot Remove MENU_MISSION_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText } ) end - + return self end end do -- MENU_COALITION --- @type MENU_COALITION -- @extends Core.Menu#MENU_BASE - + --- Manages the main menus for DCS.coalition. - -- + -- -- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. - -- + -- -- -- @usage -- -- This demo creates a menu structure for the planes within the red coalition. @@ -547,7 +551,7 @@ do -- MENU_COALITION -- end -- -- local function AddStatusMenu() - -- + -- -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. -- MenuStatus = MENU_COALITION:New( coalition.side.RED, "Status for Planes" ) -- MenuStatusShow = MENU_COALITION_COMMAND:New( coalition.side.RED, "Show Status", MenuStatus, ShowStatus, "Status of planes is ok!", "Message to Red Coalition" ) @@ -555,12 +559,12 @@ do -- MENU_COALITION -- -- local MenuAdd = MENU_COALITION_COMMAND:New( coalition.side.RED, "Add Status Menu", MenuCoalitionRed, AddStatusMenu ) -- local MenuRemove = MENU_COALITION_COMMAND:New( coalition.side.RED, "Remove Status Menu", MenuCoalitionRed, RemoveStatusMenu ) - -- + -- -- @field #MENU_COALITION MENU_COALITION = { ClassName = "MENU_COALITION" } - + --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. -- @param #MENU_COALITION self -- @param DCS#coalition.side Coalition The coalition owning the menu. @@ -570,15 +574,15 @@ do -- MENU_COALITION function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) MENU_INDEX:PrepareCoalition( Coalition ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) if CoalitionMenu then return CoalitionMenu else local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) MENU_INDEX:SetCoalitionMenu( Coalition, Path, self ) - + self.Coalition = Coalition - + self.MenuPath = missionCommands.addSubMenuForCoalition( Coalition, MenuText, self.MenuParentPath ) self:SetParentMenu( self.MenuText, self ) return self @@ -594,27 +598,27 @@ do -- MENU_COALITION end return self end - + --- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept! -- @param #MENU_COALITION self -- @return #MENU_COALITION function MENU_COALITION:RemoveSubMenus() - + for MenuID, Menu in pairs( self.Menus or {} ) do Menu:Remove() end - + self.Menus = nil end - + --- Removes the main menu and the sub menus recursively of this MENU_COALITION. -- @param #MENU_COALITION self -- @return #nil function MENU_COALITION:Remove( MenuStamp, MenuTag ) - + MENU_INDEX:PrepareCoalition( self.Coalition ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) if CoalitionMenu == self then self:RemoveSubMenus() if not MenuStamp or self.MenuStamp ~= MenuStamp then @@ -631,17 +635,17 @@ do -- MENU_COALITION else BASE:E( { "Cannot Remove MENU_COALITION", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Coalition = self.Coalition } ) end - + return self end end do -- MENU_COALITION_COMMAND - + --- @type MENU_COALITION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. -- @@ -649,7 +653,7 @@ do -- MENU_COALITION_COMMAND MENU_COALITION_COMMAND = { ClassName = "MENU_COALITION_COMMAND" } - + --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. -- @param #MENU_COALITION_COMMAND self -- @param DCS#coalition.side Coalition The coalition owning the menu. @@ -659,19 +663,19 @@ do -- MENU_COALITION_COMMAND -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. -- @return #MENU_COALITION_COMMAND function MENU_COALITION_COMMAND:New( Coalition, MenuText, ParentMenu, CommandMenuFunction, ... ) - + MENU_INDEX:PrepareCoalition( Coalition ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) if CoalitionMenu then CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction ) CoalitionMenu:SetCommandMenuArguments( arg ) return CoalitionMenu else - + local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) MENU_INDEX:SetCoalitionMenu( Coalition, Path, self ) - + self.Coalition = Coalition self.MenuPath = missionCommands.addCommandForCoalition( self.Coalition, MenuText, self.MenuParentPath, self.MenuCallHandler ) self:SetParentMenu( self.MenuText, self ) @@ -686,18 +690,18 @@ do -- MENU_COALITION_COMMAND missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + return self end - + --- Removes a radio command item for a coalition -- @param #MENU_COALITION_COMMAND self -- @return #nil function MENU_COALITION_COMMAND:Remove( MenuStamp, MenuTag ) - + MENU_INDEX:PrepareCoalition( self.Coalition ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) if CoalitionMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then @@ -713,7 +717,7 @@ do -- MENU_COALITION_COMMAND else BASE:E( { "Cannot Remove MENU_COALITION_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Coalition = self.Coalition } ) end - + return self end end @@ -728,20 +732,20 @@ do local _MENUGROUPS = {} --- @type MENU_GROUP -- @extends Core.Menu#MENU_BASE - - - --- Manages the main menus for @{Wrapper.Group}s. - -- + + + --- Manages the main menus for @{Wrapper.Group}s. + -- -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. - -- + -- -- @usage -- -- This demo creates a menu structure for the two groups of planes. -- -- Each group will receive a different menu structure. -- -- To test, join the planes, then look at the other radio menus (Option F10). -- -- Then switch planes and check if the menu is still there. -- -- And play with the Add and Remove menu options. - -- + -- -- -- Note that in multi player, this will only work after the DCS groups bug is solved. -- -- local function ShowStatus( PlaneGroup, StatusText, Coalition ) @@ -789,7 +793,7 @@ do MENU_GROUP = { ClassName = "MENU_GROUP" } - + --- MENU_GROUP constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -797,7 +801,7 @@ do -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP self function MENU_GROUP:New( Group, MenuText, ParentMenu ) - + MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) @@ -809,13 +813,13 @@ do self.Group = Group self.GroupID = Group:GetID() self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath ) - + self:SetParentMenu( self.MenuText, self ) return self end - + end - + --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP self -- @return #MENU_GROUP @@ -823,15 +827,15 @@ do do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Refresh() end end - + return self end - + --- Refreshes a new radio item for a group and submenus, ordering by (numerical) MenuTag -- @param #MENU_GROUP self -- @return #MENU_GROUP @@ -840,7 +844,7 @@ do do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) - + local MenuTable = {} for MenuText, Menu in pairs( self.Menus or {} ) do local tag = Menu.MenuTag or math.random(1,10000) @@ -849,12 +853,12 @@ do table.sort(MenuTable, function (k1, k2) return k1.tag < k2.tag end ) for _, Menu in pairs( MenuTable ) do Menu.Entry:Refresh() - end + end end - + return self end - + --- Removes the sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP self -- @param MenuStamp @@ -864,9 +868,9 @@ do for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end - + self.Menus = nil - + end --- Removes the main menu and sub menus recursively of this MENU_GROUP. @@ -877,7 +881,7 @@ do function MENU_GROUP:Remove( MenuStamp, MenuTag ) MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) if GroupMenu == self then self:RemoveSubMenus( MenuStamp, MenuTag ) if not MenuStamp or self.MenuStamp ~= MenuStamp then @@ -895,15 +899,15 @@ do BASE:E( { "Cannot Remove MENU_GROUP", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) return nil end - + return self end - - + + --- @type MENU_GROUP_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. + + --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. -- @@ -911,7 +915,7 @@ do MENU_GROUP_COMMAND = { ClassName = "MENU_GROUP_COMMAND" } - + --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -923,7 +927,7 @@ do function MENU_GROUP_COMMAND:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... ) MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) if GroupMenu then GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) GroupMenu:SetCommandMenuArguments( arg ) @@ -931,12 +935,12 @@ do else self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) MENU_INDEX:SetGroupMenu( Group, Path, self ) - + self.Group = Group self.GroupID = Group:GetID() - + self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, MenuText, self.MenuParentPath, self.MenuCallHandler ) - + self:SetParentMenu( self.MenuText, self ) return self end @@ -949,10 +953,10 @@ do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + return self end - + --- Removes a menu structure for a group. -- @param #MENU_GROUP_COMMAND self -- @param MenuStamp @@ -961,7 +965,7 @@ do function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag ) MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) if GroupMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then @@ -977,7 +981,7 @@ do else BASE:E( { "Cannot Remove MENU_GROUP_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) end - + return self end end @@ -985,20 +989,20 @@ end do --- @type MENU_GROUP_DELAYED -- @extends Core.Menu#MENU_BASE - - - --- The MENU_GROUP_DELAYED class manages the main menus for groups. + + + --- The MENU_GROUP_DELAYED class manages the main menus for groups. -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. -- The creation of the menu item is delayed however, and must be created using the @{#MENU_GROUP.Set} method. -- This method is most of the time called after the "old" menu items have been removed from the sub menu. - -- + -- -- -- @field #MENU_GROUP_DELAYED MENU_GROUP_DELAYED = { ClassName = "MENU_GROUP_DELAYED" } - + --- MENU_GROUP_DELAYED constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP_DELAYED self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -1006,7 +1010,7 @@ do -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP_DELAYED self function MENU_GROUP_DELAYED:New( Group, MenuText, ParentMenu ) - + MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) @@ -1023,11 +1027,11 @@ do self.MenuPath = {} end table.insert( self.MenuPath, self.MenuText ) - + self:SetParentMenu( self.MenuText, self ) return self end - + end --- Refreshes a new radio item for a group and submenus @@ -1039,7 +1043,7 @@ do missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) self.MenuSet = true end - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Set() end @@ -1053,15 +1057,15 @@ do do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Refresh() end end - + return self end - + --- Removes the sub menus recursively of this MENU_GROUP_DELAYED. -- @param #MENU_GROUP_DELAYED self -- @param MenuStamp @@ -1071,9 +1075,9 @@ do for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end - + self.Menus = nil - + end --- Removes the main menu and sub menus recursively of this MENU_GROUP. @@ -1084,7 +1088,7 @@ do function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag ) MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) if GroupMenu == self then self:RemoveSubMenus( MenuStamp, MenuTag ) if not MenuStamp or self.MenuStamp ~= MenuStamp then @@ -1102,16 +1106,16 @@ do BASE:E( { "Cannot Remove MENU_GROUP_DELAYED", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) return nil end - + return self end - - + + --- @type MENU_GROUP_COMMAND_DELAYED -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_GROUP_COMMAND_DELAYED.New} method, which constructs a MENU_GROUP_COMMAND_DELAYED object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND_DELAYED.Remove}. -- @@ -1119,7 +1123,7 @@ do MENU_GROUP_COMMAND_DELAYED = { ClassName = "MENU_GROUP_COMMAND_DELAYED" } - + --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -1131,7 +1135,7 @@ do function MENU_GROUP_COMMAND_DELAYED:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... ) MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) if GroupMenu then GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) GroupMenu:SetCommandMenuArguments( arg ) @@ -1139,17 +1143,17 @@ do else self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) MENU_INDEX:SetGroupMenu( Group, Path, self ) - + self.Group = Group self.GroupID = Group:GetID() - + if self.MenuParentPath then self.MenuPath = UTILS.DeepCopy( self.MenuParentPath ) else self.MenuPath = {} end table.insert( self.MenuPath, self.MenuText ) - + self:SetParentMenu( self.MenuText, self ) return self end @@ -1165,7 +1169,7 @@ do end end end - + --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @return #MENU_GROUP_COMMAND_DELAYED @@ -1174,10 +1178,10 @@ do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler ) end - + return self end - + --- Removes a menu structure for a group. -- @param #MENU_GROUP_COMMAND_DELAYED self -- @param MenuStamp @@ -1186,7 +1190,7 @@ do function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag ) MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) if GroupMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then @@ -1202,7 +1206,7 @@ do else BASE:E( { "Cannot Remove MENU_GROUP_COMMAND_DELAYED", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) end - + return self end end From 90b588420f7bca79a1ca753ce1e7268a67bc55d2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 19 May 2024 11:31:50 +0200 Subject: [PATCH 04/15] #UTILS - Add Kola map to GMT function #MANTIS - with checkforfriendlies, restrict to airborne ones --- Moose Development/Moose/Functional/Mantis.lua | 4 ++-- Moose Development/Moose/Utilities/Utils.lua | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index a648696f9..4fea096f4 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -638,7 +638,7 @@ do -- TODO Version -- @field #string version - self.version="0.8.16" + self.version="0.8.17" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -1264,7 +1264,7 @@ do end local friendlyset -- Core.Set#SET_GROUP if self.checkforfriendlies == true then - friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterOnce() + friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterOnce() end for _,_coord in pairs (set) do local coord = _coord -- get current coord to check diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 5d53100e9..14c513a40 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1950,7 +1950,9 @@ function UTILS.GMTToLocalTimeDifference() elseif theatre==DCSMAP.Falklands then return -3 -- Fireland is UTC-3 hours. elseif theatre==DCSMAP.Sinai then - return 2 -- Currently map is +2 but should be +3 (DCS bug?) + return 2 -- Currently map is +2 but should be +3 (DCS bug?) + elseif theatre==DCSMAP.Kola then + return 3 -- Currently map is +2 but should be +3 (DCS bug?) else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 From c985d40ca06d97249971a731c54458665e9712ff Mon Sep 17 00:00:00 2001 From: Niels Vaes Date: Sun, 19 May 2024 12:46:17 +0200 Subject: [PATCH 05/15] Fix when DCS adds duplicate points to the points table of a drawing resulting in wrong triangulation (#2128) * Adding a new TerminalType (100)that seems to be introduced in the update that brought Muwaffaq Salti. The base has a couple of spots (like 04, 05, 06) that can only accommodate smaller type fixed wing aircraft, like the F-16, but not bigger types like the Warthog of the Strike Eagle. Because we weren't checking for this new type, spawning in these particular spots always resulted in an airstart, because `_CheckTerminalType` would always return `false` * Adding Shapes over from old MOOSE branch * cleanup * adding HEXtoRGBA * removing Arrow.lua, it's part of Polygon.lua * Fix when DCS adds duplicate points to the `points` table of a drawing, resulting in wrong triangulation --- Moose Development/Moose/Core/Zone.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index d2432703a..19055634b 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -3076,9 +3076,25 @@ function ZONE_POLYGON:NewFromDrawing(DrawingName) -- points for the drawings are saved in local space, so add the object's map x and y coordinates to get -- world space points we can use for _, point in UTILS.spairs(object["points"]) do + -- check if we want to skip adding a point + local skip = false local p = {x = object["mapX"] + point["x"], y = object["mapY"] + point["y"] } - table.add(points, p) + + -- Check if the same coordinates already exist in the list, skip if they do + -- This can happen when drawing a Polygon in Free mode, DCS adds points on + -- top of each other that are in the `mission` file, but not visible in the + -- Mission Editor + for _, pt in pairs(points) do + if pt.x == p.x and pt.y == p.y then + skip = true + end + end + + -- if it's a unique point, add it + if not skip then + table.add(points, p) + end end elseif object["polygonMode"] == "rect" then -- the points for a rect are saved as local coordinates with an angle. To get the world space points from this @@ -3096,6 +3112,7 @@ function ZONE_POLYGON:NewFromDrawing(DrawingName) points = {p1, p2, p3, p4} else + -- bring the Arrow code over from Shape/Polygon -- something else that might be added in the future end end From af39a3ae9ccc3300d3a78fdb3f506a0280294876 Mon Sep 17 00:00:00 2001 From: Mike Young <117502908+DarthZyll@users.noreply.github.com> Date: Tue, 21 May 2024 00:37:05 -0400 Subject: [PATCH 06/15] Update Message.lua (#2130) the Label and port were not being pulled from MSRS Config, causing them to default to "MESSAGE" and 5002 when calling the MESSAGE.SetMSRS() function with no params --- Moose Development/Moose/Core/Message.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 1ffcb1516..f5f278b55 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -509,10 +509,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G end _MESSAGESRS.label = Label or MSRS.Label or "MESSAGE" - _MESSAGESRS.MSRS:SetLabel(Label or "MESSAGE") + _MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label) _MESSAGESRS.port = Port or MSRS.port or 5002 - _MESSAGESRS.MSRS:SetPort(Port or 5002) + _MESSAGESRS.MSRS:SetPort(_MESSAGESRS.port) _MESSAGESRS.volume = Volume or MSRS.volume or 1 _MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume) From 783e29f189a99026da06255a0d90fee1c55ed0a7 Mon Sep 17 00:00:00 2001 From: kaltokri Date: Tue, 21 May 2024 16:50:33 +0200 Subject: [PATCH 07/15] Fixed link in Func.Range to "476 vFG - Air Weapons Range Objects" --- Moose Development/Moose/Functional/Range.lua | 190 +++++++++---------- 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 3019fe29c..3472132b5 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -7,7 +7,7 @@ -- Implementation is based on the [Simple Range Script](https://forums.eagle.ru/showthread.php?t=157991) by Ciribob, which itself was motivated -- by a script by SNAFU [see here](https://forums.eagle.ru/showthread.php?t=109174). -- --- [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is highly recommended for this class. +-- [476th - Air Weapons Range Objects mod](https://www.476vfightergroup.com/downloads.php?do=download&downloadid=482) is highly recommended for this class. -- -- **Main Features:** -- @@ -46,7 +46,7 @@ -- -- ### Contributions: FlightControl, Ciribob -- ### SRS Additions: Applevangelist --- +-- -- === -- @module Functional.Range -- @image Range.JPG @@ -102,7 +102,7 @@ -- @field #string targetpath Path where to save the target sheets. -- @field #string targetprefix File prefix for target sheet files. -- @field Sound.SRS#MSRS controlmsrs SRS wrapper for range controller. --- @field Sound.SRS#MSRSQUEUE controlsrsQ SRS queue for range controller. +-- @field Sound.SRS#MSRSQUEUE controlsrsQ SRS queue for range controller. -- @field Sound.SRS#MSRS instructmsrs SRS wrapper for range instructor. -- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor. -- @field #number Coalition Coalition side for the menu, if any. @@ -169,7 +169,7 @@ -- -- ## Specifying Coordinates -- --- It is also possible to specify coordinates rather than unit or static objects as bombing target locations. This has the advantage, that even when the unit/static object is dead, the specified +-- It is also possible to specify coordinates rather than unit or static objects as bombing target locations. This has the advantage, that even when the unit/static object is dead, the specified -- coordinate will still be a valid impact point. This can be done via the @{#RANGE.AddBombingTargetCoordinate}(*coord*, *name*, *goodhitrange*) function. -- -- # Fine Tuning @@ -231,8 +231,8 @@ -- By default, the sound files are placed in the "Range Soundfiles/" folder inside the mission (.miz) file. Another folder can be specified via the @{#RANGE.SetSoundfilesPath}(*path*) function. -- -- ## Voice output via SRS --- --- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}(). +-- +-- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}(). -- Range control and instructor frequencies and voices can then be set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}(). -- -- # Persistence @@ -243,11 +243,11 @@ -- The next time you start the mission, these results are also automatically loaded. -- -- Strafing results are currently **not** saved. --- +-- -- # FSM Events --- +-- -- This class creates additional events that can be used by mission designers for custom reactions --- +-- -- * `EnterRange` when a player enters a range zone. See @{#RANGE.OnAfterEnterRange} -- * `ExitRange` when a player leaves a range zone. See @{#RANGE.OnAfterExitRange} -- * `Impact` on impact of a player's weapon on a bombing target. See @{#RANGE.OnAfterImpact} @@ -371,7 +371,7 @@ RANGE = { -- @param #number boxlength Length of strafe pit box in meters. -- @param #number boxwidth Width of strafe pit box in meters. -- @param #number goodpass Number of hits for a good strafing pit pass. --- @param #number foulline Distance of foul line in meters. +-- @param #number foulline Distance of foul line in meters. RANGE.Defaults = { goodhitrange = 25, strafemaxalt = 914, @@ -625,9 +625,9 @@ function RANGE:New( RangeName, Coalition ) -- Get range name. -- TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu. self.rangename = RangeName or "Practice Range" - + self.Coalition = Coalition - + -- Log id. self.lid = string.format( "RANGE %s | ", self.rangename ) @@ -993,9 +993,9 @@ end -- @param #string Host Host. Default "127.0.0.1". -- @return #RANGE self function RANGE:SetFunkManOn(Port, Host) - + self.funkmanSocket=SOCKET:New(Port, Host) - + return self end @@ -1200,7 +1200,7 @@ end -- @param #string PathToSRS Path to SRS directory. -- @param #number Port SRS port. Default 5002. -- @param #number Coalition Coalition side, e.g. `coalition.side.BLUE` or `coalition.side.RED`. Default `coalition.side.BLUE`. --- @param #number Frequency Frequency to use. Default is 256 MHz for range control and 305 MHz for instructor. If given, both control and instructor get this frequency. +-- @param #number Frequency Frequency to use. Default is 256 MHz for range control and 305 MHz for instructor. If given, both control and instructor get this frequency. -- @param #number Modulation Modulation to use, defaults to radio.modulation.AM -- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0 -- @param #string PathToGoogleKey Path to Google TTS credentials. @@ -1208,9 +1208,9 @@ end function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey) if PathToSRS or MSRS.path then - + self.useSRS=true - + self.controlmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 256, Modulation or radio.modulation.AM) self.controlmsrs:SetPort(Port or MSRS.port) self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE) @@ -1224,12 +1224,12 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, self.instructmsrs:SetLabel("RANGEI") self.instructmsrs:SetVolume(Volume or 1.0) self.instructsrsQ = MSRSQUEUE:New("INSTRUCT") - - if PathToGoogleKey then + + if PathToGoogleKey then self.controlmsrs:SetGoogle(PathToGoogleKey) self.instructmsrs:SetGoogle(PathToGoogleKey) end - + else self:E(self.lid..string.format("ERROR: No SRS path specified!")) end @@ -1739,9 +1739,9 @@ end -- @param Core.Event#EVENTDATA EventData function RANGE:OnEventBirth( EventData ) self:F( { eventbirth = EventData } ) - + if not EventData.IniPlayerName then return end - + local _unitName = EventData.IniUnitName local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) @@ -1762,7 +1762,7 @@ function RANGE:OnEventBirth( EventData ) -- Reset current strafe status. self.strafeStatus[_uid] = nil - + if self.Coalition then if EventData.IniCoalition == self.Coalition then self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName ) @@ -1771,7 +1771,7 @@ function RANGE:OnEventBirth( EventData ) -- Add Menu commands after a delay of 0.1 seconds. self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName ) end - + -- By default, some bomb impact points and do not flare each hit on target. self.PlayerSettings[_playername] = {} -- #RANGE.PlayerData self.PlayerSettings[_playername].smokebombimpact = self.defaultsmokebomb @@ -1905,21 +1905,21 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV local _distance = nil local _closeCoord = nil --Core.Point#COORDINATE local _hitquality = "POOR" - + -- Get callsign. local _callsign = self:_myname( playerData.unitname ) - + local _playername=playerData.playername - + local _unit=playerData.unit - + -- Coordinate of impact point. local impactcoord = weapon:GetImpactCoordinate() - + -- Check if impact happened in range zone. local insidezone = self.rangezone:IsCoordinateInZone( impactcoord ) - + -- Smoke impact point of bomb. if playerData.smokebombimpact and insidezone then if playerData.delaysmoke then @@ -1928,19 +1928,19 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV impactcoord:Smoke( playerData.smokecolor ) end end - + -- Loop over defined bombing targets. for _, _bombtarget in pairs( self.bombingTargets ) do local bombtarget=_bombtarget --#RANGE.BombTarget - + -- Get target coordinate. local targetcoord = self:_GetBombTargetCoordinate( _bombtarget ) - + if targetcoord then - + -- Distance between bomb and target. local _temp = impactcoord:Get2DDistance( targetcoord ) - + -- Find closest target to last known position of the bomb. if _distance == nil or _temp < _distance then _distance = _temp @@ -1957,21 +1957,21 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV else _hitquality = "POOR" end - + end end end - + -- Count if bomb fell less than ~1 km away from the target. if _distance and _distance <= self.scorebombdistance then -- Init bomb player results. if not self.bombPlayerResults[_playername] then self.bombPlayerResults[_playername] = {} end - + -- Local results. local _results = self.bombPlayerResults[_playername] - + local result = {} -- #RANGE.BombResult result.command=SOCKET.DataType.BOMBRESULT result.name = _closetTarget.name or "unknown" @@ -1993,24 +1993,24 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV result.attackVel = attackVel result.attackAlt = attackAlt result.date=os and os.date() or "n/a" - + -- Add to table. table.insert( _results, result ) - + -- Call impact. self:Impact( result, playerData ) - + elseif insidezone then - + -- Send message. -- DONE SRS message local _message = string.format( "%s, weapon impacted too far from nearest range target (>%.1f km). No score!", _callsign, self.scorebombdistance / 1000 ) if self.useSRS then local ttstext = string.format( "%s, weapon impacted too far from nearest range target, mor than %.1f kilometer. No score!", _callsign, self.scorebombdistance / 1000 ) - self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) + self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) end self:_DisplayMessageToGroup( _unit, _message, nil, false ) - + if self.rangecontrol then -- weapon impacted too far from the nearest target! No Score! if self.useSRS then @@ -2019,11 +2019,11 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV self.rangecontrol:NewTransmission( RANGE.Sound.RCWeaponImpactedTooFar.filename, RANGE.Sound.RCWeaponImpactedTooFar.duration, self.soundpath, nil, nil, _message, self.subduration ) end end - + else self:T( self.lid .. "Weapon impacted outside range zone." ) end - + end --- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). @@ -2036,7 +2036,7 @@ function RANGE:OnEventShot( EventData ) if EventData.Weapon == nil or EventData.IniDCSUnit == nil or EventData.IniPlayerName == nil then return end - + -- Create weapon object. local weapon=WEAPON:New(EventData.weapon) @@ -2048,7 +2048,7 @@ function RANGE:OnEventShot( EventData ) -- Get player unit and name. local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) - + -- Distance Player-to-Range. Set this to larger value than the threshold. local dPR = self.BombtrackThreshold * 2 @@ -2063,16 +2063,16 @@ function RANGE:OnEventShot( EventData ) -- Player data. local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData - + -- Attack parameters. local attackHdg=_unit:GetHeading() local attackAlt=_unit:GetHeight() attackAlt = UTILS.MetersToFeet(attackAlt) - local attackVel=_unit:GetVelocityKNOTS() + local attackVel=_unit:GetVelocityKNOTS() -- Tracking info and init of last bomb position. self:T( self.lid .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, weapon:GetTypeName(), weapon:GetName())) - + -- Set callback function on impact. weapon:SetFuncImpact(RANGE._OnImpact, self, playerData, attackHdg, attackAlt, attackVel) @@ -2144,33 +2144,33 @@ end function RANGE:onafterEnterRange( From, Event, To, player ) if self.instructor and self.rangecontrol then - + if self.useSRS then - - + + local text = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz", self.rangecontrolfreq) local ttstext = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.", self.rangecontrolfreq) - + local group = player.client:GetGroup() - + self.instructsrsQ:NewTransmission(ttstext, nil, self.instructmsrs, nil, 1, {group}, text, 10) - + else - + -- Range control radio frequency split. local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." ) - + -- Radio message that player entered the range - + -- You entered the bombing range. For hit assessment, contact the range controller at xy MHz self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath ) self.instructor:Number2Transmission( RF[1] ) - + if tonumber( RF[2] ) > 0 then self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath ) self.instructor:Number2Transmission( RF[2] ) end - + self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath ) end end @@ -2188,11 +2188,11 @@ function RANGE:onafterExitRange( From, Event, To, player ) if self.instructor then -- You left the bombing range zone. Have a nice day! if self.useSRS then - + local text = "You left the bombing range zone. " - + local r=math.random(5) - + if r==1 then text=text.."Have a nice day!" elseif r==2 then @@ -2202,9 +2202,9 @@ function RANGE:onafterExitRange( From, Event, To, player ) elseif r==4 then text=text.."See you in two weeks!" elseif r==5 then - text=text.."!" + text=text.."!" end - + self.instructsrsQ:NewTransmission(text, nil, self.instructmsrs, nil, 1, {player.client:GetGroup()}, text, 10) else self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath ) @@ -2238,7 +2238,7 @@ function RANGE:onafterImpact( From, Event, To, result, player ) text = text .. string.format( " %s hit.", result.quality ) if self.rangecontrol then - + if self.useSRS then local group = player.client:GetGroup() self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10) @@ -2263,10 +2263,10 @@ function RANGE:onafterImpact( From, Event, To, result, player ) -- Unit. if player.unitname and not self.useSRS then - + -- Get unit. local unit = UNIT:FindByName( player.unitname ) - + -- Send message. self:_DisplayMessageToGroup( unit, text, nil, true ) self:T( self.lid .. text ) @@ -2276,7 +2276,7 @@ function RANGE:onafterImpact( From, Event, To, result, player ) if self.autosave then self:Save() end - + -- Send result to FunkMan, which creates fancy MatLab figures and sends them to Discord via a bot. if self.funkmanSocket then self.funkmanSocket:SendTable(result) @@ -2545,7 +2545,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName ) local _message = string.format( "My Top %d Strafe Pit Results:\n", self.ndisplayresult ) -- Get player results. - local _results = self.strafePlayerResults[_playername] + local _results = self.strafePlayerResults[_playername] -- Create message. if _results == nil then @@ -2851,7 +2851,7 @@ function RANGE:_DisplayRangeInfo( _unitname ) end end text = text .. string.format( "Instructor %.3f MHz (Relay=%s)\n", self.instructorfreq, alive ) - end + end if self.rangecontrol then local alive = "N/A" if self.rangecontrolrelayname then @@ -3079,10 +3079,10 @@ function RANGE:_CheckInZone( _unitName ) local unitheading = 0 -- RangeBoss if _unit and _playername then - + -- Player data. local playerData=self.PlayerSettings[_playername] -- #RANGE.PlayerData - + --- Function to check if unit is in zone and facing in the right direction and is below the max alt. local function checkme( targetheading, _zone ) local zone = _zone -- Core.Zone#ZONE @@ -3096,7 +3096,7 @@ function RANGE:_CheckInZone( _unitName ) if towardspit then local vec3 = _unit:GetVec3() - local vec2 = { x = vec3.x, y = vec3.z } -- DCS#Vec2 + local vec2 = { x = vec3.x, y = vec3.z } -- DCS#Vec2 local landheight = land.getHeight( vec2 ) local unitalt = vec3.y - landheight @@ -3143,7 +3143,7 @@ function RANGE:_CheckInZone( _unitName ) -- Send message. self:_DisplayMessageToGroup( _unit, _msg, nil, true ) - + if self.rangecontrol then if self.useSRS then local group = _unit:GetGroup() @@ -3162,9 +3162,9 @@ function RANGE:_CheckInZone( _unitName ) -- Result. local _result = self.strafeStatus[_unitID] --#RANGE.StrafeStatus - + local _sound = nil -- #RANGE.Soundfile - + -- Calculate accuracy of run. Number of hits wrt number of rounds fired. local shots = _result.ammo - _ammo local accur = 0 @@ -3174,7 +3174,7 @@ function RANGE:_CheckInZone( _unitName ) accur = 100 end end - + -- Results text and sound message. local resulttext="" if _result.pastfoulline == true then -- @@ -3211,7 +3211,7 @@ function RANGE:_CheckInZone( _unitName ) -- Send message. self:_DisplayMessageToGroup( _unit, _text ) - + -- Strafe result. local result = {} -- #RANGE.StrafeResult result.command=SOCKET.DataType.STRAFERESULT @@ -3228,14 +3228,14 @@ function RANGE:_CheckInZone( _unitName ) result.rangename = self.rangename result.airframe=playerData.airframe result.invalid = _result.pastfoulline - + -- Griger Results. self:StrafeResult(playerData, result) - + -- Save trap sheet. if playerData and playerData.targeton and self.targetsheet then self:_SaveTargetSheet( _playername, result ) - end + end -- Voice over. if self.rangecontrol then @@ -3300,7 +3300,7 @@ function RANGE:_CheckInZone( _unitName ) -- Send message. self:_DisplayMessageToGroup( _unit, _msg, 10, true ) - + -- Trigger event that player is rolling in. self:RollingIn(playerData, target) @@ -3436,18 +3436,18 @@ function RANGE:_GetBombTargetCoordinate( target ) local coord = nil -- Core.Point#COORDINATE if target.type == RANGE.TargetType.UNIT then - + -- Check if alive if target.target and target.target:IsAlive() then -- Get current position. coord = target.target:GetCoordinate() -- Save as last known position in case target dies. - target.coordinate=coord + target.coordinate=coord else -- Use stored position. coord = target.coordinate end - + elseif target.type == RANGE.TargetType.STATIC then -- Static targets dont move. @@ -3457,11 +3457,11 @@ function RANGE:_GetBombTargetCoordinate( target ) -- Coordinates dont move. coord = target.coordinate - + elseif target.type == RANGE.TargetType.SCENERY then -- Coordinates dont move. - coord = target.coordinate + coord = target.coordinate else self:E( self.lid .. "ERROR: Unknown target type." ) @@ -3668,7 +3668,7 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display, _to local playermessage = self.PlayerSettings[playername].messages -- Send message to player if messages enabled and not only for the examiner. - + if _gid and (playermessage == true or display) and (not self.examinerexclusive) then if _togroup and _grp then local m = MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp) @@ -4023,9 +4023,9 @@ function RANGE:_GetPlayerUnitAndName( _unitName ) self:F2( _unitName ) if _unitName ~= nil then - + local multiplayer = false - + -- Get DCS unit from its name. local DCSunit = Unit.getByName( _unitName ) @@ -4064,7 +4064,7 @@ function RANGE:_myname( unitname ) if grp and grp:IsAlive() then pname = grp:GetCustomCallSign(true,true) end - end + end return pname end From 6df4fffafdfaaf1d6e3d7c0f83437164622e4d8f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 May 2024 12:58:32 +0200 Subject: [PATCH 08/15] Kola Airbase Names --- Moose Development/Moose/Wrapper/Airbase.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index e0e2d5f21..b2416a51e 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -722,35 +722,35 @@ AIRBASE.Sinai = { --- Airbases of the Kola map -- +-- * AIRBASE.Kola.Banak -- * AIRBASE.Kola.Bas_100 -- * AIRBASE.Kola.Bodo -- * AIRBASE.Kola.Jokkmokk -- * AIRBASE.Kola.Kalixfors -- * AIRBASE.Kola.Kemi_Tornio -- * AIRBASE.Kola.Kiruna --- * AIRBASE.Kola.Lakselv -- * AIRBASE.Kola.Monchegorsk -- * AIRBASE.Kola.Murmansk_International --- * AIRBASE.Kola.Olenegorsk +-- * AIRBASE.Kola.Olenya -- * AIRBASE.Kola.Rovaniemi --- * AIRBASE.Kola.Severomorsk1 --- * AIRBASE.Kola.Severomorsk3 +-- * AIRBASE.Kola.Severomorsk_1 +-- * AIRBASE.Kola.Severomorsk_3 -- -- @field Kola AIRBASE.Kola = { + ["Banak"] = "Banak", ["Bas_100"] = "Bas 100", ["Bodo"] = "Bodo", ["Jokkmokk"] = "Jokkmokk", ["Kalixfors"] = "Kalixfors", ["Kemi_Tornio"] = "Kemi Tornio", ["Kiruna"] = "Kiruna", - ["Lakselv"] = "Lakselv", ["Monchegorsk"] = "Monchegorsk", ["Murmansk_International"] = "Murmansk International", - ["Olenegorsk"] = "Olenegorsk", + ["Olenya"] = "Olenya", ["Rovaniemi"] = "Rovaniemi", - ["Severomorsk1"] = "Severomorsk1", - ["Severomorsk3"] = "Severomorsk3", + ["Severomorsk_1"] = "Severomorsk-1", + ["Severomorsk_3"] = "Severomorsk-3", } --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". From 778ae1b8e5040544dfad9f99f810ee84d0ce91d6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 27 May 2024 17:07:26 +0200 Subject: [PATCH 09/15] #MENU - small fix --- Moose Development/Moose/Core/Menu.lua | 49 +++++++++++++++++---------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index f47476dc0..44c15b08d 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -170,7 +170,8 @@ function MENU_INDEX:Refresh( Group ) end do -- MENU_BASE - --- @type MENU_BASE + --- + -- @type MENU_BASE -- @extends Core.Base#BASE --- Defines the main MENU class where other MENU classes are derived from. -- This is an abstract class, so don't use it. @@ -210,6 +211,7 @@ do -- MENU_BASE return self end + function MENU_BASE:SetParentMenu( MenuText, Menu ) if self.ParentMenu then self.ParentMenu.Menus = self.ParentMenu.Menus or {} @@ -281,8 +283,10 @@ do -- MENU_BASE end end -do -- MENU_COMMAND_BASE - --- @type MENU_COMMAND_BASE +do + --- + -- MENU_COMMAND_BASE + -- @type MENU_COMMAND_BASE -- @field #function MenuCallHandler -- @extends Core.Menu#MENU_BASE @@ -347,8 +351,10 @@ do -- MENU_COMMAND_BASE end end -do -- MENU_MISSION - --- @type MENU_MISSION +do + --- + -- MENU_MISSION + -- @type MENU_MISSION -- @extends Core.Menu#MENU_BASE --- Manages the main menus for a complete mission. -- @@ -513,8 +519,9 @@ do -- MENU_MISSION_COMMAND return self end end -do -- MENU_COALITION - --- @type MENU_COALITION +do + --- MENU_COALITION + -- @type MENU_COALITION -- @extends Core.Menu#MENU_BASE --- Manages the main menus for DCS.coalition. @@ -639,9 +646,10 @@ do -- MENU_COALITION return self end end -do -- MENU_COALITION_COMMAND +do - --- @type MENU_COALITION_COMMAND + --- MENU_COALITION_COMMAND + -- @type MENU_COALITION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. @@ -729,8 +737,11 @@ do -- So every menu for a client created must be tracked so that program logic accidentally does not create. -- the same menus twice during initialization logic. -- These menu classes are handling this logic with this variable. + local _MENUGROUPS = {} - --- @type MENU_GROUP + + --- + -- @type MENU_GROUP -- @extends Core.Menu#MENU_BASE @@ -761,7 +772,7 @@ do -- MenuStatus[MenuGroupName]:Remove() -- end -- - -- --- @param Wrapper.Group#GROUP MenuGroup + -- -- @param Wrapper.Group#GROUP MenuGroup -- local function AddStatusMenu( MenuGroup ) -- local MenuGroupName = MenuGroup:GetName() -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. @@ -902,9 +913,9 @@ do return self end - - - --- @type MENU_GROUP_COMMAND + + --- + -- @type MENU_GROUP_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. @@ -987,7 +998,8 @@ do end --- MENU_GROUP_DELAYED do - --- @type MENU_GROUP_DELAYED + --- + -- @type MENU_GROUP_DELAYED -- @extends Core.Menu#MENU_BASE @@ -1038,6 +1050,7 @@ do -- @param #MENU_GROUP_DELAYED self -- @return #MENU_GROUP_DELAYED function MENU_GROUP_DELAYED:Set() + if not self.GroupID then return end do if not self.MenuSet then missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) @@ -1109,9 +1122,9 @@ do return self end - - - --- @type MENU_GROUP_COMMAND_DELAYED + + --- + -- @type MENU_GROUP_COMMAND_DELAYED -- @extends Core.Menu#MENU_COMMAND_BASE --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. From c87e91d8452c29aab7da4e37ba84241a0d3d6dc4 Mon Sep 17 00:00:00 2001 From: Mike Young <117502908+DarthZyll@users.noreply.github.com> Date: Sat, 1 Jun 2024 01:32:20 -0400 Subject: [PATCH 10/15] Update Set.lua: added handler for EVENTS.PlayerLeaveUnit in SET_GROUP:FilterStart() (#2134) Ops.CSAR was throwing the following errors constantly when a player would leave the CSAR helo: GROUP05000.GetDCSObject((ERROR: Could not get DCS group object of group Archer-1 because DCS object could not be found!)) This was because the SET_GROUP FilterStart on allheligroupset was not handling the scenario when a player left w/o a death. --- Moose Development/Moose/Core/Set.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index ce3a11827..a401b6f3f 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1516,6 +1516,7 @@ do self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnDeadOrCrash ) if self.Filter.Zones then self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self) local timing = self.ZoneTimerInterval or 30 From 333ed629bbe6121b868b36fcf4ab4244de125f27 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 7 Jun 2024 16:03:45 +0200 Subject: [PATCH 11/15] Adding Kiowa support in CSAR und CTLD --- Moose Development/Moose/Ops/CSAR.lua | 3 ++- Moose Development/Moose/Ops/CTLD.lua | 3 ++- Moose Development/Moose/Utilities/Utils.lua | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index e21824e7e..9867523e5 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -292,10 +292,11 @@ CSAR.AircraftType["AH-64D_BLK_II"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2 CSAR.AircraftType["MH-60R"] = 10 CSAR.AircraftType["OH-6A"] = 2 +CSAR.AircraftType["OH-58D"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.23" +CSAR.version="1.0.24" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 507fe0675..da51b1e5a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1250,11 +1250,12 @@ CTLD.UnitTypeCapabilities = { ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, + ["OH-58D"] = {type="OH-58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, } --- CTLD class version. -- @field #string version -CTLD.version="1.0.53" +CTLD.version="1.0.54" --- Instantiate a new CTLD. -- @param #CTLD self diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 14c513a40..0b26f377d 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2249,6 +2249,11 @@ function UTILS.IsLoadingDoorOpen( unit_name ) return true end + if type_name == " OH-58D" and (unit:getDrawArgumentValue(35) > 0 or unit:getDrawArgumentValue(421) == -1) then + BASE:T(unit_name .. " cargo door is open") + return true + end + return false end -- nil From 16dc3860f73927432bcc108d674a9b2cdcf7fc6a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 8 Jun 2024 11:48:27 +0200 Subject: [PATCH 12/15] #MANTIS - fix omission to set own name --- Moose Development/Moose/Functional/Mantis.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 4fea096f4..b6d711964 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -509,7 +509,8 @@ do -- DONE: Treat Awacs separately, since they might be >80km off site -- DONE: Allow tables of prefixes for the setup -- DONE: Auto-Mode with range setups for various known SAM types. - + + self.name = name or "mymantis" self.SAM_Templates_Prefix = samprefix or "Red SAM" self.EWR_Templates_Prefix = ewrprefix or "Red EWR" self.HQ_Template_CC = hq or nil @@ -638,7 +639,7 @@ do -- TODO Version -- @field #string version - self.version="0.8.17" + self.version="0.8.18" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- From 137f0251fb52da7b98afd143fa93bbc857bcc492 Mon Sep 17 00:00:00 2001 From: kaltokri Date: Sun, 9 Jun 2024 10:59:58 +0200 Subject: [PATCH 13/15] Fixed error in documentation of UTILS.LoadFromFile --- Moose Development/Moose/Utilities/Utils.lua | 240 ++++++++++---------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 0b26f377d..9c4b6bd50 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -104,7 +104,7 @@ CALLSIGN={ Shell=3, Navy_One=4, Mauler=5, - Bloodhound=6, + Bloodhound=6, }, -- JTAC JTAC={ @@ -418,7 +418,7 @@ function UTILS._OneLineSerialize(tbl) end end - + tbl_str[#tbl_str + 1] = '}' return table.concat(tbl_str) else @@ -435,7 +435,7 @@ UTILS.BasicSerialize = function(s) if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'userdata') ) then return tostring(s) elseif type(s) == "table" then - return UTILS._OneLineSerialize(s) + return UTILS._OneLineSerialize(s) elseif type(s) == 'string' then s = string.format('(%s)', s) return s @@ -564,15 +564,15 @@ end -- @param #string fname File name. function UTILS.Gdump(fname) if lfs and io then - + local fdir = lfs.writedir() .. [[Logs\]] .. fname - + local f = io.open(fdir, 'w') - + f:write(UTILS.TableShow(_G)) - + f:close() - + env.info(string.format('Wrote debug data to $1', fdir)) else env.error("WARNING: lfs and/or io not de-sanitized - cannot dump _G!") @@ -869,17 +869,17 @@ UTILS.tostringLLM2KData = function( lat, lon, acc) -- degrees, decimal minutes. latMin = UTILS.Round(latMin, acc) lonMin = UTILS.Round(lonMin, acc) - + if latMin == 60 then latMin = 0 latDeg = latDeg + 1 end - + if lonMin == 60 then lonMin = 0 lonDeg = lonDeg + 1 end - + local minFrmtStr -- create the formatting string for the minutes place if acc <= 0 then -- no decimal place. minFrmtStr = '%02d' @@ -887,7 +887,7 @@ UTILS.tostringLLM2KData = function( lat, lon, acc) local width = 3 + acc -- 01.310 - that's a width of 6, for example. minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' end - + -- 024 23'N or 024 23.123'N return latHemi..string.format('%02d:', latDeg) .. string.format(minFrmtStr, latMin), lonHemi..string.format('%02d:', lonDeg) .. string.format(minFrmtStr, lonMin) @@ -899,9 +899,9 @@ UTILS.tostringMGRS = function(MGRS, acc) --R2.1 if acc <= 0 then return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph else - + if acc > 5 then acc = 5 end - + -- Test if Easting/Northing have less than 4 digits. --MGRS.Easting=123 -- should be 00123 --MGRS.Northing=5432 -- should be 05432 @@ -1384,7 +1384,7 @@ end function UTILS.VecDist2D(a, b) local d = math.huge - + if (not a) or (not b) then return d end local c={x=b.x-a.x, y=b.y-a.y} @@ -1400,12 +1400,12 @@ end -- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @return #number Distance between the vectors. function UTILS.VecDist3D(a, b) - - + + local d = math.huge - + if (not a) or (not b) then return d end - + local c={x=b.x-a.x, y=b.y-a.y, z=b.z-a.z} d=math.sqrt(UTILS.VecDot(c, c)) @@ -1801,7 +1801,7 @@ function UTILS.GetCoalitionEnemy(Coalition, Neutral) local Coalitions={} if Coalition then - if Coalition==coalition.side.RED then + if Coalition==coalition.side.RED then Coalitions={coalition.side.BLUE} elseif Coalition==coalition.side.BLUE then Coalitions={coalition.side.RED} @@ -1809,7 +1809,7 @@ function UTILS.GetCoalitionEnemy(Coalition, Neutral) Coalitions={coalition.side.RED, coalition.side.BLUE} end end - + if Neutral then table.insert(Coalitions, coalition.side.NEUTRAL) end @@ -1840,17 +1840,17 @@ end -- @param #number Typename The type name. -- @return #string The Reporting name or "Bogey". function UTILS.GetReportingName(Typename) - + local typename = string.lower(Typename) - + for name, value in pairs(ENUMS.ReportingName.NATO) do local svalue = string.lower(value) if string.find(typename,svalue,1,true) then return name end end - - return "Bogey" + + return "Bogey" end --- Get the callsign name from its enumerator value @@ -1881,49 +1881,49 @@ function UTILS.GetCallsignName(Callsign) return name end end - + for name, value in pairs(CALLSIGN.B1B) do if value==Callsign then return name end end - + for name, value in pairs(CALLSIGN.B52) do if value==Callsign then return name end end - + for name, value in pairs(CALLSIGN.F15E) do if value==Callsign then return name end end - + for name, value in pairs(CALLSIGN.F16) do if value==Callsign then return name end end - + for name, value in pairs(CALLSIGN.F18) do if value==Callsign then return name end end - + for name, value in pairs(CALLSIGN.FARP) do if value==Callsign then return name end end - + for name, value in pairs(CALLSIGN.TransportAircraft) do if value==Callsign then return name end end - + return "Ghostrider" end @@ -1952,7 +1952,7 @@ function UTILS.GMTToLocalTimeDifference() elseif theatre==DCSMAP.Sinai then return 2 -- Currently map is +2 but should be +3 (DCS bug?) elseif theatre==DCSMAP.Kola then - return 3 -- Currently map is +2 but should be +3 (DCS bug?) + return 3 -- Currently map is +2 but should be +3 (DCS bug?) else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 @@ -2157,19 +2157,19 @@ function UTILS.GetRandomTableElement(t, replace) BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") return end - + math.random() math.random() math.random() - + local r=math.random(#t) - + local element=t[r] - + if not replace then table.remove(t, r) end - + return element end @@ -2198,7 +2198,7 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " a side door is open ") return true end - + if string.find(type_name, "SA342" ) and (unit:getDrawArgumentValue(34) == 1) then BASE:T(unit_name .. " front door(s) are open or doors removed") return true @@ -2223,7 +2223,7 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " door is open") return true end - + if type_name == "UH-60L" and (unit:getDrawArgumentValue(401) == 1 or unit:getDrawArgumentValue(402) == 1) then BASE:T(unit_name .. " cargo door is open") return true @@ -2233,27 +2233,27 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " front door(s) are open") return true end - + if type_name == "AH-64D_BLK_II" then BASE:T(unit_name .. " front door(s) are open") return true -- no doors on this one ;) end - + if type_name == "Bronco-OV-10A" then BASE:T(unit_name .. " front door(s) are open") return true -- no doors on this one ;) end - + if type_name == "MH-60R" and (unit:getDrawArgumentValue(403) > 0 or unit:getDrawArgumentValue(403) == -1) then BASE:T(unit_name .. " cargo door is open") return true end - + if type_name == " OH-58D" and (unit:getDrawArgumentValue(35) > 0 or unit:getDrawArgumentValue(421) == -1) then BASE:T(unit_name .. " cargo door is open") return true end - + return false end -- nil @@ -2362,7 +2362,7 @@ function UTILS.GenerateUHFrequencies(Start,End) local FreeUHFFrequencies = {} local _start = 220000000 - + if not Start then while _start < 399000000 do if _start ~= 243000000 then @@ -2373,7 +2373,7 @@ function UTILS.GenerateUHFrequencies(Start,End) else local myend = End*1000000 or 399000000 local mystart = Start*1000000 or 220000000 - + while _start < 399000000 do if _start ~= 243000000 and (_start < mystart or _start > myend) then print(_start) @@ -2381,10 +2381,10 @@ function UTILS.GenerateUHFrequencies(Start,End) end _start = _start + 500000 end - + end - - + + return FreeUHFFrequencies end @@ -2425,7 +2425,7 @@ function UTILS.GenerateLaserCodes() return jtacGeneratedLaserCodes end ---- Ensure the passed object is a table. +--- Ensure the passed object is a table. -- @param #table Object The object that should be a table. -- @param #boolean ReturnNil If `true`, return `#nil` if `Object` is nil. Otherwise an empty table `{}` is returned. -- @return #table The object that now certainly *is* a table. @@ -2437,11 +2437,11 @@ function UTILS.EnsureTable(Object, ReturnNil) end else if ReturnNil then - return nil + return nil else - Object={} + Object={} end - + end return Object @@ -2453,30 +2453,30 @@ end -- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line. -- @return #boolean outcome True if saving is possible, else false. function UTILS.SaveToFile(Path,Filename,Data) - -- Thanks to @FunkyFranky + -- Thanks to @FunkyFranky -- Check io module is available. if not io then BASE:E("ERROR: io not desanitized. Can't save current file.") return false end - + -- Check default path. if Path==nil and not lfs then BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end - + -- Set path or default. local path = nil if lfs then path=Path or lfs.writedir() end - + -- Set file name. local filename=Filename if path~=nil then filename=path.."\\"..filename end - + -- write local f = assert(io.open(filename, "wb")) f:write(Data) @@ -2484,43 +2484,43 @@ function UTILS.SaveToFile(Path,Filename,Data) return true end ---- Function to save an object to a file +--- Function to load an object from a file. -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. -- @return #boolean outcome True if reading is possible and successful, else false. -- @return #table data The data read from the filesystem (table of lines of text). Each line is one single #string! function UTILS.LoadFromFile(Path,Filename) - -- Thanks to @FunkyFranky + -- Thanks to @FunkyFranky -- Check io module is available. if not io then BASE:E("ERROR: io not desanitized. Can't save current state.") return false end - + -- Check default path. if Path==nil and not lfs then BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end - + -- Set path or default. local path = nil if lfs then path=Path or lfs.writedir() end - + -- Set file name. local filename=Filename if path~=nil then filename=path.."\\"..filename end - + -- Check if file exists. local exists=UTILS.CheckFileExists(Path,Filename) if not exists then BASE:I(string.format("ERROR: File %s does not exist!",filename)) return false end - + -- read local file=assert(io.open(filename, "rb")) local loadeddata = {} @@ -2547,30 +2547,30 @@ function UTILS.CheckFileExists(Path,Filename) return false end end - + -- Check io module is available. if not io then BASE:E("ERROR: io not desanitized.") return false end - + -- Check default path. if Path==nil and not lfs then BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end - + -- Set path or default. local path = nil if lfs then path=Path or lfs.writedir() end - + -- Set file name. local filename=Filename if path~=nil then filename=path.."\\"..filename end - + -- Check if file exists. local exists=_fileexists(filename) if not exists then @@ -2607,7 +2607,7 @@ end -- @return #boolean outcome True if saving is successful, else false. -- @usage -- We will go through the list and find the corresponding group and save the current group size (0 when dead). --- These groups are supposed to be put on the map in the ME and have *not* moved (e.g. stationary SAM sites). +-- These groups are supposed to be put on the map in the ME and have *not* moved (e.g. stationary SAM sites). -- Position is still saved for your usage. -- The idea is to reduce the number of units when reloading the data again to restart the saved mission. -- The data will be a simple comma separated list of groupname and size, with one header line. @@ -2646,12 +2646,12 @@ end -- @return #boolean outcome True if saving is successful, else false. -- @usage -- We will go through the set and find the corresponding group and save the current group size and current position. --- The idea is to respawn the groups **spawned during an earlier run of the mission** at the given location and reduce --- the number of units in the group when reloading the data again to restart the saved mission. Note that *dead* groups +-- The idea is to respawn the groups **spawned during an earlier run of the mission** at the given location and reduce +-- the number of units in the group when reloading the data again to restart the saved mission. Note that *dead* groups -- cannot be covered with this. -- **Note** Do NOT use dashes or hashes in group template names (-,#)! -- The data will be a simple comma separated list of groupname and size, with one header line. --- The current task/waypoint/etc cannot be restored. +-- The current task/waypoint/etc cannot be restored. function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured) local filename = Filename or "SetOfGroups" local data = "--Save SET of groups: "..Filename .."\n" @@ -2666,7 +2666,7 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured) end if string.find(template,"#") then template = string.gsub(name,"#(%d+)$","") - end + end local units = group:CountAliveUnits() local position = group:GetVec3() if Structured then @@ -2678,7 +2678,7 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured) data = string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata) else data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) - end + end end end -- save the data @@ -2749,16 +2749,16 @@ end -- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read. -- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density) - + local fires = {} - + local function Smokers(name,coord,effect,density) local eff = math.random(8) if type(effect) == "number" then eff = effect end coord:BigSmokeAndFire(eff,density,name) table.insert(fires,name) end - + local function Cruncher(group,typename,anzahl) local units = group:GetUnits() local reduced = 0 @@ -2776,7 +2776,7 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema end end end - + local reduce = true if Reduce == false then reduce = false end local filename = Filename or "StateListofGroups" @@ -2818,13 +2818,13 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema end local reduce = false if loadednumber < _number then reduce = true end - - --BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce))) - + + --BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce))) + if reduce then - Cruncher(actualgroup,_name,_number-loadednumber) + Cruncher(actualgroup,_name,_number-loadednumber) end - + end else local reduction = actualgroup:CountAliveUnits() - size @@ -2839,7 +2839,7 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema end end table.insert(datatable,data) - end + end else return nil end @@ -2854,11 +2854,11 @@ end -- @param #boolean Cinematic (Optional, needs Structured=true) If true, place a fire/smoke effect on the dead static position. -- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke. -- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5. --- @return Core.Set#SET_GROUP Set of GROUP objects. +-- @return Core.Set#SET_GROUP Set of GROUP objects. -- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate, template=template }` -- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density) - + local fires = {} local usedtemplates = {} local spawn = true @@ -2866,14 +2866,14 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D local filename = Filename or "SetOfGroups" local setdata = SET_GROUP:New() local datatable = {} - + local function Smokers(name,coord,effect,density) local eff = math.random(8) if type(effect) == "number" then eff = effect end coord:BigSmokeAndFire(eff,density,name) table.insert(fires,name) end - + local function Cruncher(group,typename,anzahl) local units = group:GetUnits() local reduced = 0 @@ -2891,7 +2891,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D end end end - + local function PostSpawn(args) local spwndgrp = args[1] local size = args[2] @@ -2901,16 +2901,16 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D local actualsize = spwndgrp:CountAliveUnits() if actualsize > size then if Structured and structure then - + local loadedstructure = {} local strcset = UTILS.Split(structure,";") for _,_data in pairs(strcset) do local datasplit = UTILS.Split(_data,"==") loadedstructure[datasplit[1]] = tonumber(datasplit[2]) end - + local originalstructure = UTILS.GetCountPerTypeName(spwndgrp) - + for _name,_number in pairs(originalstructure) do local loadednumber = 0 if loadedstructure[_name] then @@ -2918,11 +2918,11 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D end local reduce = false if loadednumber < _number then reduce = true end - + if reduce then - Cruncher(spwndgrp,_name,_number-loadednumber) + Cruncher(spwndgrp,_name,_number-loadednumber) end - + end else local reduction = actualsize-size @@ -2935,16 +2935,16 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D end end end - + local function MultiUse(Data) - local template = Data.template + local template = Data.template if template and usedtemplates[template] and usedtemplates[template].used and usedtemplates[template].used > 1 then -- multispawn if not usedtemplates[template].done then local spwnd = 0 local spawngrp = SPAWN:New(template) spawngrp:InitLimit(0,usedtemplates[template].used) - for _,_entry in pairs(usedtemplates[template].data) do + for _,_entry in pairs(usedtemplates[template].data) do spwnd = spwnd + 1 local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd) BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) @@ -2956,7 +2956,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D return false end end - + --BASE:I("Spawn = "..tostring(spawn)) if UTILS.CheckFileExists(Path,filename) then local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) @@ -2990,13 +2990,13 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D end end end - for _id,_entry in pairs (datatable) do + for _id,_entry in pairs (datatable) do if spawn and not MultiUse(_entry) and _entry.size > 0 then local group = SPAWN:New(_entry.template) local sgrp=group:SpawnFromCoordinate(_entry.coordinate) BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) end - end + end else return nil end @@ -3025,7 +3025,7 @@ function UTILS.LoadSetOfStatics(Path,Filename) if StaticObject then datatable:AddObject(StaticObject) end - end + end else return nil end @@ -3041,7 +3041,7 @@ end -- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke. -- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5. -- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. Dead objects will have coordinate points `{x=0,y=0,z=0}` --- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` +-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` -- Returns nil when file cannot be read. function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density) local fires = {} @@ -3077,7 +3077,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,E if Cinematic then local effect = math.random(8) if type(Effect) == "number" then - effect = Effect + effect = Effect end coord:BigSmokeAndFire(effect,Density,staticname) table.insert(fires,staticname) @@ -3087,7 +3087,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,E end end end - end + end else return nil end @@ -3135,10 +3135,10 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp) if aspect == "" then BRAANATO = string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing, rangeNM, alt, track) else - BRAANATO = string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords, bearing, rangeNM, alt, aspect, track) + BRAANATO = string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords, bearing, rangeNM, alt, aspect, track) end end - return BRAANATO + return BRAANATO end --- Check if an object is contained in a table. @@ -3183,7 +3183,7 @@ function UTILS.IsAnyInTable(Table, Objects, Key) end end end - + end return false @@ -3199,30 +3199,30 @@ end -- @param #table Color Color of the line in RGB, e.g. {1,0,0} for red -- @param #number Alpha Transparency factor, between 0.1 and 1 -- @param #number LineType Line type to be used, line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. --- @param #boolean ReadOnly +-- @param #boolean ReadOnly function UTILS.PlotRacetrack(Coordinate, Altitude, Speed, Heading, Leg, Coalition, Color, Alpha, LineType, ReadOnly) local fix_coordinate = Coordinate local altitude = Altitude local speed = Speed or 350 local heading = Heading or 270 local leg_distance = Leg or 10 - + local coalition = Coalition or -1 local color = Color or {1,0,0} local alpha = Alpha or 1 local lineType = LineType or 1 - - + + speed = UTILS.IasToTas(speed, UTILS.FeetToMeters(altitude), oatcorr) - + local turn_radius = 0.0211 * speed -3.01 - + local point_two = fix_coordinate:Translate(UTILS.NMToMeters(leg_distance), heading, true, false) local point_three = point_two:Translate(UTILS.NMToMeters(turn_radius)*2, heading - 90, true, false) local point_four = fix_coordinate:Translate(UTILS.NMToMeters(turn_radius)*2, heading - 90, true, false) local circle_center_fix_four = point_two:Translate(UTILS.NMToMeters(turn_radius), heading - 90, true, false) local circle_center_two_three = fix_coordinate:Translate(UTILS.NMToMeters(turn_radius), heading - 90, true, false) - + fix_coordinate:LineToAll(point_two, coalition, color, alpha, lineType) point_four:LineToAll(point_three, coalition, color, alpha, lineType) @@ -3935,7 +3935,7 @@ function UTILS.MGRSStringToSRSFriendly(Text,Slow) Text = string.gsub(Text,"9","niner") if Slow then Text = ''..Text..'' - end + end Text = "MGRS;"..Text return Text end From ceb77e283762923ad2aa0b67d54d8320a76543cb Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 9 Jun 2024 17:58:57 +0200 Subject: [PATCH 14/15] Update Event.lua - Fixed isExist function does not exist for Kiowa Hellfire as reported by Special K --- Moose Development/Moose/Core/Event.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 5908c032b..78b69202a 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1301,7 +1301,7 @@ function EVENT:onEvent( Event ) -- STATIC --- Event.TgtDCSUnit = Event.target - if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object + if Event.target.isExist and Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object, check that isExist exists (Kiowa Hellfire issue, Special K) Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() -- Workaround for borked target info on cruise missiles if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then From 4668132b37147cb37b15f6a6bb522078e56b5076 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Jun 2024 18:31:59 +0200 Subject: [PATCH 15/15] #GROUP small fix --- Moose Development/Moose/Wrapper/Group.lua | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 96ac5d87f..4f214c7bf 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1207,15 +1207,17 @@ function GROUP:GetCoordinate() -- no luck, try the API way local DCSGroup = Group.getByName(self.GroupName) - local DCSUnits = DCSGroup:getUnits() or {} - for _,_unit in pairs(DCSUnits) do - if Object.isExist(_unit) then - local position = _unit:getPosition() - local point = position.p ~= nil and position.p or _unit:GetPoint() - if point then - --self:I(point) - local coord = COORDINATE:NewFromVec3(point) - return coord + if DCSGroup then + local DCSUnits = DCSGroup:getUnits() or {} + for _,_unit in pairs(DCSUnits) do + if Object.isExist(_unit) then + local position = _unit:getPosition() + local point = position.p ~= nil and position.p or _unit:GetPoint() + if point then + --self:I(point) + local coord = COORDINATE:NewFromVec3(point) + return coord + end end end end