From 9b3aa423aae7ec5345d6be19e63e09f48701cd80 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 11 Dec 2025 09:34:19 +0100 Subject: [PATCH 01/17] xx --- Moose Development/Moose/AI/AI_A2G_BAI.lua | 4 ++-- Moose Development/Moose/AI/AI_CAP.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua index 8a39ad0d2..3ec713bad 100644 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ b/Moose Development/Moose/AI/AI_A2G_BAI.lua @@ -11,8 +11,8 @@ -- @module AI.AI_A2G_BAI -- @image AI_Air_To_Ground_Engage.JPG --- @type AI_A2G_BAI --- @extends AI.AI_A2A_Engage#AI_A2A_Engage -- TODO: Documentation. This class does not exist, unable to determine what it extends. +--- @type AI_A2G_BAI +-- @extends AI.AI_Air_Engage#AI_A2A_Engage -- TODO: Documentation. This class does not exist, unable to determine what it extends. --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. -- diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index 8f1f1501b..c504791f7 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -31,7 +31,7 @@ -- @module AI.AI_CAP -- @image AI_Combat_Air_Patrol.JPG --- @type AI_CAP_ZONE +--- @type AI_CAP_ZONE -- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. -- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed. -- @extends AI.AI_Patrol#AI_PATROL_ZONE From f9410a9169273d2ce34f76e592dc421f7960643b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 11 Dec 2025 15:47:48 +0100 Subject: [PATCH 02/17] #EASYA2G - Initial release --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Modules_local.lua | 1 + Moose Development/Moose/Ops/Auftrag.lua | 2 +- Moose Development/Moose/Ops/EasyA2G.lua | 907 +++++++++++++++++++++ Moose Development/Moose/Ops/EasyGCICAP.lua | 32 +- Moose Development/Moose/Ops/OpsGroup.lua | 9 + Moose Setup/Moose.files | 1 + 7 files changed, 944 insertions(+), 9 deletions(-) create mode 100644 Moose Development/Moose/Ops/EasyA2G.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index fb08421e4..171532400 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -118,6 +118,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/PlayerRecce.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' ) +__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyA2G.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index 356464f50..f43c2a378 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -116,6 +116,7 @@ __Moose.Include( 'Ops\\Operation.lua' ) __Moose.Include( 'Ops\\FlightControl.lua' ) __Moose.Include( 'Ops\\PlayerRecce.lua' ) __Moose.Include( 'Ops\\EasyGCICAP.lua' ) +__Moose.Include( 'Ops\\EasyA2G.lua' ) __Moose.Include( 'AI\\AI_Balancer.lua' ) __Moose.Include( 'AI\\AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index a3ac50a46..4b49a2842 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -3060,7 +3060,7 @@ end --- **[LEGION, COMMANDER, CHIEF]** Set the repeat delay in seconds after a mission is successful/failed. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level. -- @param #AUFTRAG self --- @param #number Nrepeat Repeat delay in seconds. Default 1. +-- @param #number RepeatDelay Repeat delay in seconds. Default 1. -- @return #AUFTRAG self function AUFTRAG:SetRepeatDelay(RepeatDelay) self.repeatDelay = RepeatDelay diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua new file mode 100644 index 000000000..e385878a1 --- /dev/null +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -0,0 +1,907 @@ +------------------------------------------------------------------------- +-- Easy A2G Engagement Class, based on OPS classes +------------------------------------------------------------------------- +-- +-- ## Documentation: +-- +-- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyAG.html +-- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyAG). +-- +------------------------------------------------------------------------- +-- Date: Dec 2025 +-- Last Update: Dec 2025 +------------------------------------------------------------------------- +-- +--- **Ops** - Easy A2G Manager +-- +-- === +-- +-- **Main Features:** +-- +-- * Automatically create and manage A2G defenses using an AirWing and Squadrons for one coalition +-- * Easy set-up +-- * Add additional AirWings on other airbases +-- * Each wing can have more than one Squadron - tasking to Squadrons is done on a random basis per AirWing +-- * Create borders and zones of engagement +-- * Detection can be ground based and/or via AWACS +-- +-- === +-- +-- ### AUTHOR: **applevangelist** +-- +-- @module Ops.EasyAG +-- @image AI_Air_To_Ground_Dispatching.JPG + + +--- EASYA2G Class +-- @type EASYA2G +-- @field #string ClassName +-- @field #number overhead +-- @field #number engagerange +-- @field #number capgrouping +-- @field #string airbasename +-- @field Wrapper.Airbase#AIRBASE airbase +-- @field #number coalition +-- @field #string alias +-- @field #table wings +-- @field Ops.Intel#INTEL Intel +-- @field #number resurrection +-- @field #number capspeed +-- @field #number capalt +-- @field #number capdir +-- @field #number capleg +-- @field #number maxinterceptsize +-- @field #number missionrange +-- @field #number noalert5 +-- @field #table ManagedAW +-- @field #table ManagedSQ +-- @field #table ManagedCP +-- @field #table ManagedTK +-- @field #table ManagedEWR +-- @field #table ManagedREC +-- @field #number MaxAliveMissions +-- @field #boolean debug +-- @field #number repeatsonfailure +-- @field Core.Set#SET_ZONE GoZoneSet +-- @field Core.Set#SET_ZONE NoGoZoneSet +-- @field Core.Set#SET_ZONE ConflictZoneSet +-- @field #boolean Monitor +-- @field #boolean TankerInvisible +-- @field #number CapFormation +-- @field #table ReadyFlightGroups +-- @field #boolean DespawnAfterLanding +-- @field #boolean DespawnAfterHolding +-- @field #list ListOfAuftrag +-- @field #string defaulttakeofftype Take off type +-- @field #number FuelLowThreshold +-- @field #number FuelCriticalThreshold +-- @field #boolean showpatrolpointmarks +-- @field #table EngageTargetTypes +-- @extends Ops.EasyGCIA2G#EASYGCICAP + +--- *“High-Threat Close-Air-Support is a Myth.”* -- Mike “Starbaby” Pietrucha. +-- +-- === +-- +-- # The EasyAG Concept +-- +-- The idea of this class is partially to make the OPS classes easier operational for an A2G defense network, and to replace the legacy AI_A2G_Dispatcher system - not to it's +-- full extent, but make a basic system work very quickly. +-- +-- # Setup +-- +-- ## Basic understanding +-- +-- The basics are, there is **one** and only **one** AirWing per airbase. Each AirWing has **at least** one Squadron, who will do A2G tasks. Squadrons will be randomly chosen for the task at hand. +-- Each AirWing has **at least** one Conflict Zone that it manages. COnflict Zones will be covered by the AirWing automatically as long as airframes are available. Detected enemy ground forces will be assigned to **one** +-- AirWing based on proximity (that is, if you have more than one). +-- +-- ## Assignment of tasks for enemies +-- +-- An exisiting plane or a newly spawned plane will take care of the intruders. Standard overhead is 0.1, i.e. a group of 10 intrudes will +-- be managed by one planes from the assigned AirWing. There is an maximum missions limitation per AirWing, so we do not spam the skies. +-- +-- ## Basic set-up code +-- +-- ### Prerequisites +-- +-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!** +-- E.g. for Kutaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ. +-- Next put a late activated template group for your A2G Squadron on the map. Last, put a zone on the map for the Defense operations, let's name it "Blue Zone 1". Size of the zone plays no role. +-- Put a scout system on the map and name it aptly, like "Blue SCOUT". +-- +-- ### Zones +-- +-- For our example, you create a RED and a BLUE border, as a closed polygonal zone representing the borderlines. You can also have conflict zone, where - for our example - BLUE will attack +-- RED groups, despite being on or close to RED territory. Think of a no-fly zone or an limited area of engagement. Conflict zones take precedence over borders, i.e. they can overlap all borders. +-- +-- ### Code it +-- +-- -- Set up a basic system for the blue side, we'll reside on Kutaisi, and use GROUP objects with "Blue SCOUT" in the name as Detecting Systems. +-- local mywing = EASYA2G:New("A2G",AIRBASE.Caucasus.Kutaisi,"blue","SCOUT") +-- +-- -- Add a holding/ingress point belonging to our airbase, we'll be at 5k ft doing 250 kn, initial direction 225 degrees (West), leg 5NM +-- -- This will effectively be the ingress coordinate into the cnflict zone +-- local Coordinate = ZONE:New("A2G Loitering"):GetCoordinate() +-- mywing:AddHoldingPointA2G(AIRBASE.Caucasus.Kutaisi,Coordinate,5000,250,225,5) +-- +-- -- Add a recon point over the conflict zone, we'll use a reaper for recon +-- local Coordinate2 = ZONE:New("A2G Recon"):GetCoordinate() +-- mywing:AddPatrolPointRecon(AIRBASE.Caucasus.Kutaisi,Coordinate2,15000,225,225,5) +-- +-- -- Add three Squadrons with templates "Hero 1" and "Hero 2", 20 airframes, skill as set +-- mywing:AddSquadron("A2G Flight", "Hero 1", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.GOOD, Modex, Livery) +-- mywing:AddSquadron("A2G Helo", "Hero 2", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.HIGH, Modex, Livery) +-- mywing:AddReconSquadron("Recon Drone", "SpyInTheSky SCOUT", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.EXCELLENT, Modex, Livery) +-- +-- -- Ensure our reaper doesn't get immediately killed +-- mywing:SetTankerAndScoutsInvisible(true) +-- +-- -- Add a couple of zones +-- -- We'll defend our own border +-- mywing:AddAcceptZone(ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) )) +-- -- We'll attack intruders also here - conflictzones can overlap borders(!) - limited zone of engagement +-- mywing:AddConflictZone(ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" ))) +-- -- We'll leave the reds alone on their turf +-- mywing:AddRejectZone(ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) )) +-- +-- -- Optional - Draw the borders on the map so we see what's going on +-- -- Set up borders on map +-- local BlueBorder = ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) ) +-- BlueBorder:DrawZone(-1,{0,0,1},1,FillColor,FillAlpha,1,true) +-- local ConflictZone = ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" )) +-- ConflictZone:DrawZone(-1,{1,1,0},1,FillColor,FillAlpha,2,true) +-- local BlueNoGoZone = ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) ) +-- BlueNoGoZone:DrawZone(-1,{1,0,0},1,FillColor,FillAlpha,4,true) +-- +-- ### Add a second airwing with squads and own patrol point (optional) +-- +-- -- Set this up at Sukhumi +-- mywing:AddAirwing(AIRBASE.Caucasus.Sukhumi_Babushara,"Blue A2G Sukhumi") +-- -- A2G Point "Blue Zone 2" +-- mywing:AddPatrolPointA2G(AIRBASE.Caucasus.Sukhumi_Babushara,ZONE:FindByName("Blue Zone 2"):GetCoordinate(),30000,400,90,20) +-- +-- -- This one has two squadrons to choose from +-- mywing:AddSquadron("Blue Sq3 F16","A2G Sukhumi II",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,402,"JASDF 6th TFS 43-8526 Skull Riders") +-- mywing:AddSquadron("Blue Sq2 F15","A2G Sukhumi I",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,202,"390th Fighter SQN") +-- +-- ### Add a tanker (optional) +-- +-- -- **Note** If you need different tanker types, i.e. Boom and Drogue, set them up at different AirWings! +-- -- Add a tanker point +-- mywing:AddPatrolPointTanker(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone Tanker"):GetCoordinate(),20000,280,270,50) +-- -- Add a tanker squad - Radio 251 AM, TACAN 51Y +-- mywing:AddTankerSquadron("Blue Tanker","Tanker Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.EXCELLENT,602,nil,251,radio.modulation.AM,51) +-- +-- ### Add an AWACS (optional) +-- +-- -- Add an AWACS point +-- mywing:AddPatrolPointAwacs(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone AWACS"):GetCoordinate(),25000,300,270,50) +-- -- Add an AWACS squad - Radio 251 AM, TACAN 51Y +-- mywing:AddAWACSSquadron("Blue AWACS","AWACS Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.AVERAGE,702,nil,271,radio.modulation.AM) +-- +-- # Fine-Tuning +-- +-- ## Change Defaults +-- +-- * @{#EASYA2G.SetDefaultResurrection}: Set how many seconds the AirWing stays inoperable after the AirWing STATIC HQ ist destroyed, default 900 secs. +-- * @{#EASYA2G.SetDefaultA2GSpeed}: Set how many knots the A2G flights should do (will be altitude corrected), default 225 kn. +-- * @{#EASYA2G.SetDefaultA2GAlt}: Set at which altitude (ASL) the A2G planes will fly, default 10,000 ft. +-- * @{#EASYA2G.SetDefaultA2GDirection}: Set the initial direction from the A2G point the planes will fly in degrees, default is 90°. +-- * @{#EASYA2G.SetDefaultA2GLeg}: Set the length of the A2G leg, default is 5 NM. +-- * @{#EASYA2G.SetDefaultA2GGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 1. +-- * @{#EASYA2G.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100. +-- * @{#EASYA2G.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2. +-- * @{#EASYA2G.SetDefaultEngageRange}: Set max engage range for A2G flights if they detect intruders, defaults to 50. +-- * @{#EASYA2G.SetMaxAliveMissions}: Set max parallel missions can be done (A2G+GCI+Alert5+Tanker+AWACS), defaults to 8. +-- * @{#EASYA2G.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3. +-- * @{#EASYA2G.SetTankerAndScoutsInvisible}: Set Tanker and Scouts to be invisible to enemy AI eyes. Is set to `true` by default. +-- +-- ## Debug and Monitor +-- +-- mywing.debug = true -- log information +-- mywing.Monitor = true -- show some statistics on screen +-- +-- +-- @field #EASYA2G +EASYA2G = { + ClassName = "EASYA2G", + overhead = 0.2, + capgrouping = 1, + airbasename = nil, + airbase = nil, + coalition = "blue", + alias = nil, + wings = {}, + Intel = nil, + resurrection = 900, + capspeed = 300, + capalt = 25000, + capdir = 45, + capleg = 5, + maxinterceptsize = 2, + missionrange = 100, + noalert5 = 2, + ManagedAW = {}, + ManagedSQ = {}, + ManagedCP = {}, + ManagedTK = {}, + ManagedEWR = {}, + ManagedREC = {}, + MaxAliveMissions = 8, + debug = true, + engagerange = 50, + repeatsonfailure = 3, + GoZoneSet = nil, + NoGoZoneSet = nil, + ConflictZoneSet = nil, + Monitor = false, + TankerInvisible = true, + CapFormation = nil, + ReadyFlightGroups = {}, + DespawnAfterLanding = false, + DespawnAfterHolding = true, + ListOfAuftrag = {}, + defaulttakeofftype = "hot", + FuelLowThreshold = 25, + FuelCriticalThreshold = 10, + showpatrolpointmarks = false, + EngageTargetTypes = {"Ground"}, +} + +--- Internal Squadron data type +-- @type EASYA2G.Squad +-- @field #string TemplateName +-- @field #string SquadName +-- @field #string AirbaseName +-- @field #number AirFrames +-- @field #string Skill +-- @field #string Modex +-- @field #string Livery +-- @field #boolean Tanker +-- @field #boolean AWACS +-- @field #boolean RECON +-- @field #number Frequency +-- @field #number Modulation +-- @field #number TACAN + +--- Internal Wing data type +-- @type EASYA2G.Wing +-- @field #string AirbaseName +-- @field #string Alias +-- @field #string CapZoneName + +--- Internal CapPoint data type +-- @type EASYA2G.CapPoint +-- @field #string AirbaseName +-- @field Core.Point#COORDINATE Coordinate +-- @field #number Altitude +-- @field #number Speed +-- @field #number Heading +-- @field #number LegLength +-- @field Core.Zone#ZONE_BASE Zone + +--- EASYA2G class version. +-- @field #string version +EASYA2G.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: TBD + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new GCIA2G Manager +-- @param #EASYA2G self +-- @param #string Alias A Name for this A2G Setup +-- @param #string AirbaseName Name of the Home Airbase +-- @param #string Coalition Coalition, e.g. "blue" or "red" +-- @param #string ScoutName (Partial) group name of the detection system of the coalition, e.g. "Red SCOUT", can be handed in as table of names, e.g.{"SCOUT","DRONE","SAM"} +-- @return #EASYA2G self +function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName) + -- Inherit everything from FSM class. + + local self=BASE:Inherit(self, EASYGCICAP:New(Alias, AirbaseName, Coalition, ScoutName)) -- #EASYA2G + + -- defaults + self.alias = Alias or AirbaseName.." A2G Wing" + self.coalitionname = string.lower(Coalition) or "blue" + self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED + self.wings = {} + if type(ScoutName) == "string" then ScoutName = {ScoutName} end + self.EWRName = ScoutName --or self.coalitionname.." EWR" + --self.CapZoneName = CapZoneName + self.airbasename = AirbaseName + self.airbase = AIRBASE:FindByName(self.airbasename) + self.GoZoneSet = SET_ZONE:New() + self.NoGoZoneSet = SET_ZONE:New() + self.ConflictZoneSet = SET_ZONE:New() + self.resurrection = 900 + self.capspeed = 225 + self.capalt = 5000 + self.capdir = 90 + self.capleg = 5 + self.capgrouping = 2 + self.missionrange = 100 + self.noalert5 = 2 + self.MaxAliveMissions = 8 + self.engagerange = 50 + self.repeatsonfailure = 3 + self.Monitor = false + self.TankerInvisible = true + self.CapFormation = ENUMS.Formation.FixedWing.FingerFour.Group + self.DespawnAfterLanding = false + self.DespawnAfterHolding = true + self.ListOfAuftrag = {} + self.defaulttakeofftype = "hot" + self.FuelLowThreshold = 25 + self.FuelCriticalThreshold = 10 + self.showpatrolpointmarks = false + self.EngageTargetTypes = {"Ground"} + + -- Set some string id for output to DCS.log file. + self.lid=string.format("EASYA2G %s | ", self.alias) + + -- Add FSM transitions. + -- From State --> Event --> To State + self:SetStartState("Stopped") + self:AddTransition("Stopped", "Start", "Running") + self:AddTransition("Running", "Stop", "Stopped") + self:AddTransition("*", "Status", "*") + + self:AddAirwing(self.airbasename,self.alias,self.CapZoneName) + + self:I(self.lid.."Created new instance (v"..self.version..")") + + self:__Start(math.random(6,12)) + + return self +end + +------------------------------------------------------------------------- +-- Functions +------------------------------------------------------------------------- + +--- Set Tanker and Scouts to be invisible to enemy AI eyes +-- @param #EASYA2G self +-- @param #boolean Switch Set to true or false, by default this is set to true already +-- @return #EASYA2G self +function EASYA2G:SetTankerAndScoutsInvisible(Switch) + self:T(self.lid.."SetTankerAndScoutsInvisible") + self.TankerInvisible = Switch + return self +end + +--- Set default A2G Speed in knots +-- @param #EASYA2G self +-- @param #number Speed Speed defaults to 300 +-- @return #EASYA2G self +function EASYA2G:SetDefaultA2GSpeed(Speed) + self:T(self.lid.."SetDefaultSpeed") + self.capspeed = Speed or 300 + return self +end + +--- Set A2G Flight formation. +-- @param #EASYA2G self +-- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group +-- @return #EASYA2G self +function EASYA2G:SetA2GFormation(Formation) + self:T(self.lid.."SetA2GFormation") + self.CapFormation = Formation + return self +end + +--- Set default A2G Altitude in feet +-- @param #EASYA2G self +-- @param #number Altitude Altitude defaults to 25000 +-- @return #EASYA2G self +function EASYA2G:SetDefaultA2GAlt(Altitude) + self:T(self.lid.."SetDefaultAltitude") + self.capalt = Altitude or 25000 + return self +end + +--- Set default A2G lieg initial direction in degrees +-- @param #EASYA2G self +-- @param #number Direction Direction defaults to 90 (East) +-- @return #EASYA2G self +function EASYA2G:SetDefaultA2GDirection(Direction) + self:T(self.lid.."SetDefaultDirection") + self.capdir = Direction or 90 + return self +end + +--- Set default leg length in NM +-- @param #EASYA2G self +-- @param #number Leg Leg defaults to 5 +-- @return #EASYA2G self +function EASYA2G:SetDefaultA2GLeg(Leg) + self:T(self.lid.."SetDefaultLeg") + self.capleg = Leg or 5 + return self +end + +--- Set default grouping, i.e. how many airplanes per A2G point +-- @param #EASYA2G self +-- @param #number Grouping Grouping defaults to 2 +-- @return #EASYA2G self +function EASYA2G:SetDefaultA2GGrouping(Grouping) + self:T(self.lid.."SetDefaultA2GGrouping") + self.capgrouping = Grouping or 2 + return self +end + +--- Set A2G mission start to vary randomly between Start end End seconds. +-- @param #EASYA2G self +-- @param #number Start +-- @param #number End +-- @return #EASYA2G self +function EASYA2G:SetA2GStartTimeVariation(Start, End) + self.capOptionVaryStartTime = Start or 5 + self.capOptionVaryEndTime = End or 60 + return self +end + +--- Set which target types A2G flights will prefer to engage, defaults to {"Ground"} +-- @param #EASYA2G self +-- @param #table types Table of comma separated #string entries, defaults to {"Ground"} (everything that is ground and is not a weapon). Useful other options are e.g. {"Armored vehicles"}, {"Tanks"}, +-- or {"APC"} or combinations like {"APC", "Tanks", "Artillery"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes). +-- @return #EASYA2G self +function EASYA2G:SetA2GEngageTargetTypes(types) + self.EngageTargetTypes = types or {"Ground"} + return self +end + +--- Add a A2G patrol/holding point to a Wing +-- @param #EASYA2G self +-- @param #string AirbaseName Name of the Wing's airbase +-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). +-- @param #number Altitude Defaults to 25000 feet ASL. +-- @param #number Speed Defaults to 300 knots TAS. +-- @param #number Heading Defaults to 90 degrees (East). +-- @param #number LegLength Defaults to 15 NM. +-- @return #EASYA2G self +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) + self:T(self.lid.."AddHoldingPointA2G")--..Coordinate:ToStringLLDDM()) + local coordinate = Coordinate + local EntryCAP = {} -- #EASYGCICAP.CapPoint + if Coordinate:IsInstanceOf("ZONE_BASE") then + -- adjust coordinate and get the coordinate from the zone + coordinate = Coordinate:GetCoordinate() + EntryCAP.Zone = Coordinate + end + EntryCAP.AirbaseName = AirbaseName + EntryCAP.Coordinate = coordinate + EntryCAP.Altitude = Altitude or 25000 + EntryCAP.Speed = Speed or 300 + EntryCAP.Heading = Heading or 90 + EntryCAP.LegLength = LegLength or 5 + self.ManagedCP[#self.ManagedCP+1] = EntryCAP + if self.debug then + local mark = MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() + end + return self +end + + +--- (Internal) Add a Squadron to an Airwing of the manager +-- @param #EASYA2G self +-- @param #string TemplateName Name of the group template. +-- @param #string SquadName Squadron name - must be unique! +-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi +-- @param #number AirFrames Number of available airframes, e.g. 20. +-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE +-- @param #string Modex (optional) Modex to be used,e.g. 402. +-- @param #string Livery (optional) Livery name to be used. +-- @param #number Frequency (optional) Radio Frequency to be used. +-- @param #number Modulation (optional) Radio Modulation to be used, e.g. radio.modulation.AM or radio.modulation.FM +-- @return #EASYA2G self +function EASYA2G:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation) + self:T(self.lid.."_AddSquadron "..SquadName) + -- Add Squadrons + local Squadron_One = SQUADRON:New(TemplateName,AirFrames,SquadName) + Squadron_One:AddMissionCapability({AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE}) + --Squadron_One:SetFuelLowRefuel(true) + Squadron_One:SetFuelLowThreshold(0.3) + Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetModex(Modex) + Squadron_One:SetLivery(Livery) + Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) + Squadron_One:SetMissionRange(self.missionrange) + + local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING + + wing:AddSquadron(Squadron_One) + wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE},75) + + return self +end + +--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready. +-- @param #EASYA2G self +-- @param #table ReadyFlightGroups ReadyFlightGroups +-- @param Ops.Auftrag#AUFTRAG Auftrag The Auftrag +-- @param Wrapper.Group#GROUP Group The Target +-- @param #number WingSize Calculated number of Flights +-- @return #boolean assigned +-- @return #number leftover +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) + self:T("_TryAssignMission for size "..WingSize or 1) + local assigned = false + local wingsize = WingSize or 1 + local mindist = 0 + local disttable = {} + if Group and Group:IsAlive() then + local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0) + self:T(self.lid..string.format("Assignment for %s",Group:GetName())) + for _name,_FG in pairs(ReadyFlightGroups or {}) do + local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP + local fcoord = FG:GetCoordinate() + local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1)) + self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist)) + disttable[#disttable+1] = { FG=FG, dist=dist} + if dist>mindist then mindist=dist end + end + + local function sortDistance(a, b) + return a.dist < b.dist + end + + table.sort(disttable, sortDistance) + + for _,_entry in ipairs(disttable) do + local FG = _entry.FG -- Ops.FlightGroup#FLIGHTGROUP + FG:AddMission(Auftrag) + local cm = FG:GetMissionCurrent() + if cm then cm:Cancel() end + wingsize = wingsize - 1 + self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) + if wingsize == 0 then + assigned = true + break + end + end + end + + return assigned, wingsize +end + +--- Find a holding point closest to the group to be attacked (if any set) +-- @param #EASYA2G self +-- @param Wrapper.Group#GROUP Group +-- @return Core.Point#COORDINATE Point (can be nil!) +function EASYA2G:_GetClosestHoldingPoint(Group) + local point = nil + local mindist = 0 + if Group and Group:IsAlive() then + local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0) + for _,_data in pairs(self.ManagedCP or {}) do + local data = _data -- #EASYGCICAP.CapPoint + --data.Coordinate + local dist = math.floor(UTILS.Round(data.Coordinate:Get2DDistance(gcoord)/1000,1)) + self:T(self.lid..string.format("Holding Point Distance %dkm",dist)) + if dist>mindist then + mindist=dist + point=data.Coordinate + end + end + end + return point +end + +--- Here, we'll decide if we need to launch an attacking flight, and from where +-- @param #EASYA2G self +-- @param Ops.Intel#INTEL.Cluster Cluster +-- @return #EASYA2G self +function EASYA2G:_AssignMission(Cluster) + self:I(self.lid.."_AssignMission") + -- Here, we'll decide if we need to launch an attacking flight, and from where + local overhead = self.overhead + local capspeed = self.capspeed + 100 + local capalt = self.capalt or 5000 + local maxsize = self.maxinterceptsize + local repeatsonfailure = self.repeatsonfailure + + local wings = self.wings + local ctlpts = self.ManagedCP + local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping + local nogozoneset = self.NoGoZoneSet + local conflictzoneset = self.ConflictZoneSet + local ReadyFlightGroups = self.ReadyFlightGroups + + -- Aircraft? + if Cluster.ctype == INTEL.Ctype.AIRCRAFT then return end + -- Threatlevel 0..10 + local contact = self.Intel:GetHighestThreatContact(Cluster) + local name = contact.groupname --#string + local threat = contact.threatlevel --#number + local position = self.Intel:CalcClusterFuturePosition(Cluster,300) + -- calculate closest zone + local bestdistance = 2000*1000 -- 2000km + local targetairwing = nil -- Ops.Airwing#AIRWING + local targetawname = "" -- #string + local clustersize = self.Intel:ClusterCountUnits(Cluster) or 1 + local wingsize = math.abs(overhead * (clustersize+1)) + if wingsize > maxsize then wingsize = maxsize end + -- existing mission, and if so - done? + local retrymission = true + if Cluster.mission and (not Cluster.mission:IsOver()) then + retrymission = false + end + if (retrymission) and (wingsize >= 1) then + MESSAGE:New(string.format("**** %s Attackers need wingsize %d", UTILS.GetCoalitionName(self.coalition), wingsize),15,"A2G"):ToAllIf(self.debug):ToLog() + for _,_data in pairs (wings) do + local airwing = _data[1] -- Ops.Airwing#AIRWING + local zone = _data[2] -- Core.Zone#ZONE + local zonecoord = zone:GetCoordinate() + local name = _data[3] -- #string + local coa = AIRBASE:FindByName(name):GetCoalition() + local distance = position:DistanceFromPointVec2(zonecoord) + local airframes = airwing:CountAssets(true) + local samecoalitionab = coa == self.coalition and true or false + if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then + bestdistance = distance + targetairwing = airwing + targetawname = name + end + end + for _,_data in pairs (ctlpts) do + --local airwing = _data[1] -- Ops.Airwing#AIRWING + --local zone = _data[2] -- Core.Zone#ZONE + --local zonecoord = zone:GetCoordinate() + --local name = _data[3] -- #string + + local data = _data -- #EASYGCICAP.CapPoint + local name = data.AirbaseName + local zonecoord = data.Coordinate + if data.Zone then + -- refresh coordinate in case we have a (moving) zone + zonecoord = data.Zone:GetCoordinate() + end + local airwing = wings[name][1] + local coa = AIRBASE:FindByName(name):GetCoalition() + local samecoalitionab = coa == self.coalition and true or false + local distance = position:DistanceFromPointVec2(zonecoord) + local airframes = airwing:CountAssets(true) + if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then + bestdistance = distance + targetairwing = airwing -- Ops.Airwing#AIRWING + targetawname = name + end + end + local text = string.format("Closest Airwing is %s", targetawname) + local m = MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() + -- Do we have a matching airwing? + if targetairwing then + local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort) + local missioncount = self:_CountAliveAuftrags() + -- Enough airframes on mission already? + self:T(self.lid.." Assets on Mission "..AssetCount) + if missioncount < MaxAliveMissions then + local repeats = repeatsonfailure + local Vec1 = contact.group:GetVec2() + local Vec2 = targetairwing:GetVec2() + --local HoldingVec2 = UTILS.FindNearestPointOnCircle(Vec1,UTILS.NMToMeters(10),Vec2) + local IngressCoordinate = self:_GetClosestHoldingPoint(contact.group) + if IngressCoordinate == nil then + local IngressVec2 = UTILS.FindNearestPointOnCircle(Vec1,UTILS.NMToMeters(10),Vec2) + IngressCoordinate = COORDINATE:NewFromVec2(IngressVec2) + end + local InterceptAuftrag = AUFTRAG:NewBAI(contact.group,capalt) + :SetMissionRange(150) + :SetPriority(1,true,1) + :SetRepeatDelay(300) + --:SetRequiredAssets(wingsize) + :SetRepeatOnFailure(repeats) + :SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt)) + :SetMissionAltitude(capalt) + -- TODO: Refine this + --:SetMissionHoldingCoord(COORDINATE:NewFromVec2(HoldingVec2),capalt,capspeed,120) + :SetMissionIngressCoord(IngressCoordinate,capalt,capspeed) + --:SetMissionEgressCoord(COORDINATE:NewFromVec2(HoldingVec2),capalt,capspeed) + + if nogozoneset:Count() > 0 then + InterceptAuftrag:AddConditionSuccess( + function(group,zoneset,conflictset) + local success = false + if group and group:IsAlive() then + local coord = group:GetCoordinate() + if coord and zoneset:Count() > 0 and zoneset:IsCoordinateInZone(coord) then + success = true + end + if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then + success = false + end + else + success = true -- target dead + end + return success + end, + contact.group, + nogozoneset, + conflictzoneset + ) + end + + InterceptAuftrag:AddConditionFailure( + function() + local failure = false + if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting() then failure = true end + return failure + end + ) + + table.insert(self.ListOfAuftrag,InterceptAuftrag) + local assigned, rest = self:_TryAssignMission(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) + if not assigned then + InterceptAuftrag:SetRequiredAssets(rest) + targetairwing:AddMission(InterceptAuftrag) + end + Cluster.mission = InterceptAuftrag + end + else + MESSAGE:New("**** Not enough airframes available or max mission limit reached!",15,"EasyA2G"):ToAllIf(self.debug):ToLog() + end + end +end + +--- (Internal) Start detection. +-- @param #EASYA2G self +-- @return #EASYA2G self +function EASYA2G:_StartIntel() + self:T(self.lid.."_StartIntel") + -- Border GCI Detection + local BlueAir_DetectionSetGroup = SET_GROUP:New() + BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName ) + BlueAir_DetectionSetGroup:FilterStart() + + -- Intel type detection + local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias) + BlueIntel:SetClusterAnalysis(true,false,false) + BlueIntel:SetForgetTime(300) + BlueIntel:SetAcceptZones(self.GoZoneSet) + BlueIntel:SetRejectZones(self.NoGoZoneSet) + BlueIntel:SetConflictZones(self.ConflictZoneSet) + BlueIntel:SetVerbosity(0) + BlueIntel:Start() + + if self.debug then + BlueIntel.debug = true + end + + local function AssignCluster(Cluster) + self:_AssignMission(Cluster) + end + + function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) + AssignCluster(Cluster) + end + + self.Intel = BlueIntel + return self +end + +------------------------------------------------------------------------- +-- TODO FSM Functions +------------------------------------------------------------------------- + +--- (Internal) FSM Function onafterStart +-- @param #EASYA2G self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #EASYA2G self +function EASYA2G:onafterStart(From,Event,To) + self:T({From,Event,To}) + self:_StartIntel() + self:_CreateAirwings() + self:_CreateSquads() + --self:_SetCAPPatrolPoints() + self:_SetTankerPatrolPoints() + self:_SetAwacsPatrolPoints() + self:_SetReconPatrolPoints() + self:__Status(-10) + return self +end + +--- (Internal) FSM Function onafterStatus +-- @param #EASYA2G self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #EASYGCICAP self +function EASYA2G:onafterStatus(From,Event,To) + self:T({From,Event,To}) + -- cleanup + local cleaned = false + local cleanlist = {} + for _,_auftrag in pairs(self.ListOfAuftrag) do + local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG + if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then + table.insert(cleanlist,auftrag) + cleaned = true + end + end + if cleaned == true then + self.ListOfAuftrag = nil + self.ListOfAuftrag = cleanlist + end + -- Gather Some Stats + local function counttable(tbl) + local count = 0 + for _,_data in pairs(tbl) do + count = count + 1 + end + return count + end + local wings = counttable(self.ManagedAW) + local squads = counttable(self.ManagedSQ) + local caps = counttable(self.ManagedCP) + local assets = 0 + local instock = 0 + local capmission = 0 + local interceptmission = 0 + local reconmission = 0 + local awacsmission = 0 + local tankermission = 0 + local alert5mission = 0 + for _,_wing in pairs(self.wings) do + local count = _wing[1]:CountAssetsOnMission(MissionTypes,Cohort) + local count2 = _wing[1]:CountAssets(true,MissionTypes,Attributes) + --capmission = capmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.PATROLRACETRACK}) + interceptmission = interceptmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) + reconmission = reconmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) + awacsmission = awacsmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) + tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) + alert5mission = alert5mission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) + assets = assets + count + instock = instock + count2 + local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) + -- update ready groups + self.ReadyFlightGroups = nil + self.ReadyFlightGroups = {} + for _,_asset in pairs(assetsonmission or {}) do + local asset = _asset -- Functional.Warehouse#WAREHOUSE.Assetitem + local FG = asset.flightgroup -- Ops.FlightGroup#FLIGHTGROUP + if FG then + local name = FG:GetName() + local engage = FG:IsEngaging() + local hasmissiles = FG:CanAirToGround() + self:T("Is Alert5? "..tostring(FG:GetMissionCurrent().type)) + local isalert5 = (FG:GetMissionCurrent() ~= nil and FG:GetMissionCurrent().type == AUFTRAG.Type.ALERT5) and true or false + local ready = hasmissiles and FG:IsFuelGood() and (FG:IsAirborne() or isalert5) + self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()), tostring(isalert5))) + if ready then + self.ReadyFlightGroups[name] = FG + end + end + end + end + if self.Monitor then + local threatcount = #self.Intel.Clusters or 0 + local text = self.alias + text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock + text = text.."\nThreats: "..threatcount + text = text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission + --text = text.."\n - A2G Holding: "..capmission + text = text.."\n - A2G Attack: "..interceptmission + text = text.."\n - AWACS: "..awacsmission + text = text.."\n - TANKER: "..tankermission + text = text.."\n - Recon: "..reconmission + text = text.."\n - Alert5 "..alert5mission + text = text.."\nMission Limit: "..self.MaxAliveMissions + MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) + end + self:__Status(30) + return self +end + + diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 8a902cad5..14b11e347 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -109,7 +109,7 @@ -- ### Prerequisites -- -- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!** --- E.g. for Kuitaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ. +-- E.g. for Kutaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ. -- Next put a late activated template group for your CAP/GCI Squadron on the map. Last, put a zone on the map for the CAP operations, let's name it "Blue Zone 1". Size of the zone plays no role. -- Put an EW radar system on the map and name it aptly, like "Blue EWR". -- @@ -278,7 +278,8 @@ EASYGCICAP = { EASYGCICAP.version="0.1.30" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO list +-- + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: TBD @@ -611,7 +612,6 @@ function EASYGCICAP:SetCapStartTimeVariation(Start, End) return self end - --- Set which target types CAP flights will prefer to engage, defaults to {"Air"} -- @param #EASYGCICAP self -- @param #table types Table of comma separated #string entries, defaults to {"Air"} (everything that flies and is not a weapon). Useful other options are e.g. {"Bombers"}, {"Fighters"}, @@ -656,7 +656,7 @@ function EASYGCICAP:_CreateAirwings() return self end ---- (internal) Create and add another AirWing to the manager +--- (Internal) Create and add another AirWing to the manager -- @param #EASYGCICAP self -- @param #string Airbasename -- @param #string Alias @@ -763,8 +763,13 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) end end - if self.noalert5 > 0 then - local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) + if self.noalert5 > 0 then + local alert + if self.ClassName == "EASYGCICAP" then + alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) + elseif self.ClassName == "EASYA2G" then + alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) + end alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) @@ -1459,6 +1464,8 @@ function EASYGCICAP:_AssignIntercept(Cluster) if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then success = false end + else + success = true end return success end, @@ -1468,6 +1475,14 @@ function EASYGCICAP:_AssignIntercept(Cluster) ) end + InterceptAuftrag:AddConditionFailure( + function() + local failure = false + if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting() then failure = true end + return failure + end + ) + table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -1603,7 +1618,7 @@ function EASYGCICAP:onafterStatus(From,Event,To) tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) assets = assets + count instock = instock + count2 - local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) + local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5, AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) -- update ready groups self.ReadyFlightGroups = nil self.ReadyFlightGroups = {} @@ -1614,7 +1629,8 @@ function EASYGCICAP:onafterStatus(From,Event,To) local name = FG:GetName() local engage = FG:IsEngaging() local hasmissiles = FG:IsOutOfMissiles() == nil and true or false - local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne() + local isalert5 = (FG:GetMissionCurrent() ~= nil and FG:GetMissionCurrent().type == AUFTRAG.Type.ALERT5) and true or false + local ready = hasmissiles and FG:IsFuelGood() and (FG:IsAirborne() or isalert5) --self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready))) if ready then self.ReadyFlightGroups[name] = FG diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 13b4597f4..aedaee30e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2741,6 +2741,15 @@ function OPSGROUP:IsOutOfTorpedos() return self.outofTorpedos end +--- Check if the group is out of A2G Ammo +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of torpedos. +function OPSGROUP:IsOutOfA2GAmmo() + if (self.outofMissilesAG and self.outofBombs and self.outofGuns) or self.outofAmmo then + return true + end + return false +end --- Check if the group has currently switched a LASER on. -- @param #OPSGROUP self diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index f7c8be377..99a9b58f9 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -104,6 +104,7 @@ Ops/FlightControl.lua Ops/PlayerTask.lua Ops/PlayerRecce.lua Ops/EasyGCICAP.lua +Ops/EasyA2G.lua Ops/OpsZone.lua Ops/ArmyGroup.lua Ops/OpsTransport.lua From 46c0d4717545cb3ed7175e1d3ea9554b62f27c09 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:37:30 +0100 Subject: [PATCH 03/17] Update build-docs.yml --- .github/workflows/build-docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index c28165ca3..08c79dc3d 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -77,11 +77,11 @@ jobs: - name: Install markdown (prereq for LuaDocumentor) run: | - sudo luarocks install markdown 0.32-2 + sudo luarocks install markdown 0.33-1 - name: Install penlight (prereq for LuaDocumentor) run: | - sudo luarocks install penlight 1.11.0-1 + sudo luarocks install penlight 1.14.0-3 - name: Install metalua-compiler (prereq for LuaDocumentor) run: | From 804e0ed69da14d2574d1d1819776d284898860de Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 12 Dec 2025 10:51:55 +0100 Subject: [PATCH 04/17] Docu fixes --- Moose Development/Moose/Ops/EasyA2G.lua | 36 +++++++++++++-- Moose Development/Moose/Ops/EasyGCICAP.lua | 54 +++++++++++++++++++++- 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index e385878a1..b32f2df96 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -80,7 +80,7 @@ -- @field #number FuelCriticalThreshold -- @field #boolean showpatrolpointmarks -- @field #table EngageTargetTypes --- @extends Ops.EasyGCIA2G#EASYGCICAP +-- @extends Ops.EasyGCICAP#EASYGCICAP --- *“High-Threat Close-Air-Support is a Myth.”* -- Mike “Starbaby” Pietrucha. -- @@ -286,7 +286,7 @@ EASYA2G = { --- EASYA2G class version. -- @field #string version -EASYA2G.version="0.0.1" +EASYA2G.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -356,6 +356,34 @@ function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName) self:AddTransition("Running", "Stop", "Stopped") self:AddTransition("*", "Status", "*") + --- On Before "Start" event. + -- @function [parent=#EASYA2G] OnBeforeStart + -- @param #EASYA2G self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- On After "Start" event. + -- @function [parent=#EASYA2G] OnAfterStart + -- @param #EASYA2G self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- On Before "Status" event. + -- @function [parent=#EASYA2G] OnBeforeStatus + -- @param #EASYA2G self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- On After "Status" event. + -- @function [parent=#EASYA2G] OnAfterStatus + -- @param #EASYA2G self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + self:AddAirwing(self.airbasename,self.alias,self.CapZoneName) self:I(self.lid.."Created new instance (v"..self.version..")") @@ -817,7 +845,7 @@ end -- @param #string From -- @param #string Event -- @param #string To --- @return #EASYGCICAP self +-- @return #EASYA2G self function EASYA2G:onafterStatus(From,Event,To) self:T({From,Event,To}) -- cleanup @@ -903,5 +931,3 @@ function EASYA2G:onafterStatus(From,Event,To) self:__Status(30) return self end - - diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 14b11e347..f7267d603 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -205,7 +205,7 @@ EASYGCICAP = { coalition = "blue", alias = nil, wings = {}, - Intel = nil, + Intel = nil, -- Ops.Intel#INTEL resurrection = 900, capspeed = 300, capalt = 25000, @@ -275,7 +275,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.30" +EASYGCICAP.version="0.1.32" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- @@ -351,6 +351,34 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self:__Start(math.random(6,12)) + --- On Before "Start" event. + -- @function [parent=#EASYGCICAP] OnBeforeStart + -- @param #EASYGCICAP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- On After "Start" event. + -- @function [parent=#EASYGCICAP] OnAfterStart + -- @param #EASYGCICAP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- On Before "Status" event. + -- @function [parent=#EASYGCICAP] OnBeforeStatus + -- @param #EASYGCICAP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- On After "Status" event. + -- @function [parent=#EASYGCICAP] OnAfterStatus + -- @param #EASYGCICAP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + return self end @@ -370,6 +398,28 @@ function EASYGCICAP:GetAirwing(AirbaseName) return nil end +--- Add an agent to the underlying INTEL detection - caution, we need to be started first for this to work! +-- Normally this isn't necessary when the Group name is correctly filled (see EWRName in `New()`). +-- @param #EASYGCICAP self +-- @param Wrapper.Group#GROUP Group The group object to be added as Intel Agent. +-- @return #EASYGCICAP self +function EASYGCICAP:AddAgent(Group) + self:T(self.lid.."AddAgent") + if Group:IsInstanceOf("GROUP") and self.Intel ~= nil then + self.Intel:AddAgent(Group) + if self.TankerInvisible == true then + Group:SetCommandInvisible(true) + Group:OptionROEHoldFire() + if Group:IsAir() then + Group:OptionROTEvadeFire() + else + Group:OptionDisperseOnAttack(30) + end + end + end + return self +end + --- Get a table of all managed AirWings -- @param #EASYGCICAP self -- @return #table Table of Ops.AirWing#AIRWING Airwings From 51f40081f357a7134bbe017ed705c83f6b50cf44 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 12 Dec 2025 12:51:44 +0100 Subject: [PATCH 05/17] docu header --- Moose Development/Moose/Ops/EasyA2G.lua | 11 +++++++++-- Moose Development/Moose/Ops/EasyGCICAP.lua | 9 +++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index b32f2df96..c5678dd60 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -1,3 +1,12 @@ +--- **Ops** - Create your A2G Defenses. +-- +-- **Main Features:** +-- +-- * Automatically create and manage A2G defenses using an AirWing and Squadrons for one coalition +-- * Easy set-up +-- +-- === +-- ------------------------------------------------------------------------- -- Easy A2G Engagement Class, based on OPS classes ------------------------------------------------------------------------- @@ -15,8 +24,6 @@ -- Last Update: Dec 2025 ------------------------------------------------------------------------- -- ---- **Ops** - Easy A2G Manager --- -- === -- -- **Main Features:** diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index f7267d603..fff814be0 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -1,3 +1,12 @@ +--- **Ops** - Create your A2A Defenses. +-- +-- **Main Features:** +-- +-- * Automatically create and manage A2A defenses using an AirWing and Squadrons for one coalition +-- * Easy set-up +-- +-- === +-- ------------------------------------------------------------------------- -- Easy CAP/GCI Class, based on OPS classes ------------------------------------------------------------------------- From 5e2481af7acbcfaba580e408a288978e8732e3b0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 Feb 2026 12:21:13 +0100 Subject: [PATCH 06/17] xx --- Moose Development/Moose/Cargo/Cargo.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index 76307dd78..57068feda 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -372,7 +372,7 @@ CARGOS = {} do -- CARGO - -- @type CARGO + --- @type CARGO -- @extends Core.Fsm#FSM_PROCESS -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. From d4de219032f2b8bd1f4226d62110c94612c208e7 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:36:32 +0100 Subject: [PATCH 07/17] Delete Moose Development/Moose/AI directory --- Moose Development/Moose/AI/AI_A2G_BAI.lua | 98 ---- Moose Development/Moose/AI/AI_CAP.lua | 543 ---------------------- 2 files changed, 641 deletions(-) delete mode 100644 Moose Development/Moose/AI/AI_A2G_BAI.lua delete mode 100644 Moose Development/Moose/AI/AI_CAP.lua diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua deleted file mode 100644 index 3ec713bad..000000000 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ /dev/null @@ -1,98 +0,0 @@ ---- **AI** - Models the process of air to ground BAI engagement for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_A2G_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2G_BAI --- @image AI_Air_To_Ground_Engage.JPG - ---- @type AI_A2G_BAI --- @extends AI.AI_Air_Engage#AI_A2A_Engage -- TODO: Documentation. This class does not exist, unable to determine what it extends. - ---- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- --- ![Banner Image](..\Images\deprecated.png) --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_A2G_BAI -AI_A2G_BAI = { - ClassName = "AI_A2G_BAI", -} - ---- Creates a new AI_A2G_BAI object --- @param #AI_A2G_BAI self --- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2G_BAI -function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - local AI_Air = AI_AIR:New( AIGroup ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL - local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - local self = BASE:Inherit( self, AI_Air_Engage ) - - return self -end - ---- Creates a new AI_A2G_BAI object --- @param #AI_A2G_BAI self --- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2G_BAI -function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType) -end - ---- Evaluate the attack and create an AttackUnitTask list. --- @param #AI_A2G_BAI self --- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. --- @param Wrapper.Group#GROUP DefenderGroup The group of defenders. --- @param #number EngageAltitude The altitude to engage the targets. --- @return #AI_A2G_BAI self -function AI_A2G_BAI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do - if AttackUnit then - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - self:T( { "BAI Unit:", AttackUnit:GetName() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) - end - end - end - - return AttackUnitTasks -end diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua deleted file mode 100644 index c504791f7..000000000 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ /dev/null @@ -1,543 +0,0 @@ ---- **AI** - Perform Combat Air Patrolling (CAP) for airplanes. --- --- **Features:** --- --- * Patrol AI airplanes within a given zone. --- * Trigger detected events when enemy airplanes are detected. --- * Manage a fuel threshold to RTB on time. --- * Engage the enemy when detected. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_CAP) --- --- === --- --- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1YCyPxJgoZn-CfhwyeW65L) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Quax**: Concept, Advice & Testing. --- * **Pikey**: Concept, Advice & Testing. --- * **Gunterlund**: Test case revision. --- * **Whisper**: Testing. --- * **Delta99**: Testing. --- --- === --- --- @module AI.AI_CAP --- @image AI_Combat_Air_Patrol.JPG - ---- @type AI_CAP_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. --- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed. --- @extends AI.AI_Patrol#AI_PATROL_ZONE - ---- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group} --- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- --- ![Banner Image](..\Images\deprecated.png) --- --- ![Process](..\Presentations\AI_CAP\Dia3.JPG) --- --- The AI_CAP_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_CAP\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_CAP\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_CAP\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_CAP\Dia9.JPG) --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- ![Process](..\Presentations\AI_CAP\Dia10.JPG) --- --- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_CAP\Dia13.JPG) --- --- ## 1. AI_CAP_ZONE constructor --- --- * @{#AI_CAP_ZONE.New}(): Creates a new AI_CAP_ZONE object. --- --- ## 2. AI_CAP_ZONE is a FSM --- --- ![Process](..\Presentations\AI_CAP\Dia2.JPG) --- --- ### 2.1 AI_CAP_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the bogeys. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2 AI_CAP_ZONE Events --- --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_CAP_ZONE.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_CAP_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Set the Range of Engagement --- --- ![Range](..\Presentations\AI_CAP\Dia11.JPG) --- --- An optional range can be set in meters, --- that will define when the AI will engage with the detected airborne enemy targets. --- The range can be beyond or smaller than the range of the Patrol Zone. --- The range is applied at the position of the AI. --- Use the method @{#AI_CAP_ZONE.SetEngageRange}() to define that range. --- --- ## 4. Set the Zone of Engagement --- --- ![Zone](..\Presentations\AI_CAP\Dia12.JPG) --- --- An optional @{Core.Zone} can be set, --- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{#AI_CAP_ZONE.SetEngageZone}() to define that Zone. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- --- === --- --- @field #AI_CAP_ZONE -AI_CAP_ZONE = { - ClassName = "AI_CAP_ZONE", -} - ---- Creates a new AI_CAP_ZONE object --- @param #AI_CAP_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAP_ZONE - - self.Accomplished = false - self.Engaging = false - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_CAP_ZONE] OnBeforeEngage - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_CAP_ZONE] OnAfterEngage - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAP_ZONE] Engage - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAP_ZONE] __Engage - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_CAP_ZONE] OnLeaveEngaging --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_CAP_ZONE] OnEnterEngaging --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_CAP_ZONE] OnBeforeFired - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_CAP_ZONE] OnAfterFired - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAP_ZONE] Fired - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAP_ZONE] __Fired - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] OnBeforeDestroy - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] OnAfterDestroy - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] Destroy - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] __Destroy - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_CAP_ZONE] OnBeforeAbort - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_CAP_ZONE] OnAfterAbort - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAP_ZONE] Abort - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAP_ZONE] __Abort - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] OnBeforeAccomplish - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] OnAfterAccomplish - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] Accomplish - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] __Accomplish - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - ---- Set the Engage Zone which defines where the AI will engage bogies. --- @param #AI_CAP_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP. --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - ---- Set the Engage Range when the AI will engage with airborne enemies. --- @param #AI_CAP_ZONE self --- @param #number EngageRange The Engage Range. --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:SetEngageRange( EngageRange ) - self:F2() - - if EngageRange then - self.EngageRange = EngageRange - else - self.EngageRange = nil - end -end - ---- onafter State Transition for Event Start. --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To ) - - -- Call the parent Start event handler - self:GetParent(self).onafterStart( self, Controllable, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) - -end - --- @param AI.AI_CAP#AI_CAP_ZONE --- @param Wrapper.Group#GROUP EngageGroup -function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm ) - - EngageGroup:F( { "AI_CAP_ZONE.EngageRoute:", EngageGroup:GetName() } ) - - if EngageGroup:IsAlive() then - Fsm:__Engage( 1 ) - end -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To ) - - if From ~= "Engaging" then - - local Engage = false - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( DetectedUnit ) - if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then - Engage = true - break - end - end - - if Engage == true then - self:F( 'Detected -> Engaging' ) - self:__Engage( 1 ) - end - end -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) - - if Controllable and Controllable:IsAlive() then - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - if not CurrentVec2 then return self end - - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - ToEngageZoneSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - COORDINATE.WaypointType.TurningPoint, - COORDINATE.WaypointAction.TurningPoint, - ToTargetSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - - Controllable:OptionROEOpenFire() - Controllable:OptionROTEvadeFire() - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( { DetectedUnit, DetectedUnit:IsAlive(), DetectedUnit:IsAir() } ) - if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then - if self.EngageZone then - if DetectedUnit:IsInZone( self.EngageZone ) then - self:F( {"Within Zone and Engaging ", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - else - if self.EngageRange then - if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3() ) <= self.EngageRange then - self:F( {"Within Range and Engaging", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - else - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - if #AttackTasks == 0 then - self:F("No targets found -> Going back to Patrolling") - self:__Abort( 1 ) - self:__Route( 1 ) - self:SetDetectionActivated() - else - - AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAP_ZONE.EngageRoute", self ) - EngageRoute[1].task = Controllable:TaskCombo( AttackTasks ) - - self:SetDetectionDeactivated() - end - - Controllable:Route( EngageRoute, 0.5 ) - - end -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionOff() -end - --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_CAP_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - --- @param #AI_CAP_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_CAP_ZONE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end From d12ad85fbde15842677f67fbcec5e12096b99ded Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:37:23 +0100 Subject: [PATCH 08/17] Delete Moose Development/Moose/Modules_local.lua --- Moose Development/Moose/Modules_local.lua | 187 ---------------------- 1 file changed, 187 deletions(-) delete mode 100644 Moose Development/Moose/Modules_local.lua diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua deleted file mode 100644 index f43c2a378..000000000 --- a/Moose Development/Moose/Modules_local.lua +++ /dev/null @@ -1,187 +0,0 @@ -__Moose.Include( 'Utilities\\Enums.lua' ) -__Moose.Include( 'Utilities\\Utils.lua' ) -__Moose.Include( 'Utilities\\Profiler.lua' ) ---__Moose.Include( 'Utilities\\STTS.lua' ) -__Moose.Include( 'Utilities\\FiFo.lua' ) -__Moose.Include( 'Utilities\\Socket.lua' ) - -__Moose.Include( 'Core\\Base.lua' ) -__Moose.Include( 'Core\\Beacon.lua' ) -__Moose.Include( 'Core\\UserFlag.lua' ) -__Moose.Include( 'Core\\Report.lua' ) -__Moose.Include( 'Core\\Scheduler.lua' ) -__Moose.Include( 'Core\\ScheduleDispatcher.lua' ) -__Moose.Include( 'Core\\Event.lua' ) -__Moose.Include( 'Core\\Settings.lua' ) -__Moose.Include( 'Core\\Menu.lua' ) -__Moose.Include( 'Core\\Zone.lua' ) -__Moose.Include( 'Core\\Velocity.lua' ) -__Moose.Include( 'Core\\Database.lua' ) -__Moose.Include( 'Core\\Set.lua' ) -__Moose.Include( 'Core\\Point.lua' ) -__Moose.Include( 'Core\\Pathline.lua' ) -__Moose.Include( 'Core\\Message.lua' ) -__Moose.Include( 'Core\\Fsm.lua' ) -__Moose.Include( 'Core\\Spawn.lua' ) -__Moose.Include( 'Core\\SpawnStatic.lua' ) -__Moose.Include( 'Core\\Timer.lua' ) -__Moose.Include( 'Core\\Goal.lua' ) -__Moose.Include( 'Core\\Spot.lua' ) -__Moose.Include( 'Core\\Astar.lua' ) -__Moose.Include( 'Core\\MarkerOps_Base.lua' ) -__Moose.Include( 'Core\\TextAndSound.lua' ) -__Moose.Include( 'Core\\Condition.lua' ) -__Moose.Include( 'Core\\ClientMenu.lua' ) -__Moose.Include( 'Core\\Vector.lua' ) - -__Moose.Include( 'Wrapper\\Object.lua' ) -__Moose.Include( 'Wrapper\\Identifiable.lua' ) -__Moose.Include( 'Wrapper\\Positionable.lua' ) -__Moose.Include( 'Wrapper\\Controllable.lua' ) -__Moose.Include( 'Wrapper\\Group.lua' ) -__Moose.Include( 'Wrapper\\Unit.lua' ) -__Moose.Include( 'Wrapper\\Client.lua' ) -__Moose.Include( 'Wrapper\\Static.lua' ) -__Moose.Include( 'Wrapper\\Airbase.lua' ) -__Moose.Include( 'Wrapper\\Scenery.lua' ) -__Moose.Include( 'Wrapper\\Marker.lua' ) -__Moose.Include( 'Wrapper\\Net.lua' ) -__Moose.Include( 'Wrapper\\Weapon.lua' ) -__Moose.Include( 'Wrapper\\Storage.lua' ) -__Moose.Include( 'Wrapper\\DynamicCargo.lua' ) - -__Moose.Include( 'Cargo\\Cargo.lua' ) -__Moose.Include( 'Cargo\\CargoUnit.lua' ) -__Moose.Include( 'Cargo\\CargoSlingload.lua' ) -__Moose.Include( 'Cargo\\CargoCrate.lua' ) -__Moose.Include( 'Cargo\\CargoGroup.lua' ) - -__Moose.Include( 'Functional\\Scoring.lua' ) -__Moose.Include( 'Functional\\CleanUp.lua' ) -__Moose.Include( 'Functional\\Movement.lua' ) -__Moose.Include( 'Functional\\Sead.lua' ) -__Moose.Include( 'Functional\\Escort.lua' ) -__Moose.Include( 'Functional\\MissileTrainer.lua' ) -__Moose.Include( 'Functional\\ATC_Ground.lua' ) -__Moose.Include( 'Functional\\Detection.lua' ) -__Moose.Include( 'Functional\\DetectionZones.lua' ) -__Moose.Include( 'Functional\\Designate.lua' ) -__Moose.Include( 'Functional\\RAT.lua' ) -__Moose.Include( 'Functional\\Range.lua' ) -__Moose.Include( 'Functional\\ZoneGoal.lua' ) -__Moose.Include( 'Functional\\ZoneGoalCoalition.lua' ) -__Moose.Include( 'Functional\\ZoneCaptureCoalition.lua' ) -__Moose.Include( 'Functional\\Artillery.lua' ) -__Moose.Include( 'Functional\\Suppression.lua' ) -__Moose.Include( 'Functional\\PseudoATC.lua' ) -__Moose.Include( 'Functional\\Warehouse.lua' ) -__Moose.Include( 'Functional\\Fox.lua' ) -__Moose.Include( 'Functional\\Mantis.lua' ) -__Moose.Include( 'Functional\\Shorad.lua' ) -__Moose.Include( 'Functional\\Autolase.lua' ) -__Moose.Include( 'Functional\\AICSAR.lua' ) -__Moose.Include( 'Functional\\AmmoTruck.lua' ) -__Moose.Include( 'Functional\\Tiresias.lua' ) -__Moose.Include( 'Functional\\Stratego.lua' ) -__Moose.Include( 'Functional\\ClientWatch.lua' ) - -__Moose.Include( 'Ops\\Airboss.lua' ) -__Moose.Include( 'Ops\\RecoveryTanker.lua' ) -__Moose.Include( 'Ops\\RescueHelo.lua' ) -__Moose.Include( 'Ops\\ATIS.lua' ) -__Moose.Include( 'Ops\\Auftrag.lua' ) -__Moose.Include( 'Ops\\Target.lua' ) -__Moose.Include( 'Ops\\OpsGroup.lua' ) -__Moose.Include( 'Ops\\FlightGroup.lua' ) -__Moose.Include( 'Ops\\NavyGroup.lua' ) -__Moose.Include( 'Ops\\ArmyGroup.lua' ) -__Moose.Include( 'Ops\\Cohort.lua' ) -__Moose.Include( 'Ops\\Squadron.lua' ) -__Moose.Include( 'Ops\\Platoon.lua' ) -__Moose.Include( 'Ops\\Legion.lua' ) -__Moose.Include( 'Ops\\AirWing.lua' ) -__Moose.Include( 'Ops\\Brigade.lua' ) -__Moose.Include( 'Ops\\Intelligence.lua' ) -__Moose.Include( 'Ops\\Commander.lua' ) -__Moose.Include( 'Ops\\OpsTransport.lua' ) -__Moose.Include( 'Ops\\CSAR.lua' ) -__Moose.Include( 'Ops\\CTLD.lua' ) -__Moose.Include( 'Ops\\OpsZone.lua' ) -__Moose.Include( 'Ops\\Chief.lua' ) -__Moose.Include( 'Ops\\Flotilla.lua' ) -__Moose.Include( 'Ops\\Fleet.lua' ) -__Moose.Include( 'Ops\\Awacs.lua' ) -__Moose.Include( 'Ops\\PlayerTask.lua' ) -__Moose.Include( 'Ops\\Operation.lua' ) -__Moose.Include( 'Ops\\FlightControl.lua' ) -__Moose.Include( 'Ops\\PlayerRecce.lua' ) -__Moose.Include( 'Ops\\EasyGCICAP.lua' ) -__Moose.Include( 'Ops\\EasyA2G.lua' ) - -__Moose.Include( 'AI\\AI_Balancer.lua' ) -__Moose.Include( 'AI\\AI_Air.lua' ) -__Moose.Include( 'AI\\AI_Air_Patrol.lua' ) -__Moose.Include( 'AI\\AI_Air_Engage.lua' ) -__Moose.Include( 'AI\\AI_A2A_Patrol.lua' ) -__Moose.Include( 'AI\\AI_A2A_Cap.lua' ) -__Moose.Include( 'AI\\AI_A2A_Gci.lua' ) -__Moose.Include( 'AI\\AI_A2A_Dispatcher.lua' ) -__Moose.Include( 'AI\\AI_A2G_BAI.lua' ) -__Moose.Include( 'AI\\AI_A2G_CAS.lua' ) -__Moose.Include( 'AI\\AI_A2G_SEAD.lua' ) -__Moose.Include( 'AI\\AI_A2G_Dispatcher.lua' ) -__Moose.Include( 'AI\\AI_Patrol.lua' ) -__Moose.Include( 'AI\\AI_Cap.lua' ) -__Moose.Include( 'AI\\AI_Cas.lua' ) -__Moose.Include( 'AI\\AI_Bai.lua' ) -__Moose.Include( 'AI\\AI_Formation.lua' ) -__Moose.Include( 'AI\\AI_Escort.lua' ) -__Moose.Include( 'AI\\AI_Escort_Request.lua' ) -__Moose.Include( 'AI\\AI_Escort_Dispatcher.lua' ) -__Moose.Include( 'AI\\AI_Escort_Dispatcher_Request.lua' ) -__Moose.Include( 'AI\\AI_Cargo.lua' ) -__Moose.Include( 'AI\\AI_Cargo_APC.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Helicopter.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Airplane.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Ship.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher_APC.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Helicopter.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Airplane.lua' ) -__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Ship.lua' ) - -__Moose.Include( 'Actions\\Act_Assign.lua' ) -__Moose.Include( 'Actions\\Act_Route.lua' ) -__Moose.Include( 'Actions\\Act_Account.lua' ) -__Moose.Include( 'Actions\\Act_Assist.lua' ) - -__Moose.Include( 'Sound\\UserSound.lua' ) -__Moose.Include( 'Sound\\SoundOutput.lua' ) -__Moose.Include( 'Sound\\Radio.lua' ) -__Moose.Include( 'Sound\\RadioQueue.lua' ) -__Moose.Include( 'Sound\\RadioSpeech.lua' ) -__Moose.Include( 'Sound\\SRS.lua' ) - -__Moose.Include( 'Tasking\\CommandCenter.lua' ) -__Moose.Include( 'Tasking\\Mission.lua' ) -__Moose.Include( 'Tasking\\Task.lua' ) -__Moose.Include( 'Tasking\\TaskInfo.lua' ) -__Moose.Include( 'Tasking\\Task_Manager.lua' ) -__Moose.Include( 'Tasking\\DetectionManager.lua' ) -__Moose.Include( 'Tasking\\Task_A2G_Dispatcher.lua' ) -__Moose.Include( 'Tasking\\Task_A2G.lua' ) -__Moose.Include( 'Tasking\\Task_A2A_Dispatcher.lua' ) -__Moose.Include( 'Tasking\\Task_A2A.lua' ) -__Moose.Include( 'Tasking\\Task_Cargo.lua' ) -__Moose.Include( 'Tasking\\Task_Cargo_Transport.lua' ) -__Moose.Include( 'Tasking\\Task_Cargo_CSAR.lua' ) -__Moose.Include( 'Tasking\\Task_Cargo_Dispatcher.lua' ) -__Moose.Include( 'Tasking\\Task_Capture_Zone.lua' ) -__Moose.Include( 'Tasking\\Task_Capture_Dispatcher.lua' ) - -__Moose.Include( 'Navigation\\Point.lua' ) -__Moose.Include( 'Navigation\\Beacons.lua' ) -__Moose.Include( 'Navigation\\Radios.lua' ) -__Moose.Include( 'Navigation\\Towns.lua' ) - -__Moose.Include( 'Globals.lua' ) From 80d2c9871df8ad402178b7794bf4a63f4e573c81 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 6 Feb 2026 17:29:03 +0100 Subject: [PATCH 09/17] xx --- Moose Development/Moose/Core/Event.lua | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index abe39b807..17240d9a7 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1068,6 +1068,36 @@ end do -- Event Creation + --- Creation of a New Cargo Event. + -- @param #EVENT self + -- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created. + function EVENT:CreateEventNewCargo( Cargo ) + self:F( { Cargo } ) + + local Event = { + id = EVENTS.NewCargo, + time = timer.getTime(), + cargo = Cargo, + } + + world.onEvent( Event ) + end + + --- Creation of a Cargo Deletion Event. + -- @param #EVENT self + -- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created. + function EVENT:CreateEventDeleteCargo( Cargo ) + self:F( { Cargo } ) + + local Event = { + id = EVENTS.DeleteCargo, + time = timer.getTime(), + cargo = Cargo, + } + + world.onEvent( Event ) + end + --- Creation of a New Zone Event. -- @param #EVENT self -- @param Core.Zone#ZONE_BASE Zone The Zone created. From 0beb6b20227797f536cdc7924f4ada37aa4077cc Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 7 Feb 2026 22:34:36 +0100 Subject: [PATCH 10/17] Warehouse & OpsTransport **WAREHOUSE** - Switched from AI_CARGO_DISPATCHER to OPSTRANSPORT **OPSTRANSPORT** - Fixed bugs --- .../Moose/Functional/Warehouse.lua | 268 ++++-------------- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 11 +- Moose Development/Moose/Ops/OpsTransport.lua | 6 +- 4 files changed, 71 insertions(+), 216 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 6db991fd6..597c81fe1 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -35,7 +35,6 @@ -- === -- -- ### Author: **funkyfranky** --- ### Co-author: FlightControl (cargo dispatcher classes) -- -- === -- @@ -132,14 +131,7 @@ -- a reasonable degree in DCS at the moment and hence cannot be used yet. -- -- Furthermore, ground assets can be transferred between warehouses by transport units. These are APCs, helicopters and airplanes. The transportation process is modeled --- in a realistic way by using the corresponding cargo dispatcher classes, i.e. --- --- * @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC} --- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER} --- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE} --- --- Depending on which cargo dispatcher is used (ground or airbore), similar considerations like in the self propelled case are necessary. Howver, note that --- the dispatchers as of yet cannot use user defined off road paths for example since they are classes of their own and use a different routing logic. +-- in a realistic way by using the @{Ops.OpsTransport#OPSTRANSPORT} class. -- -- === -- @@ -230,18 +222,6 @@ -- of 630 kg. This is important as groups cannot be split between carrier units when transporting, i.e. the total weight of the whole group must be smaller than the -- cargo bay of the transport carrier. -- --- ### Setting the Load Radius --- Boading and loading of cargo into a carrier is modeled in a realistic fashion in the AI\_CARGO\DISPATCHER classes, which are used inernally by the WAREHOUSE class. --- Meaning that troops (cargo) will board, i.e. run or drive to the carrier, and only once they are in close proximity to the transporter they will be loaded (disappear). --- --- Unfortunately, there are some situations where problems can occur. For example, in DCS tanks have the strong tentendcy not to drive around obstacles but rather to roll over them. --- I have seen cases where an aircraft of the same coalition as the tank was in its way and the tank drove right through the plane waiting on a parking spot and destroying it. --- --- As a workaround it is possible to set a larger load radius so that the cargo units are despawned further away from the carrier via the optional **loadradius** parameter: --- --- warehouseBatumi:AddAsset("Leopard 2", nil, nil, nil, nil, 250) --- --- Adding the asset like this will cause the units to be loaded into the carrier already at a distance of 250 meters. -- -- ### Setting the AI Skill -- @@ -486,7 +466,7 @@ -- and the road connection is less than 3 km. -- -- The user can set the road connection manually with the @{#WAREHOUSE.SetRoadConnection} function. This is only functional for self propelled assets at the moment --- and not if using the AI dispatcher classes since these have a different logic to find the route. +-- and not if using the OPSTRANSPORT class since this has a different logic to find the route. -- -- ## Off Road Connections -- @@ -595,7 +575,7 @@ -- -- ## Cargo Bay and Weight Limitations -- --- The transportation of cargo is handled by the AI\_Dispatcher classes. These take the cargo bay of a carrier and the weight of +-- The transportation of cargo is handled by the `OPSTRANSPORT` class. This takes the cargo bay of a carrier and the weight of -- the cargo into account so that a carrier can only load a realistic amount of cargo. -- -- However, if troops are supposed to be transported between warehouses, there is one important limitations one has to keep in mind. @@ -1798,12 +1778,13 @@ _WAREHOUSEDB = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="1.0.2a" +WAREHOUSE.version="2.0.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DONE: Switch from AI Dispatchers to OPSTRANSPORT -- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time? -- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths. -- NOGO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? @@ -3743,12 +3724,15 @@ function WAREHOUSE:_JobDone() -- Check conditions for being back home. local ishome=false - if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then - -- Units go back to the spawn zone, helicopters land and they should not move any more. - ishome=inspawnzone and onground and notmoving + if category==Group.Category.GROUND then + -- Ground units go back to the spawn zone and they should not move any more. + ishome=inspawnzone and notmoving elseif category==Group.Category.AIRPLANE then -- Planes need to be on ground at their home airbase and should not move any more. ishome=athomebase and onground and notmoving + elseif category==Group.Category.HELICOPTER then + -- Helicopters go back to their airbase or spawn zone and should not move any more. + ishome=(athomebase or inspawnzone) and onground and notmoving end -- Debug text. @@ -4697,136 +4681,48 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet ------------------------------------------------------------------------------------------------------------------------------------ -- Prepare cargo groups for transport ------------------------------------------------------------------------------------------------------------------------------------ + + -- TODO: set asset.weight for cargos - -- Board radius, i.e. when the cargo will begin to board the carrier - local _boardradius=500 + ------------------------- + -- Create OPSTRANSPORT -- + ------------------------- - if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then - _boardradius=5000 - elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - --_loadradius=1000 - --_boardradius=nil - elseif Request.transporttype==WAREHOUSE.TransportType.APC then - --_boardradius=nil - elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER - or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then - _boardradius=6000 - end - - -- Empty cargo group set. - local CargoGroups=SET_CARGO:New() - - -- Add cargo groups to set. - for _,_group in pairs(CargoGroupSet:GetSetObjects()) do - - -- Find asset belonging to this group. - local asset=self:FindAssetInDB(_group) - -- New cargo group object. - local cargogroup=CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_boardradius, asset.loadradius) - - -- Set weight for this group. - cargogroup:SetWeight(asset.weight) - - -- Add group to group set. - CargoGroups:AddCargo(cargogroup) - - end - - ------------------------ - -- Create Dispatchers -- - ------------------------ - - -- Cargo dispatcher. - local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER + -- OPSTRANSPORT + local CargoTransport --Ops.OpsTransport#OPSTRANSPORT if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then - -- Pickup and deploy zones. - local PickupAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName())) - local DeployAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName())) - - -- Define dispatcher for this task. - CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportGroupSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet) - - -- Set home zone. - CargoTransport:SetHomeZone(ZONE_AIRBASE:New(self.airbase:GetName())) + CargoTransport = OPSTRANSPORT:New(CargoGroupSet, ZONE_AIRBASE:New(self.airbase:GetName()), ZONE_AIRBASE:New(Request.airbase:GetName())) + CargoTransport:SetEmbarkZone(self.spawnzone) + CargoTransport:SetDisembarkZone(Request.warehouse.spawnzone) elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - - -- Pickup and deploy zones. - local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone) - local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) - - -- Define dispatcher for this task. - CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet) - - -- Home zone. - CargoTransport:SetHomeZone(self.spawnzone) + + CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.spawnzone, Request.warehouse.spawnzone) elseif Request.transporttype==WAREHOUSE.TransportType.APC then - -- Pickup and deploy zones. - local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone) - local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) - - -- Define dispatcher for this task. - CargoTransport = AI_CARGO_DISPATCHER_APC:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet, 0) - - -- Set home zone. - CargoTransport:SetHomeZone(self.spawnzone) + CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.spawnzone, Request.warehouse.spawnzone) elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER - or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then - - -- Pickup and deploy zones. - local PickupZoneSet = SET_ZONE:New():AddZone(self.portzone) - PickupZoneSet:AddZone(self.harborzone) - local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.harborzone) - + or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then + + CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.portzone, Request.warehouse.portzone) + CargoTransport:SetEmbarkZone(self.spawnzone) + CargoTransport:SetDisembarkZone(Request.warehouse.spawnzone) -- Get the shipping lane to use and pass it to the Dispatcher local remotename = Request.warehouse.warehouse:GetName() local ShippingLane = self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])] - - -- Define dispatcher for this task. - CargoTransport = AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet, ShippingLane) - - -- Set home zone - CargoTransport:SetHomeZone(self.portzone) + + -- TODO: Add shipping lane + -- CargoTransport:AddPathTransport(PathGroup) else self:E(self.lid.."ERROR: Unknown transporttype!") end - -- Set pickup and deploy radii. - -- The 20 m inner radius are to ensure that the helo does not land on the warehouse itself in the middle of the default spawn zone. - local pickupouter = 200 - local pickupinner = 0 - local deployouter = 200 - local deployinner = 0 - if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER - or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then - pickupouter=1000 - pickupinner=20 - deployouter=1000 - deployinner=0 - else - pickupouter=200 - pickupinner=0 - if self.spawnzone.Radius~=nil then - pickupouter=self.spawnzone.Radius - pickupinner=20 - end - deployouter=200 - deployinner=0 - if self.spawnzone.Radius~=nil then - deployouter=Request.warehouse.spawnzone.Radius - deployinner=20 - end - end - CargoTransport:SetPickupRadius(pickupouter, pickupinner) - CargoTransport:SetDeployRadius(deployouter, deployinner) - -- Adjust carrier units. This has to come AFTER the dispatchers have been defined because they set the cargobay free weight! Request.carriercargo={} @@ -4847,111 +4743,63 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet end end - -------------------------------- - -- Dispatcher Event Functions -- - -------------------------------- + ---------------------------------- + -- Opstransport Event Functions -- + ---------------------------------- - --- Function called after carrier picked up something. - function CargoTransport:OnAfterPickedUp(From, Event, To, Carrier, PickupZone) - - -- Get warehouse state. - local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE - - -- Debug message. - local text=string.format("Carrier group %s picked up at pickup zone %s.", Carrier:GetName(), PickupZone:GetName()) - warehouse:T(warehouse.lid..text) - - end - - --- Function called if something was deployed. - function CargoTransport:OnAfterDeployed(From, Event, To, Carrier, DeployZone) - - -- Get warehouse state. - local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE - - -- Debug message. - -- TODO: Depoloy zone is nil! - --local text=string.format("Carrier group %s deployed at deploy zone %s.", Carrier:GetName(), DeployZone:GetName()) - --warehouse:T(warehouse.lid..text) - - end - - --- Function called if carrier group is going home. - function CargoTransport:OnAfterHome(From, Event, To, Carrier, Coordinate, Speed, Height, HomeZone) - - -- Get warehouse state. - local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE - - -- Debug message. - local text=string.format("Carrier group %s going home to zone %s.", Carrier:GetName(), HomeZone:GetName()) - warehouse:T(warehouse.lid..text) - - end + CargoTransport.warehouse = self --- Function called when a carrier unit has loaded a cargo group. - function CargoTransport:OnAfterLoaded(From, Event, To, Carrier, Cargo, CarrierUnit, PickupZone) + function CargoTransport:OnAfterLoaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier, CarrierElement) -- Get warehouse state. - local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE - - -- Debug message. - local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone %s", Carrier:GetName(), Cargo:GetName(), CarrierUnit:GetName(), PickupZone:GetName()) - warehouse:T(warehouse.lid..text) + local warehouse=CargoTransport.warehouse --#WAREHOUSE -- Get cargo group object. - local group=Cargo:GetObject() --Wrapper.Group#GROUP + local group=OpsGroupCargo:GetGroup() --Cargo:GetObject() --Wrapper.Group#GROUP -- Get request. local request=warehouse:_GetRequestOfGroup(group, warehouse.pending) -- Add cargo group to this carrier. - table.insert(request.carriercargo[CarrierUnit:GetName()], warehouse:_GetNameWithOut(Cargo:GetName())) + table.insert(request.carriercargo[CarrierElement.name], warehouse:_GetNameWithOut(group:GetName())) end --- Function called when cargo has arrived and was unloaded. - function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo, CarrierUnit, DeployZone) + function CargoTransport:OnAfterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier) -- Get warehouse state. - local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + local warehouse=CargoTransport.warehouse --Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE -- Get group obejet. - local group=Cargo:GetObject() --Wrapper.Group#GROUP + local group=OpsGroupCargo:GetGroup() --Cargo:GetObject() --Wrapper.Group#GROUP -- Debug message. - local text=string.format("Cargo group %s was unloaded from carrier unit %s.", tostring(group:GetName()), tostring(CarrierUnit:GetName())) + local text=string.format("Cargo group %s was unloaded from carrier group %s.", tostring(group:GetName()), tostring(OpsGroupCarrier:GetName())) warehouse:T(warehouse.lid..text) - -- Load the cargo in the warehouse. - --Cargo:Load(warehouse.warehouse) - -- Trigger Arrived event. warehouse:Arrived(group) end + + -- TODO: Probably can also add some cargo/carrier dead functions here to simplify things at other places - --- On after BackHome event. - function CargoTransport:OnAfterBackHome(From, Event, To, Carrier) - - -- Intellisense. - local carrier=Carrier --Wrapper.Group#GROUP - - -- Get warehouse state. - local warehouse=carrier:GetState(carrier, "WAREHOUSE") --#WAREHOUSE - carrier:SmokeWhite() - - -- Debug info. - local text=string.format("Carrier %s is back home at warehouse %s.", tostring(Carrier:GetName()), tostring(warehouse.warehouse:GetName())) - MESSAGE:New(text, 5):ToAllIf(warehouse.Debug) - warehouse:I(warehouse.lid..text) - - -- Call arrived event for carrier. - warehouse:__Arrived(1, Carrier) + -- Assign cargo to carriers + for _,carriergroup in pairs(TransportGroupSet:GetSetObjects()) do + local opsgroup=nil + if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE or Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + opsgroup=FLIGHTGROUP:New(carriergroup) + elseif Request.transporttype==WAREHOUSE.TransportType.APC then + opsgroup=ARMYGROUP:New(carriergroup) + elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER + or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then + opsgroup=NAVYGROUP:New(carriergroup) + end + opsgroup:AddOpsTransport(CargoTransport) end - -- Start dispatcher. - CargoTransport:__Start(5) - end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 269dae89e..86a2be4a0 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2782,7 +2782,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n, N) -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. local current=self:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, waypointType, waypointAction, speed, true, nil, {}, "Current") table.insert(wp, current) - + -- Add remaining waypoints to route. for i=n, N do table.insert(wp, self.waypoints[i]) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index aedaee30e..7a1d8d3df 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -514,7 +514,7 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="1.0.4" +OPSGROUP.version="1.0.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -5100,7 +5100,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) if Task.description=="Task_Land_At" then self:T(self.lid.."Taske DONE Task_Land_At ==> Wait") - self:Cruise() + -- After the land task, we set the helo to wait. This is because of an issue that the passing waypoint function is triggered immidiately if we do not do this! self:Wait(20, 100) else self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") @@ -8331,7 +8331,8 @@ function OPSGROUP:_CheckCargoTransport() end -- Boarding finished ==> Transport cargo. - if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC, self) and not boarding then + local required=self.cargoTransport:_CheckRequiredCargos(self.cargoTZC, self) + if gotcargo and required and not boarding then self:T(self.lid.."Boarding/loading finished ==> Loaded") self.Tloading=nil self:LoadingDone() @@ -9967,6 +9968,10 @@ function OPSGROUP:onafterTransport(From, Event, To) self:T(self.lid.."ERROR: No current task but landed at?!") end end + + if self:IsWaiting() then + self:__Cruise(-10) + end elseif self:IsArmygroup() then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index f5c8e84f2..a6bbf9061 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -244,7 +244,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.8.0" +OPSTRANSPORT.version="0.9.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -2127,7 +2127,9 @@ function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo, CarrierGroup) requiredCargos={} for _,_cargo in pairs(TransportZoneCombo.Cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - table.insert(requiredCargos, cargo.opsgroup) + if not cargo.delivered then + table.insert(requiredCargos, cargo.opsgroup) + end end end From 2ebce9fd03b5256d996da6543d60f4b376b46b1c Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 10 Feb 2026 22:17:22 +0100 Subject: [PATCH 11/17] AUFTRAG - New FREIGHTTRANSPORT (internal cargo transportation) WIP --- Moose Development/Moose/Ops/Auftrag.lua | 64 +++++++++++++++++++++++- Moose Development/Moose/Ops/OpsGroup.lua | 5 ++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 4b49a2842..04978cb6e 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -458,6 +458,7 @@ _AUFTRAGSNR=0 -- @field #string NOTHING Nothing. -- @field #string PATROLRACETRACK Patrol Racetrack. -- @field #string STRAFING Strafing run. +-- @field #string FREIGHTTRANSPORT Freight transport. AUFTRAG.Type={ ANTISHIP="Anti Ship", AWACS="AWACS", @@ -506,6 +507,7 @@ AUFTRAG.Type={ NOTHING="Nothing", PATROLRACETRACK="Patrol Racetrack", STRAFING="Strafing", + FREIGHTTRANSPORT="FREIGHTTRANSPORT", } --- Special task description. @@ -2172,6 +2174,33 @@ function AUFTRAG:NewCARGOTRANSPORT(StaticCargo, DropZone) return mission end +--- **[AIR]** Create a FREIGHT TRANSPORT mission. +-- @param #AUFTRAG self +-- @param Wrapper.Static#STATIC StaticCargo Static cargo object. +-- @param Wrapper.Airbase#AIRBASE Destination Destination airbase, where the cargo is unloaded. +-- @return #AUFTRAG self +function AUFTRAG:NewFREIGHTTRANSPORT(StaticCargo, Destination) + + local mission=AUFTRAG:New(AUFTRAG.Type.FREIGHTTRANSPORT) + + mission:_TargetFromObject(StaticCargo) + + mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.FREIGHTTRANSPORT) + + -- Set ROE and ROT. + mission.optionROE=ENUMS.ROE.ReturnFire + mission.optionROT=ENUMS.ROT.PassiveDefense + + mission.categories={AUFTRAG.Category.HELICOPTER, AUFTRAG.Category.AIRCRAFT} + + mission.DCStask=mission:GetDCSMissionTask() + + mission.DCStask.params.groupId=StaticCargo:GetID() + mission.DCStask.params.cargo=StaticCargo + + return mission +end + --[[ --- **[AIR, GROUND, NAVAL]** Create a OPS TRANSPORT mission. @@ -6201,8 +6230,9 @@ end --- Get DCS task table for the given mission. -- @param #AUFTRAG self +-- @param Wrapper.Group#GROUP MissionGroup (Optional) Group that is supposed to carry out the mission. This might not exist when the AUFTRAG is created but only when the AUFTRAG is passed to a certain group. -- @return DCS#Task The DCS task table. If multiple tasks are necessary, this is returned as a combo task. -function AUFTRAG:GetDCSMissionTask() +function AUFTRAG:GetDCSMissionTask(MissionGroup) local DCStasks={} @@ -6545,6 +6575,36 @@ function AUFTRAG:GetDCSMissionTask() table.insert(DCStasks, TaskCargoTransportation) + elseif self.type==AUFTRAG.Type.FREIGHTTRANSPORT then + + ------------------------------ + -- FREIGHTTRANSPORT Mission -- + ------------------------------ + + local x=nil; local y=nil; local unitIdTransport=nil + if MissionGroup then + local vec2=MissionGroup:GetVec2() + x=vec2.x + y=vec2.y + unitIdTransport=MissionGroup:GetFirstUnit():GetID() + end + + local static=self.engageTarget:GetObject() --Wrapper.Static#STATIC + + -- Task to transport cargo. + local TaskCargoTransportation={ + id = "CargoTransportationPlane", + params = { + x=x, + y=y, + unitIdTransport=nil, + groupId=static:GetID(), + unitId=static:GetID(), + } + } + + table.insert(DCStasks, TaskCargoTransportation) + elseif self.type==AUFTRAG.Type.RESCUEHELO then ------------------------- @@ -7109,6 +7169,8 @@ function AUFTRAG:GetMissionTaskforMissionType(MissionType) mtask=ENUMS.MissionTask.TRANSPORT elseif MissionType==AUFTRAG.Type.CARGOTRANSPORT then mtask=ENUMS.MissionTask.TRANSPORT + elseif MissionType==AUFTRAG.Type.FREIGHTTRANSPORT then + mtask=ENUMS.MissionTask.TRANSPORT elseif MissionType==AUFTRAG.Type.ARMORATTACK then mtask=ENUMS.MissionTask.NOTHING elseif MissionType==AUFTRAG.Type.HOVER then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7a1d8d3df..a2457a00f 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6150,6 +6150,11 @@ function OPSGROUP:RouteToMission(mission, delay) end end + + elseif mission.type==AUFTRAG.Type.FREIGHTTRANSPORT then + + -- Refresh DCS task with the known controllable. + mission.DCStask=mission:GetDCSMissionTask(self.group) elseif mission.type==AUFTRAG.Type.ARTY then From 52986ce8deb7238ff5a7cf94ce034b71601c7f87 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 13 Feb 2026 12:01:34 +0100 Subject: [PATCH 12/17] AUFTRAG --- Moose Development/Moose/Ops/Auftrag.lua | 1 + Moose Development/Moose/Ops/OpsGroup.lua | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 04978cb6e..c284b1ffe 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2197,6 +2197,7 @@ function AUFTRAG:NewFREIGHTTRANSPORT(StaticCargo, Destination) mission.DCStask.params.groupId=StaticCargo:GetID() mission.DCStask.params.cargo=StaticCargo + mission.DCStask.params.destination=Destination return mission end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a2457a00f..50c69626e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6153,9 +6153,24 @@ function OPSGROUP:RouteToMission(mission, delay) elseif mission.type==AUFTRAG.Type.FREIGHTTRANSPORT then + --- + -- FREIGHTTRANSPORT + --- + + local destination=mission.DCStask.params.destination + local cargo=mission.DCStask.params.cargo + + -- Set the waypoint coordinate directly above the airbase. + -- The only way to ensure the cargo is delivered there, because when the task is executed, the cargo is delivered to the closest airbase. + -- Hopefully, ED will change the behaviour of this task but at the moment, it is what it is. + waypointcoord=destination:GetCoordinate() + -- Refresh DCS task with the known controllable. mission.DCStask=mission:GetDCSMissionTask(self.group) - + + mission.DCStask.params.destination=destination + mission.DCStask.params.cargo=cargo + elseif mission.type==AUFTRAG.Type.ARTY then --- From 1ac0249fe5a3236780ea8c466f6a2e0d8282a558 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 15 Feb 2026 22:44:39 +0100 Subject: [PATCH 13/17] AUFTRAG - improved FREIGHTTRANSPORT mission type --- Moose Development/Moose/Core/Fsm.lua | 10 ++++ Moose Development/Moose/Ops/Auftrag.lua | 70 ++++++++++++++++-------- Moose Development/Moose/Ops/OpsGroup.lua | 51 +++++++++++++++-- 3 files changed, 102 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index e86b88267..a4448d4af 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -833,6 +833,16 @@ do -- FSM return self._handler( self, EventName, ... ) end end + + --- Clear scheduled FSM event. + -- @param #FSM self + -- @param #string EventName Event name. + function FSM:_ClearFSMEvent( EventName ) + if self._EventSchedules[EventName] then + self.CallScheduler:Remove( self._EventSchedules[EventName] ) + self._EventSchedules[EventName]=nil + end + end --- Go sub. -- @param #FSM self diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index c284b1ffe..f219083b2 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2175,12 +2175,37 @@ function AUFTRAG:NewCARGOTRANSPORT(StaticCargo, DropZone) end --- **[AIR]** Create a FREIGHT TRANSPORT mission. +-- This mission type can be used to transport cargo items internally via suitable transport aircraft (planes and helicopters), e.g. C-130 or CH-47. +-- It supports transporting one or multiple cargos (weight limits are not checked). -- @param #AUFTRAG self --- @param Wrapper.Static#STATIC StaticCargo Static cargo object. +-- @param Wrapper.Static#STATIC StaticCargo Static cargo object. Can also be passed as a `SET_STATIC` object. -- @param Wrapper.Airbase#AIRBASE Destination Destination airbase, where the cargo is unloaded. -- @return #AUFTRAG self function AUFTRAG:NewFREIGHTTRANSPORT(StaticCargo, Destination) + -- Check if Destination is given + if Destination==nil then + self:E(self.lid..string.format("ERROR: Destination is nil for AUFTRAG:NewFREIGHTTRANSPORT! You must specify the destination airbase")) + return nil + elseif type(Destination)=="string" then + Destination=AIRBASE:FindByName(Destination) + end + + -- Check if Cargo is given + if StaticCargo==nil then + self:E(self.lid..string.format("ERROR: StaticCargo is nil for AUFTRAG:NewFREIGHTTRANSPORT! You must specify the static object that represents the cargo")) + return nil + elseif type(StaticCargo)=="string" then + StaticCargo=STATIC:FindByName(StaticCargo) + end + + -- Convert static to a set if necessary + if StaticCargo:IsInstanceOf("STATIC") then + local StaticCargoSet=SET_STATIC:New() --Core.Set#SET_STATIC + StaticCargoSet:AddCargo(StaticCargo) + StaticCargo=StaticCargoSet + end + local mission=AUFTRAG:New(AUFTRAG.Type.FREIGHTTRANSPORT) mission:_TargetFromObject(StaticCargo) @@ -2195,7 +2220,6 @@ function AUFTRAG:NewFREIGHTTRANSPORT(StaticCargo, Destination) mission.DCStask=mission:GetDCSMissionTask() - mission.DCStask.params.groupId=StaticCargo:GetID() mission.DCStask.params.cargo=StaticCargo mission.DCStask.params.destination=Destination @@ -6581,30 +6605,28 @@ function AUFTRAG:GetDCSMissionTask(MissionGroup) ------------------------------ -- FREIGHTTRANSPORT Mission -- ------------------------------ + + local statics=self.engageTarget:GetObjects() - local x=nil; local y=nil; local unitIdTransport=nil - if MissionGroup then - local vec2=MissionGroup:GetVec2() - x=vec2.x - y=vec2.y - unitIdTransport=MissionGroup:GetFirstUnit():GetID() + for _, StaticObject in pairs(statics) do + local static=StaticObject --Wrapper.Static#STATIC + + self:T(static) + + -- Task to unload the cargo + local TaskCargoUnload={ + ["id"] = "CargoUnloadPlane", + ["params"] = + { + ["groupId"] = static:GetID(), + ["unitId"] = static:GetID(), + } + } + + + table.insert(DCStasks, TaskCargoUnload) + end - - local static=self.engageTarget:GetObject() --Wrapper.Static#STATIC - - -- Task to transport cargo. - local TaskCargoTransportation={ - id = "CargoTransportationPlane", - params = { - x=x, - y=y, - unitIdTransport=nil, - groupId=static:GetID(), - unitId=static:GetID(), - } - } - - table.insert(DCStasks, TaskCargoTransportation) elseif self.type==AUFTRAG.Type.RESCUEHELO then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 50c69626e..c5617c94c 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5946,6 +5946,9 @@ function OPSGROUP:RouteToMission(mission, delay) -- Debug info. self:T(self.lid..string.format("Route To Mission")) + + -- Delay in seconds before cruise or updateroute is called. + local delayGo=-1 -- Catch dead or stopped groups. if self:IsDead() or self:IsStopped() then @@ -6164,13 +6167,51 @@ function OPSGROUP:RouteToMission(mission, delay) -- The only way to ensure the cargo is delivered there, because when the task is executed, the cargo is delivered to the closest airbase. -- Hopefully, ED will change the behaviour of this task but at the moment, it is what it is. waypointcoord=destination:GetCoordinate() + + -- Get additional parameters + mission.DCStask.params.destination=destination --Wrapper.Airbase#AIRBASE + mission.DCStask.params.cargo=cargo --Core.Set#SET_STATIC - -- Refresh DCS task with the known controllable. - mission.DCStask=mission:GetDCSMissionTask(self.group) + -- Get transport unit + local unit=self.group:GetFirstUnit() + local unitIdTransport=unit:GetID() + local vec2=unit:GetVec2() - mission.DCStask.params.destination=destination - mission.DCStask.params.cargo=cargo + -- Create tasks to load/transport statics cargos + local tasks={} + for StaticName, StaticObject in pairs(cargo:GetSet()) do + local static=StaticObject --Wrapper.Static#STATIC + + -- Task to transport cargo. + local TaskCargoTransportation={ + id = "CargoTransportationPlane", + params = { + x=vec2.x, + y=vec2.y, + unitIdTransport=unitIdTransport, + groupId=static:GetID(), + unitId=static:GetID(), + } + } + + table.insert(tasks, TaskCargoTransportation) + end + -- If we have multiple tasks, we create a combo task + local TaskCargo=nil + if #tasks==1 then + TaskCargo=tasks[1] + else + TaskCargo=CONTROLLABLE.TaskCombo(nil, tasks) + end + + -- We set the task to load the cargo into the aircraft. + -- We must be careful when calling updateroute because there the task is overwritten. + -- We also clear present "UpdateRoute" FSM events + self:_ClearFSMEvent( "UpdateRoute" ) + delayGo=-30 + self.group:SetTask(TaskCargo) + elseif mission.type==AUFTRAG.Type.ARTY then --- @@ -6329,7 +6370,7 @@ function OPSGROUP:RouteToMission(mission, delay) elseif self:IsNavygroup() then self:Cruise(SpeedToMission) elseif self:IsFlightgroup() then - self:UpdateRoute() + self:__UpdateRoute(delayGo) end end From 09fd4d25b70cab5286be51486d84202624d791ef Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 16 Feb 2026 22:25:59 +0100 Subject: [PATCH 14/17] AUFTRAG - Added check for cargo in zone --- Moose Development/Moose/Core/Set.lua | 22 ++++++++++++++++++++++ Moose Development/Moose/Ops/Auftrag.lua | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 0d567c472..b3e4f82cf 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -979,6 +979,28 @@ do -- SET_BASE return ObjectNames end + + --- Checks whether all or optionally any objects is inside a given zone. + -- @param #SET_BASE self + -- @param Core.Zone#ZONE Zone The zone. + -- @param #boolean Any If `true`, at least one object has to be inside the zone. If `false` or `nil`, all objects need to be in the zone. + -- @return #boolean Retruns `true` if objects are in the zone and `false` otherwise. + function SET_BASE:IsInZone(Zone, Any) + + for ObjectName, Object in pairs(self.Set) do + local object=Object --Wrapper.Positionable#POSITIONABLE + local inzone=object:IsInZone(Zone) + if inzone and Any then + -- We want at least one and this one is + return true + elseif not inzone then + -- We want all but at least one is not + return false + end + end + + return true + end --- Flushes the current SET_BASE contents in the log ... (for debugging reasons). -- @param #SET_BASE self diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f219083b2..0ff182dd2 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -4227,6 +4227,28 @@ function AUFTRAG:IsReadyToGo() if not startme then return false end + + if self.type==AUFTRAG.Type.FREIGHTTRANSPORT then + + local cargoset=self.DCStask.params.cargo --Core.Set#SET_STATIC + + for _,_opsgroup in pairs(self:GetOpsGroups()) do + local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP + + local vec2=opsgroup.group:GetFirstUnitAlive():GetVec2() + + local zone=ZONE_RADIUS:New("Bla", vec2, 40, true) + + local inzone=cargoset:IsInZone(zone) + + if not inzone then + printf("FF cargo is not inside zone") + return false + end + + end + + end -- We're good to go! From 8471132fe79b616a596d0a53052eff30809887c3 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 22 Feb 2026 16:31:03 +0100 Subject: [PATCH 15/17] AUFTRAG AUFTRAG FREIGHTTRANSPORT - Added proper success/failure evaluation --- Moose Development/Moose/Ops/Auftrag.lua | 27 ++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 0ff182dd2..05b5cfa72 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -676,7 +676,7 @@ AUFTRAG.Category={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="1.3.0" +AUFTRAG.version="1.4.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -2177,6 +2177,11 @@ end --- **[AIR]** Create a FREIGHT TRANSPORT mission. -- This mission type can be used to transport cargo items internally via suitable transport aircraft (planes and helicopters), e.g. C-130 or CH-47. -- It supports transporting one or multiple cargos (weight limits are not checked). +-- +-- All cargo must be within a 40 meter radius around the transport aircraft for the mission to start. +-- The mission is successful if any cargo item is delivered to the destination. +-- +-- This mission type uses the underlying DCS tasks: "Cargo Transportation (internal)", "Cargo Unload" -- @param #AUFTRAG self -- @param Wrapper.Static#STATIC StaticCargo Static cargo object. Can also be passed as a `SET_STATIC` object. -- @param Wrapper.Airbase#AIRBASE Destination Destination airbase, where the cargo is unloaded. @@ -4237,12 +4242,12 @@ function AUFTRAG:IsReadyToGo() local vec2=opsgroup.group:GetFirstUnitAlive():GetVec2() - local zone=ZONE_RADIUS:New("Bla", vec2, 40, true) + local zone=ZONE_RADIUS:New("Freighttransport", vec2, 40, true) local inzone=cargoset:IsInZone(zone) if not inzone then - printf("FF cargo is not inside zone") + self:T(self.lid.."FREIGHTTRANSPORT: cargo is not inside zone ==> mission not ready to start yet!") return false end @@ -4593,6 +4598,22 @@ function AUFTRAG:Evaluate() else failed=true end + + elseif self.type==AUFTRAG.Type.FREIGHTTRANSPORT then + + local cargoset=self.DCStask.params.cargo --Core.Set#SET_STATIC + + -- Get the destination airbase zone + local dest=self.DCStask.params.destination --Wrapper.Airbase#AIRBASE + local zone=dest:GetZone() + + -- Check if ANY cargo is inside the zone (might want to make it optional that all cargo needs to be) + local inzone=cargoset:IsInZone(zone, true) + + if not inzone then + self:I(self.lid.."FF Freight/cargo not delivered to airbase zone") + failed=true + end elseif self.type==AUFTRAG.Type.RESCUEHELO then From bfc9d357d6236b5c4b392001b87921da2ae609c2 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 24 Feb 2026 15:30:16 +0100 Subject: [PATCH 16/17] Update Auftrag.lua - Added GetCargoSet function --- Moose Development/Moose/Ops/Auftrag.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 05b5cfa72..23bf9f822 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2143,6 +2143,7 @@ function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet, DropoffCoordinate, PickupC end --- **[AIR ROTARY]** Create a CARGO TRANSPORT mission. +-- This mission is for helicopters only, which transport cargo externally via slingload. -- **Important Note:** -- The dropoff zone has to be a zone defined in the Mission Editor. This is due to a restriction in the used DCS task, which takes the zone ID as input. -- Only ME zones have an ID that can be referenced. @@ -5710,6 +5711,27 @@ function AUFTRAG:GetTargetLife() end end +--- Get cargo items as set SET object. +-- This returns the cargo item(s) as set `SET` object for mission types `CARGOTRANSPORT`, `TROOPTRANSPORT` and `FREIGHTTRANSPORT`. +-- @param #AUFTRAG self +-- @return Core.Set#SET_BASE The cargo set. +function AUFTRAG:GetCargoSet() + + if self.type==AUFTRAG.Type.CARGOTRANSPORT then + local set=SET_STATIC:New() + set:AddObject(self.DCStask.params.cargo) + return set + elseif self.type==AUFTRAG.Type.TROOPTRANSPORT then + return self.transportGroupSet + elseif self.type==AUFTRAG.Type.FREIGHTTRANSPORT then + return self.DCStask.params.cargo + else + self:E(self.lid.."ERROR: GetCargoSet() is only for transport types!") + return nil + end + +end + --- Get target. -- @param #AUFTRAG self -- @return Ops.Target#TARGET The target object. Could be many things. From 5ff5da9d9a181eb8383bcab80c3c01f3f9187a03 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 26 Feb 2026 08:08:44 +0100 Subject: [PATCH 17/17] Update Enums.lua - Added new Weapon type enums - Added torpedos to Auto weapon type --- Moose Development/Moose/Utilities/Enums.lua | 32 ++++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index f1d499109..1281bbf64 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -131,14 +131,24 @@ ENUMS.WeaponFlag={ Cannons = 805306368, -- GUN_POD + BuiltInCannon --- Torpedo Torpedo = 4294967296, + --- Decoy + Decoys = 8589934592, + --- Shell + SmokeShell = 17179869184, + IlluminationShell = 34359738368, + MarkerShell = 51539607552, + MarkerWeapon = 51539620864, + SubmunitionDispenserShell = 68719476736, + ConventionalShell = 206963736576, --- -- Even More Genral - Auto = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons) - AutoDCS = 1073741822, -- Something if often see - AnyAG = 2956984318, -- Any Air-To-Ground Weapon - AnyAA = 264241152, -- Any Air-To-Air Weapon - AnyUnguided = 2952822768, -- Any Unguided Weapon - AnyGuided = 268402702, -- Any Guided Weapon + Auto = 265214230526, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons + Torpedos) + AutoDCS = 1073741822, -- Something if often see + AnyAG = 2956984318, -- Any Air-To-Ground Weapon + AnyAA = 264241152, -- Any Air-To-Air Weapon + AnyUnguided = 2952822768, -- Any Unguided Weapon + AnyGuided = 268402702, -- Any Guided Weapon + AnyShell = 258503344128, -- Any Shell } --- Weapon types by category. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on hoggit wiki. @@ -220,11 +230,11 @@ ENUMS.WeaponType.Torpedo={ } ENUMS.WeaponType.Any={ -- General combinations - Weapon = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons) - AG = 2956984318, -- Any Air-To-Ground Weapon - AA = 264241152, -- Any Air-To-Air Weapon - Unguided = 2952822768, -- Any Unguided Weapon - Guided = 268402702, -- Any Guided Weapon + Weapon = 265214230526, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons + Torpedos) + AG = 2956984318, -- Any Air-To-Ground Weapon + AA = 264241152, -- Any Air-To-Air Weapon + Unguided = 2952822768, -- Any Unguided Weapon + Guided = 268402702, -- Any Guided Weapon }