From 4553acc7f4f017bb65e4f580522fefa0dc67c008 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Aug 2026 12:37:02 +0200 Subject: [PATCH 01/14] #AIRWING - add parameter for CAP grouping #EASYGCICAP - make CAP points honor grouping --- Moose Development/Moose/Ops/AirWing.lua | 12 +++++++++--- Moose Development/Moose/Ops/EasyGCICAP.lua | 7 ++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 22efde9ff..0c54918eb 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -189,13 +189,14 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.9.7" +AIRWING.version="0.9.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Check that airbase has enough parking spots if a request is BIG. +-- DONE: Allow groupping parameter for CAP flights -- DONE: Allow (moving) zones as base for patrol points. -- DONE: Spawn in air ==> Needs WAREHOUSE update. -- DONE: Spawn hot. @@ -233,6 +234,7 @@ function AIRWING:New(warehousename, airwingname) -- Defaults: self.nflightsCAP=0 + self.nCAPgrouping=1 self.nflightsAWACS=0 self.nflightsRecon=0 self.nflightsTANKERboom=0 @@ -740,9 +742,11 @@ end --- Set number of CAP flights constantly carried out. -- @param #AIRWING self -- @param #number n (Optional) Number of flights. Default 1. +-- @param #number grouping (optional) Number of assets per flight. Default 1. -- @return #AIRWING self -function AIRWING:SetNumberCAP(n) +function AIRWING:SetNumberCAP(n,grouping) self.nflightsCAP=n or 1 + self.nCAPgrouping = grouping or 1 return self end @@ -1270,11 +1274,13 @@ function AIRWING:CheckCAP() if self.capOptionPatrolRaceTrack then missionCAP=AUFTRAG:NewPATROL_RACETRACK(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg, self.capFormation) + missionCAP:SetRequiredAssets(self.nCAPgrouping,self.nCAPgrouping) else missionCAP=AUFTRAG:NewGCICAP(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg) - + missionCAP:SetRequiredAssets(self.nCAPgrouping,self.nCAPgrouping) + end if self.capOptionVaryStartTime then diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index d1d499ddb..d26aa6f43 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -288,7 +288,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.38" +EASYGCICAP.version="0.1.39" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- @@ -796,8 +796,9 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) CAP_Wing:SetRespawnAfterDestroyed() --- #DONE avoid wings with no CAP points starting CAP anyhow; AirWing uses this to start CAP and creates points when there are none. - if counttable(self.ManagedCP) >0 then - CAP_Wing:SetNumberCAP(self.capgrouping) + local nCapPoints = counttable(self.ManagedCP) + if nCapPoints >0 then + CAP_Wing:SetNumberCAP(nCapPoints,self.capgrouping) end CAP_Wing:SetCapCloseRaceTrack(true) From e64e4ae2ae4586c5bb409a8686ad82421e32de37 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Aug 2026 12:38:15 +0200 Subject: [PATCH 02/14] #EVENT - no output on useless event 61 --- Moose Development/Moose/Core/Event.lua | 36 +++++++------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 71270e026..e0c647f2f 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -278,7 +278,6 @@ EVENTS = { SimulationUnfreeze = world.event.S_EVENT_SIMULATION_UNFREEZE or -1, HumanAircraftRepairStart = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or -1, HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1, - GroupChangeOption = world.event.S_EVENT_GROUP_CHANGE_OPTION or -1, -- dynamic cargo NewDynamicCargo = world.event.S_EVENT_NEW_DYNAMIC_CARGO or -1, DynamicCargoLoaded = world.event.S_EVENT_DYNAMIC_CARGO_LOADED or -1, @@ -292,25 +291,23 @@ EVENTS = { -- -- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event. -- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event. --- * A GROUP : A DCS Group object is involved in the Event. -- -- @type EVENTDATA -- @field #number id The identifier of the event. -- --- @field DCS#Unit initiator (UNIT/STATIC/SCENERY/GROUP) The initiating @{DCS#Unit} or @{DCS#StaticObject}. For `S_EVENT_GROUP_CHANGE_OPTION`, this is a DCS Group. +-- @field DCS#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{DCS#Unit} or @{DCS#StaticObject}. -- @field DCS#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ). -- @field DCS#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCS#Unit} or @{DCS#StaticObject}. -- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name. -- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Wrapper.Unit#UNIT} of the initiator Unit object. -- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName). --- @field DCS#Group IniDCSGroup (UNIT/GROUP) The initiating @{DCS#Group}. --- @field #string IniDCSGroupName (UNIT/GROUP) The initiating Group name. --- @field Wrapper.Group#GROUP IniGroup (UNIT/GROUP) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object. --- @field #string IniGroupName (UNIT/GROUP) The initiating GROUP name (same as IniDCSGroupName). --- @field DCS#Group.Category IniGroupCategory (GROUP) The category of the initiating group. +-- @field DCS#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}. +-- @field #string IniDCSGroupName (UNIT) The initiating Group name. +-- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object. +-- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName). -- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot. -- @field #string IniPlayerUCID (UNIT) The UCID of the initiating player in case the Unit is a client or player slot and on a multi-player server. --- @field DCS#coalition.side IniCoalition (UNIT/GROUP) The coalition of the initiator. +-- @field DCS#coalition.side IniCoalition (UNIT) The coalition of the initiator. -- @field DCS#Unit.Category IniCategory (UNIT) The category of the initiator. -- @field #string IniTypeName (UNIT) The type name of the initiator. -- @@ -737,12 +734,6 @@ local _EVENTMETA = { Side = "I", Event = "OnEventHumanAircraftRepairFinish", Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH" - }, - [EVENTS.GroupChangeOption] = { - Order = 1, - Side = "I", - Event = "OnEventGroupChangeOption", - Text = "S_EVENT_GROUP_CHANGE_OPTION" }, -- dynamic cargo [EVENTS.NewDynamicCargo] = { @@ -1243,16 +1234,7 @@ function EVENT:onEvent( Event ) if Event.initiator then - if Event.id == EVENTS.GroupChangeOption then - Event.IniDCSGroup = Event.initiator - Event.IniDCSGroupName = Group.getName(Event.initiator) - Event.IniGroupName = Event.IniDCSGroupName - Event.IniGroup = GROUP:FindByName(Event.IniDCSGroupName) - Event.IniCoalition = Group.getCoalition(Event.initiator) - Event.IniGroupCategory = Group.getCategory(Event.initiator) - else - Event.IniObjectCategory = Object.getCategory(Event.initiator) - end + Event.IniObjectCategory = Object.getCategory(Event.initiator) if Event.IniObjectCategory == Object.Category.STATIC then --- @@ -1681,7 +1663,9 @@ function EVENT:onEvent( Event ) self:T( { EventMeta.Text, Event } ) end else - self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?", tostring(Event.id))) + if Event.id ~= 61 then --- TODO Event 61 is new, but seems to have no real data to be useable, something like option changed. + self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?", tostring(Event.id))) + end end Event = nil From 490c798848e5991c5d3e4b1ab445f5de9cda2eab Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 3 Aug 2026 17:22:39 +0200 Subject: [PATCH 03/14] #AIRBOSS - prevent too early LSO calls --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 304233b81..dedb4725c 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -10410,7 +10410,7 @@ function AIRBOSS:_Groove( playerData ) -- Wait until player passed the 0.75 NM distance. local _advice = true - if playerData.TIG0 == nil and playerData.difficulty ~= AIRBOSS.Difficulty.EASY then -- rho>RXX + if (playerData.TIG0 == nil or playerData.TIG0 == 0) and playerData.difficulty ~= AIRBOSS.Difficulty.EASY then -- rho>RXX _advice = false end From 8bc5c3e56848ebe9842a093145d4391d0eb70531 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:24:38 +0200 Subject: [PATCH 04/14] Update Scenery.lua #SCENERY - return nil on register when no underlying object found --- Moose Development/Moose/Wrapper/Scenery.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index ea1f8f8f0..b9fa735a4 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -61,9 +61,19 @@ function SCENERY:Register( SceneryName, SceneryObject, SceneryZone ) end if _SCENERY[ID] then return _SCENERY[ID] end + + if SceneryObject == nil and SceneryZone == nil then return nil end -- no object to attach to local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) ) - + + if SceneryObject == nil and SceneryZone ~= nil then + SceneryObject = self:FindByZoneName(SceneryZone) + if SceneryObject==nil then + self=nil + return nil + end + end + self.SceneryName = tostring(SceneryName) self.ID = ID self.SceneryObject = SceneryObject From 51e12c8abac6c2f92bc24a7349e4943a9a266a33 Mon Sep 17 00:00:00 2001 From: leka1986 Date: Tue, 11 Aug 2026 17:56:27 +0200 Subject: [PATCH 05/14] Fix missing scenery lookup --- Moose Development/Moose/Wrapper/Scenery.lua | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index b9fa735a4..b1f4a7e9d 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -61,19 +61,9 @@ function SCENERY:Register( SceneryName, SceneryObject, SceneryZone ) end if _SCENERY[ID] then return _SCENERY[ID] end - - if SceneryObject == nil and SceneryZone == nil then return nil end -- no object to attach to local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) ) - - if SceneryObject == nil and SceneryZone ~= nil then - SceneryObject = self:FindByZoneName(SceneryZone) - if SceneryObject==nil then - self=nil - return nil - end - end - + self.SceneryName = tostring(SceneryName) self.ID = ID self.SceneryObject = SceneryObject @@ -297,7 +287,7 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role, Zone) --BASE:I("Coordinate x = "..Coordinate.x .. " y = "..Coordinate.y.." z = "..Coordinate.z) local findme = self:_FindByName(Name) - if findme then return findme end + if findme and findme:GetDCSObject() then return findme end local radius = Radius or 100 local name = Name or "unknown" @@ -331,8 +321,6 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role, Zone) if Coordinate then scenery = SceneryScan(Coordinate, radius, name) end - - if not scenery then scenery = SCENERY:Register(Name,nil,Zone) end return scenery end From b6087bc455255cd7e35a3cc13ccfc937048d1407 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 12 Aug 2026 21:17:53 +0200 Subject: [PATCH 06/14] Update RAT.lua --- Moose Development/Moose/Functional/RAT.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 93acb9615..fe1d2b81e 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -4068,8 +4068,8 @@ end -- @param #RAT self -- @param Core.Event#EVENTDATA EventData function RAT:_OnBirth(EventData) - self:F3(EventData) - self:T3(self.lid.."Captured event birth!") + self:F2(EventData) + self:T2(self.lid.."Captured event birth!") local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP From 42d1cdfcd7e15d84d4619760dd2a43358e1ce6c2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 23 Aug 2026 14:34:35 +0200 Subject: [PATCH 07/14] #CONTROLLABLE - CONTROLLABLE now caches Options set on the object wit SetOption() like Alarm state etc to avoid EVENT 61 (option change) spamming; HINT: this requires you to consistently change options only with Moose and NOT in your own scripts by e.g. getting the Controller Object and using the API directly.Mix'n' match will fail. As a helper you can also use `QueryCachedOption(OptionID)` which will return cached values, if set by Moose. --- .../Moose/Wrapper/Controllable.lua | 129 +++++++++++------- 1 file changed, 80 insertions(+), 49 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 43c96254a..27ebc28a4 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -14,6 +14,7 @@ --- @type CONTROLLABLE -- @field DCS#Controllable DCSControllable The DCS controllable class. -- @field #string ControllableName The name of the controllable. +-- @field #table ControllableOptions The Cache of controllable options. -- @extends Wrapper.Positionable#POSITIONABLE --- Wrapper class to handle the "DCS Controllable objects", which are Groups and Units: @@ -203,7 +204,8 @@ function CONTROLLABLE:New( ControllableName ) local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) -- #CONTROLLABLE -- self:F( ControllableName ) self.ControllableName = ControllableName - + -- Options Cache for Event ID 61 (ED DCS Release Aug 2026) + self.ControllableOptions = {} self.TaskScheduler = SCHEDULER:New( self ) return self end @@ -3281,16 +3283,45 @@ end -- @param #number OptionValue Value of the option -- @return #CONTROLLABLE self function CONTROLLABLE:SetOption( OptionID, OptionValue ) + + local setnewoption = false - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - Controller:setOption( OptionID, OptionValue ) - - return self + -- Check if option has changed against cached option + local ID = tostring(OptionID) + if self.ControllableOptions then + if (self.ControllableOptions[ID] ~= OptionValue) or (self.ControllableOptions[ID]==nil) then + setnewoption = true + self.ControllableOptions[ID] = OptionValue + --self:I(string.format("CONTROLLABLE %s: Option CHANGE for option %d: New value: %s!",self.ControllableName, OptionID, tostring(OptionValue))) + end end + + -- change option if changed + if setnewoption == true then + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + --self:I(string.format("CONTROLLABLE %s: Setting OPTION %d: to value: %s!",self.ControllableName, OptionID, tostring(OptionValue))) + Controller:setOption( OptionID, OptionValue ) + return self + end + --else + --self:I(string.format("CONTROLLABLE %s: Option NO CHANGE for option %d: Same value: %s!",self.ControllableName, OptionID, tostring(OptionValue))) + end + + return nil +end + +--- Query a (cached) option. Requires the option has been set with Moose(!) before. +-- @param #CONTROLLABLE self +-- @param #number OptionID ID/Type of the option. +-- @return #number OptionValue or nil if not chached (yet) +function CONTROLLABLE:QueryCachedOption(OptionID) + local ID = tostring(OptionID) + if self.ControllableOptions then + return self.ControllableOptions[ID] + end return nil end @@ -3307,11 +3338,11 @@ function CONTROLLABLE:OptionROE( ROEvalue ) local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, ROEvalue ) + self:SetOption( AI.Option.Air.id.ROE, ROEvalue ) elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, ROEvalue ) + self:SetOption( AI.Option.Ground.id.ROE, ROEvalue ) elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, ROEvalue ) + self:SetOption( AI.Option.Naval.id.ROE, ROEvalue ) end return self @@ -3349,11 +3380,11 @@ function CONTROLLABLE:OptionROEHoldFire() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) + self:SetOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) + self:SetOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) + self:SetOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) end return self @@ -3391,11 +3422,11 @@ function CONTROLLABLE:OptionROEReturnFire() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) + self:SetOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) + self:SetOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) + self:SetOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) end return self @@ -3433,11 +3464,11 @@ function CONTROLLABLE:OptionROEOpenFire() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) + self:SetOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) + self:SetOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) + self:SetOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) end return self @@ -3476,7 +3507,7 @@ function CONTROLLABLE:OptionROEOpenFireWeaponFree() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE ) + self:SetOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE ) end return self @@ -3514,7 +3545,7 @@ function CONTROLLABLE:OptionROEWeaponFree() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) + self:SetOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) end return self @@ -3552,7 +3583,7 @@ function CONTROLLABLE:OptionROTNoReaction() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) + self:SetOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) end return self @@ -3573,7 +3604,7 @@ function CONTROLLABLE:OptionROT( ROTvalue ) local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, ROTvalue ) + self:SetOption( AI.Option.Air.id.REACTION_ON_THREAT, ROTvalue ) end return self @@ -3611,7 +3642,7 @@ function CONTROLLABLE:OptionROTPassiveDefense() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) + self:SetOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) end return self @@ -3631,7 +3662,7 @@ function CONTROLLABLE:OptionPreferVerticalLanding() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.PREFER_VERTICAL, true ) + self:SetOption( AI.Option.Air.id.PREFER_VERTICAL, true ) end return self @@ -3651,7 +3682,7 @@ function CONTROLLABLE:OptionAllowFormationSideSwap() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ALLOW_FORMATION_SIDE_SWAP, true ) + self:SetOption( AI.Option.Air.id.ALLOW_FORMATION_SIDE_SWAP, true ) end return self @@ -3671,7 +3702,7 @@ function CONTROLLABLE:OptionAIRunwayLineUp() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( 37, true ) + self:SetOption( 37, true ) end return self @@ -3691,7 +3722,7 @@ function CONTROLLABLE:OptionDisengageAndRTBAfterFormationLoss() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( 38, 1 ) + self:SetOption( 38, 1 ) end return self @@ -3729,7 +3760,7 @@ function CONTROLLABLE:OptionROTEvadeFire() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) + self:SetOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) end return self @@ -3767,7 +3798,7 @@ function CONTROLLABLE:OptionROTVertical() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) + self:SetOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) end return self @@ -3787,10 +3818,10 @@ function CONTROLLABLE:OptionAlarmStateAuto() local Controller = self:_GetController() if self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO ) + self:SetOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO ) elseif self:IsShip() then - -- Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) - Controller:setOption( 9, 0 ) + -- self:SetOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) + self:SetOption( 9, 0 ) end return self @@ -3810,11 +3841,11 @@ function CONTROLLABLE:OptionAlarmStateGreen() local Controller = self:_GetController() if self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) + self:SetOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) elseif self:IsShip() then -- AI.Option.Naval.id.ALARM_STATE does not seem to exist! - -- Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) - Controller:setOption( 9, 1 ) + -- self:SetOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) + self:SetOption( 9, 1 ) end return self @@ -3834,10 +3865,10 @@ function CONTROLLABLE:OptionAlarmStateRed() local Controller = self:_GetController() if self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED ) + self:SetOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED ) elseif self:IsShip() then - -- Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) - Controller:setOption( 9, 2 ) + -- self:SetOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) + self:SetOption( 9, 2 ) end return self @@ -3864,7 +3895,7 @@ function CONTROLLABLE:OptionRTBBingoFuel( RTB ) -- R2.2 local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.RTB_ON_BINGO, RTB ) + self:SetOption( AI.Option.Air.id.RTB_ON_BINGO, RTB ) end return self @@ -3885,7 +3916,7 @@ function CONTROLLABLE:OptionRTBAmmo( WeaponsFlag ) local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.RTB_ON_OUT_OF_AMMO, WeaponsFlag ) + self:SetOption( AI.Option.Air.id.RTB_ON_OUT_OF_AMMO, WeaponsFlag ) end return self @@ -3905,7 +3936,7 @@ function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.PROHIBIT_JETT, false ) + self:SetOption( AI.Option.Air.id.PROHIBIT_JETT, false ) end return self @@ -3925,7 +3956,7 @@ function CONTROLLABLE:OptionKeepWeaponsOnThreat() local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.PROHIBIT_JETT, true ) + self:SetOption( AI.Option.Air.id.PROHIBIT_JETT, true ) end return self @@ -3965,7 +3996,7 @@ function CONTROLLABLE:OptionEvasionOfARM(Seconds) if self:IsGround() then if Seconds == nil then Seconds = false end - Controller:setOption( AI.Option.Ground.id.EVASION_OF_ARM, Seconds) + self:SetOption( AI.Option.Ground.id.EVASION_OF_ARM, Seconds) end end @@ -3986,7 +4017,7 @@ function CONTROLLABLE:OptionFormationInterval(meters) if self:IsGround() then if meters == nil or meters > 100 or meters < 0 then meters = 50 end - Controller:setOption( 30, meters) + self:SetOption( 30, meters) end end @@ -4006,7 +4037,7 @@ function CONTROLLABLE:OptionECM( ECMvalue ) local Controller = self:_GetController() if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ECM_USING, ECMvalue or 1 ) + self:SetOption( AI.Option.Air.id.ECM_USING, ECMvalue or 1 ) end end @@ -4192,11 +4223,11 @@ function CONTROLLABLE:OptionRestrictBurner( RestrictBurner ) -- Issue https://github.com/FlightControl-Master/MOOSE/issues/1216 if RestrictBurner == true then if self:IsAir() then - Controller:setOption( 16, true ) + self:SetOption( 16, true ) end else if self:IsAir() then - Controller:setOption( 16, false ) + self:SetOption( 16, false ) end end From 4849acbb327471bf4277ae5524c2d96ea89d4b93 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 23 Aug 2026 17:38:02 +0200 Subject: [PATCH 08/14] #GROUP - Respawn() to reset controller options table --- Moose Development/Moose/Wrapper/Group.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 21101fc00..875c3e0c6 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2271,6 +2271,10 @@ function GROUP:Respawn( Template, Reset ) -- Reset events. self:ResetEvents() + + -- Reset options + self.ControllableOptions = nil + self.ControllableOptions = {} return self end From e7d75ecc4021f89464f46158d9fb50883c463c39 Mon Sep 17 00:00:00 2001 From: leka1986 Date: Wed, 26 Aug 2026 18:13:12 +0200 Subject: [PATCH 09/14] Fix option cache for reused controllable wrappers --- Moose Development/Moose/Core/Database.lua | 4 ++ .../Moose/Wrapper/Controllable.lua | 59 +++++++++++-------- Moose Development/Moose/Wrapper/Group.lua | 5 +- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 0bdb67ce1..16ca7b838 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -186,6 +186,8 @@ function DATABASE:AddUnit( DCSUnitName, force ) -- Register unit self.UNITS[DCSunitName]=UNIT:Register(DCSunitName) + else + self.UNITS[DCSunitName]:ResetOptionCacheIfDCSObjectChanged() end return self.UNITS[DCSunitName] @@ -802,6 +804,8 @@ function DATABASE:AddGroup( GroupName, force ) if not self.GROUPS[GroupName] or force == true then self:T( { "Add GROUP:", GroupName } ) self.GROUPS[GroupName] = GROUP:Register( GroupName ) + else + self.GROUPS[GroupName]:ResetOptionCacheIfDCSObjectChanged() end return self.GROUPS[GroupName] diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 27ebc28a4..823f17990 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3283,34 +3283,22 @@ end -- @param #number OptionValue Value of the option -- @return #CONTROLLABLE self function CONTROLLABLE:SetOption( OptionID, OptionValue ) - - local setnewoption = false - - -- Check if option has changed against cached option local ID = tostring(OptionID) - if self.ControllableOptions then - if (self.ControllableOptions[ID] ~= OptionValue) or (self.ControllableOptions[ID]==nil) then - setnewoption = true - self.ControllableOptions[ID] = OptionValue - --self:I(string.format("CONTROLLABLE %s: Option CHANGE for option %d: New value: %s!",self.ControllableName, OptionID, tostring(OptionValue))) - end + if OptionValue ~= nil and self.ControllableOptions and self.ControllableOptions[ID] == OptionValue then + return self end - - -- change option if changed - if setnewoption == true then - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - --self:I(string.format("CONTROLLABLE %s: Setting OPTION %d: to value: %s!",self.ControllableName, OptionID, tostring(OptionValue))) - Controller:setOption( OptionID, OptionValue ) - return self - end - --else - --self:I(string.format("CONTROLLABLE %s: Option NO CHANGE for option %d: Same value: %s!",self.ControllableName, OptionID, tostring(OptionValue))) - end - - return nil + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = DCSControllable:getController() + Controller:setOption( OptionID, OptionValue ) + self.ControllableOptions = self.ControllableOptions or {} + self.ControllableOptions[ID] = OptionValue + self.ControllableOptionDCSObject = DCSControllable + return self + end + + return nil end --- Query a (cached) option. Requires the option has been set with Moose(!) before. @@ -3325,6 +3313,27 @@ function CONTROLLABLE:QueryCachedOption(OptionID) return nil end +--- Reset cached options for this controllable. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:ResetOptionCache() + self.ControllableOptions = {} + self.ControllableOptionDCSObject = self:GetDCSObject() + return self +end + +--- Reset cached options when the underlying DCS object has changed. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:ResetOptionCacheIfDCSObjectChanged() + local DCSControllable = self:GetDCSObject() + if self.ControllableOptionDCSObject ~= DCSControllable then + self.ControllableOptions = {} + self.ControllableOptionDCSObject = DCSControllable + end + return self +end + --- Set option for Rules of Engagement (ROE). -- @param #CONTROLLABLE self -- @param #number ROEvalue ROE value. See ENUMS.ROE. diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 875c3e0c6..307b1a874 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2272,9 +2272,8 @@ function GROUP:Respawn( Template, Reset ) -- Reset events. self:ResetEvents() - -- Reset options - self.ControllableOptions = nil - self.ControllableOptions = {} + -- Reset options. + self:ResetOptionCache() return self end From 5278f706f5cdbd6ae29a5713f24172346ff47934 Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 27 Aug 2026 13:53:32 +0200 Subject: [PATCH 10/14] [FIXED] CTLD maxUnloadTroopsAllowed --- Moose Development/Moose/Core/Spawn.lua | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 6a498797a..e89d53b63 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1880,6 +1880,11 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) -- SpawnTemplate.uncontrolled = self.SpawnUnControlled -- end -- end + + + if self.BeforeTemplateSpawnFunc then + self:BeforeTemplateSpawnFunc(SpawnTemplate, SpawnIndex) + end end if not NoBirth then @@ -1995,6 +2000,31 @@ function SPAWN:SpawnScheduleStop() return self end +--- Allows to place a CallFunction hook just before spawning the group to tweak the template. +-- The provided method will be called just before a new group is spawned, including its given parameters. +-- The first parameter of the BeforeTemplateSpawnFunc is the raw Template used to spawn the group, the second is the SpawnIndex. +-- @param #SPAWN self +-- @param #function BeforeTemplateSpawnFunc The function to be called before the group spawns. +-- @return #SPAWN +-- @usage +-- +-- -- Declare SpawnObject and call a function when a new Group is spawned. +-- local SpawnObject = SPAWN:New( "SpawnObject" ) +-- :InitLimit( 2, 10 ) +-- :OnBeforeSpawnGroup( function( SpawnTemplate ) + -- Tweaking the template units +-- end +-- ) +-- :SpawnScheduled( 300, 0.3 ) +-- +function SPAWN:OnBeforeTemplateSpawnGroup( BeforeTemplateSpawnFunc ) + --self:F( "OnBeforeSpawnGroup" ) + + self.BeforeTemplateSpawnFunc = BeforeTemplateSpawnFunc + + return self +end + --- Allows to place a CallFunction hook when a new group spawns. -- The provided method will be called when a new group is spawned, including its given parameters. -- The first parameter of the SpawnFunction is the @{Wrapper.Group#GROUP} that was spawned. From abf6990e8f84916ade9583a80228611064fbd942 Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 27 Aug 2026 15:51:56 +0200 Subject: [PATCH 11/14] [FIXED] SPAWN.BeforeTemplateSpawnFunc --- Moose Development/Moose/Core/Spawn.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index e89d53b63..ab50cae9b 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1883,7 +1883,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if self.BeforeTemplateSpawnFunc then - self:BeforeTemplateSpawnFunc(SpawnTemplate, SpawnIndex) + self.BeforeTemplateSpawnFunc(SpawnTemplate, SpawnIndex) end end @@ -2011,14 +2011,14 @@ end -- -- Declare SpawnObject and call a function when a new Group is spawned. -- local SpawnObject = SPAWN:New( "SpawnObject" ) -- :InitLimit( 2, 10 ) --- :OnBeforeSpawnGroup( function( SpawnTemplate ) +-- :OnBeforeTemplateSpawnGroup( function( SpawnTemplate ) -- Tweaking the template units -- end -- ) -- :SpawnScheduled( 300, 0.3 ) -- function SPAWN:OnBeforeTemplateSpawnGroup( BeforeTemplateSpawnFunc ) - --self:F( "OnBeforeSpawnGroup" ) + --self:F( "OnBeforeTemplateSpawnGroup" ) self.BeforeTemplateSpawnFunc = BeforeTemplateSpawnFunc From 7bbd79cdb03b84f2dbe7d4b718a186c9db7f0760 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:26:12 +0200 Subject: [PATCH 12/14] Update Mantis.lua Return true at the first one found in _CheckAnyEWRAlive --- Moose Development/Moose/Functional/Mantis.lua | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index a85774d7e..2e34ba601 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1890,20 +1890,22 @@ do --- [Internal] Check if any EWR or AWACS is still alive -- @param #MANTIS self -- @return #boolean outcome - function MANTIS:_CheckAnyEWRAlive() - self:T(self.lid .. "_CheckAnyEWRAlive") - local alive = false - if self.EWR_Group:CountAlive() > 0 then - alive = true - end - if not alive and self.AWACS_Prefix then - local awacs = GROUP:FindByName(self.AWACS_Prefix) - if awacs and awacs:IsAlive() then - alive = true + function MANTIS:_CheckAnyEWRAlive() + self:T(self.lid .. "_CheckAnyEWRAlive") + + for _, group in pairs(self.EWR_Group:GetSet()) do + if group and group:IsAlive() then + return true + end end + if self.AWACS_Prefix then + local awacs = GROUP:FindByName(self.AWACS_Prefix) + if awacs and awacs:IsAlive() then + return true + end + end + return false end - return alive - end --- [Internal] Function to determine state of the advanced mode -- @param #MANTIS self From 399910bb410937ab410941296604729e9e9a268a Mon Sep 17 00:00:00 2001 From: smiki Date: Fri, 28 Aug 2026 18:30:47 +0200 Subject: [PATCH 13/14] [FIXED] SPAWN.BeforeTemplateSpawnFunc --- Moose Development/Moose/Core/Spawn.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index ab50cae9b..2c931e26b 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1883,7 +1883,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if self.BeforeTemplateSpawnFunc then - self.BeforeTemplateSpawnFunc(SpawnTemplate, SpawnIndex) + self:BeforeTemplateSpawnFunc(SpawnTemplate, SpawnIndex) end end @@ -2011,7 +2011,7 @@ end -- -- Declare SpawnObject and call a function when a new Group is spawned. -- local SpawnObject = SPAWN:New( "SpawnObject" ) -- :InitLimit( 2, 10 ) --- :OnBeforeTemplateSpawnGroup( function( SpawnTemplate ) +-- :OnBeforeTemplateSpawnGroup( function( Spawn, SpawnTemplate, SpawnIndex ) -- Tweaking the template units -- end -- ) From d8f3e364ec8baf756a2e18ddc0ae77f2de448a16 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:58:57 +0200 Subject: [PATCH 14/14] Update FlightControl.lua --- Moose Development/Moose/Ops/FlightControl.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 1bb0383cf..551996165 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -19,7 +19,7 @@ -- ### Author: **funkyfranky** -- -- === --- @module OPS.FlightControl +-- @module Ops.FlightControl -- @image OPS_FlightControl.png